From 8b67572ad7e1508341345dc46a2597e9fa170cbb Mon Sep 17 00:00:00 2001 From: Rafael Marçalo Date: Thu, 5 Sep 2024 16:31:33 +0100 Subject: Removed invasive branding --- src/com/pkrandom/CustomNamesSet.java | 237 + src/com/pkrandom/FileFunctions.java | 352 + src/com/pkrandom/GFXFunctions.java | 173 + src/com/pkrandom/MiscTweak.java | 98 + src/com/pkrandom/RandomSource.java | 233 + src/com/pkrandom/Randomizer.java | 1341 ++++ src/com/pkrandom/RomFunctions.java | 475 ++ src/com/pkrandom/Settings.java | 2388 +++++++ src/com/pkrandom/SettingsUpdater.java | 389 + src/com/pkrandom/SysConstants.java | 56 + src/com/pkrandom/Utils.java | 145 + src/com/pkrandom/Version.java | 95 + src/com/pkrandom/cli/CliRandomizer.java | 230 + src/com/pkrandom/config/Generation4.tbl | 1149 +++ src/com/pkrandom/config/Generation5.tbl | 46 + src/com/pkrandom/config/customnames.rncn | Bin 0 -> 1709 bytes src/com/pkrandom/config/gameboy_jpn.tbl | 180 + src/com/pkrandom/config/gba_english.tbl | 159 + src/com/pkrandom/config/gba_jpn.tbl | 254 + src/com/pkrandom/config/gen1_offsets.ini | 1169 +++ src/com/pkrandom/config/gen2_offsets.ini | 1026 +++ src/com/pkrandom/config/gen3_offsets.ini | 2366 ++++++ src/com/pkrandom/config/gen4_offsets.ini | 1477 ++++ src/com/pkrandom/config/gen5_offsets.ini | 957 +++ src/com/pkrandom/config/gen6_offsets.ini | 190 + src/com/pkrandom/config/gen7_offsets.ini | 148 + src/com/pkrandom/config/green_translation.tbl | 66 + src/com/pkrandom/config/gsc_english.tbl | 95 + src/com/pkrandom/config/gsc_espita.tbl | 121 + src/com/pkrandom/config/gsc_freger.tbl | 114 + src/com/pkrandom/config/rby_english.tbl | 87 + src/com/pkrandom/config/rby_espita.tbl | 118 + src/com/pkrandom/config/rby_freger.tbl | 111 + src/com/pkrandom/config/realistic_gen1_english.tbl | 254 + src/com/pkrandom/config/vietcrystal.tbl | 54 + src/com/pkrandom/constants/Abilities.java | 294 + src/com/pkrandom/constants/GBConstants.java | 52 + src/com/pkrandom/constants/Gen1Constants.java | 352 + src/com/pkrandom/constants/Gen1Items.java | 285 + src/com/pkrandom/constants/Gen2Constants.java | 431 ++ src/com/pkrandom/constants/Gen2Items.java | 285 + src/com/pkrandom/constants/Gen3Constants.java | 1319 ++++ src/com/pkrandom/constants/Gen3Items.java | 410 ++ src/com/pkrandom/constants/Gen4Constants.java | 2107 ++++++ src/com/pkrandom/constants/Gen5Constants.java | 1948 +++++ src/com/pkrandom/constants/Gen6Constants.java | 2171 ++++++ src/com/pkrandom/constants/Gen7Constants.java | 2243 ++++++ src/com/pkrandom/constants/GlobalConstants.java | 252 + src/com/pkrandom/constants/Items.java | 1670 +++++ src/com/pkrandom/constants/Moves.java | 854 +++ src/com/pkrandom/constants/N3DSConstants.java | 212 + src/com/pkrandom/constants/Species.java | 1407 ++++ src/com/pkrandom/ctr/AMX.java | 227 + src/com/pkrandom/ctr/BFLIM.java | 203 + src/com/pkrandom/ctr/GARCArchive.java | 388 + src/com/pkrandom/ctr/Mini.java | 102 + src/com/pkrandom/ctr/NCCH.java | 1024 +++ src/com/pkrandom/ctr/RomfsFile.java | 121 + src/com/pkrandom/ctr/SMDH.java | 118 + .../exceptions/CannotWriteToLocationException.java | 36 + .../pkrandom/exceptions/EncryptedROMException.java | 35 + .../InvalidSupplementFilesException.java | 50 + .../exceptions/RandomizationException.java | 34 + .../pkrandom/exceptions/RandomizerIOException.java | 40 + src/com/pkrandom/newgui/Bundle.properties | 638 ++ .../pkrandom/newgui/CustomNamesEditorDialog.java | 309 + src/com/pkrandom/newgui/GameUpdateFilter.java | 49 + .../pkrandom/newgui/NewGenerationLimitDialog.form | 156 + .../pkrandom/newgui/NewGenerationLimitDialog.java | 168 + src/com/pkrandom/newgui/NewRandomizerGUI.form | 3901 ++++++++++ src/com/pkrandom/newgui/NewRandomizerGUI.java | 3700 ++++++++++ src/com/pkrandom/newgui/OperationDialog.java | 137 + src/com/pkrandom/newgui/PresetFileFilter.java | 56 + src/com/pkrandom/newgui/PresetLoadDialog.java | 504 ++ src/com/pkrandom/newgui/PresetMakeDialog.java | 265 + src/com/pkrandom/newgui/QSFileFilter.java | 55 + src/com/pkrandom/newgui/ROMFilter.java | 56 + src/com/pkrandom/newgui/emptyIcon.png | Bin 0 -> 230 bytes src/com/pkrandom/newgui/loading.gif | Bin 0 -> 3208 bytes src/com/pkrandom/newnds/CRC16.java | 52 + src/com/pkrandom/newnds/NARCArchive.java | 221 + src/com/pkrandom/newnds/NDSFile.java | 118 + src/com/pkrandom/newnds/NDSRom.java | 712 ++ src/com/pkrandom/newnds/NDSY9Entry.java | 139 + src/com/pkrandom/patches/bwexp/crystal_en_bwxp.ips | Bin 0 -> 1519 bytes src/com/pkrandom/patches/bwexp/gs_en_bwxp.ips | Bin 0 -> 1519 bytes src/com/pkrandom/patches/bwexp/rb_en_bwxp.ips | Bin 0 -> 1265 bytes src/com/pkrandom/patches/bwexp/yellow_en_bwxp.ips | Bin 0 -> 1265 bytes .../patches/hardcoded_statics/em_firstbattle.ips | Bin 0 -> 106 bytes .../patches/hardcoded_statics/fr_marowak_10.ips | Bin 0 -> 76 bytes .../patches/hardcoded_statics/fr_marowak_11.ips | Bin 0 -> 76 bytes .../patches/hardcoded_statics/lg_marowak_10.ips | Bin 0 -> 76 bytes .../patches/hardcoded_statics/lg_marowak_11.ips | Bin 0 -> 76 bytes .../hardcoded_statics/roamers/fr_roamers_10.ips | Bin 0 -> 75 bytes .../hardcoded_statics/roamers/fr_roamers_11.ips | Bin 0 -> 75 bytes .../hardcoded_statics/roamers/hgss_roamers.ips | Bin 0 -> 179 bytes .../hardcoded_statics/roamers/lg_roamers_10.ips | Bin 0 -> 75 bytes .../hardcoded_statics/roamers/lg_roamers_11.ips | Bin 0 -> 75 bytes .../hardcoded_statics/roamers/plat_roamers.ips | Bin 0 -> 243 bytes .../patches/hardcoded_statics/rs_firstbattle.ips | Bin 0 -> 104 bytes .../pkrandom/patches/hgss_catching_tutorialfix.ips | Bin 0 -> 123 bytes .../patches/instant_text/b1_instant_text.ips | Bin 0 -> 103 bytes .../patches/instant_text/b2_instant_text.ips | Bin 0 -> 24 bytes .../patches/instant_text/dp_instant_text.ips | Bin 0 -> 48 bytes .../patches/instant_text/em_instant_text.ips | Bin 0 -> 256 bytes .../patches/instant_text/fr_10_instant_text.ips | Bin 0 -> 241 bytes .../patches/instant_text/fr_11_instant_text.ips | Bin 0 -> 241 bytes .../patches/instant_text/hgss_instant_text.ips | Bin 0 -> 49 bytes .../patches/instant_text/lg_10_instant_text.ips | Bin 0 -> 241 bytes .../patches/instant_text/lg_11_instant_text.ips | Bin 0 -> 241 bytes .../patches/instant_text/plat_instant_text.ips | Bin 0 -> 49 bytes .../patches/instant_text/ruby_10_instant_text.ips | Bin 0 -> 270 bytes .../patches/instant_text/ruby_11_instant_text.ips | Bin 0 -> 270 bytes .../patches/instant_text/ruby_12_instant_text.ips | Bin 0 -> 270 bytes .../instant_text/sapphire_10_instant_text.ips | Bin 0 -> 270 bytes .../instant_text/sapphire_11_instant_text.ips | Bin 0 -> 270 bytes .../instant_text/sapphire_12_instant_text.ips | Bin 0 -> 270 bytes .../patches/instant_text/w1_instant_text.ips | Bin 0 -> 103 bytes .../patches/instant_text/w2_instant_text.ips | Bin 0 -> 24 bytes .../pkrandom/patches/musicfix/black2_musicfix.ips | Bin 0 -> 460 bytes .../patches/musicfix/black2_ovl36_musicfix.ips | Bin 0 -> 41 bytes .../pkrandom/patches/musicfix/black_musicfix.ips | Bin 0 -> 264 bytes .../patches/musicfix/black_ovl21_musicfix.ips | Bin 0 -> 39 bytes .../pkrandom/patches/musicfix/diamond_musicfix.ips | Bin 0 -> 202 bytes src/com/pkrandom/patches/musicfix/em_musicfix.ips | Bin 0 -> 160 bytes .../pkrandom/patches/musicfix/fr_musicfix_10.ips | Bin 0 -> 137 bytes .../pkrandom/patches/musicfix/fr_musicfix_11.ips | Bin 0 -> 137 bytes .../pkrandom/patches/musicfix/lg_musicfix_10.ips | Bin 0 -> 137 bytes .../pkrandom/patches/musicfix/lg_musicfix_11.ips | Bin 0 -> 137 bytes .../pkrandom/patches/musicfix/plat_musicfix.ips | Bin 0 -> 277 bytes .../pkrandom/patches/musicfix/white2_musicfix.ips | Bin 0 -> 460 bytes .../patches/musicfix/white2_ovl36_musicfix.ips | Bin 0 -> 41 bytes .../pkrandom/patches/musicfix/white_musicfix.ips | Bin 0 -> 264 bytes .../patches/musicfix/white_ovl21_musicfix.ips | Bin 0 -> 39 bytes .../patches/national_dex/bw1_national_dex.ips | Bin 0 -> 1753 bytes .../patches/national_dex/bw2_national_dex.ips | Bin 0 -> 6540 bytes .../patches/national_dex/dp_national_dex.ips | Bin 0 -> 30 bytes .../patches/national_dex/hgss_national_dex.ips | Bin 0 -> 404 bytes .../patches/national_dex/plat_national_dex.ips | Bin 0 -> 20 bytes .../pkrandom/patches/pt_fast_distortion_world.ips | Bin 0 -> 372 bytes src/com/pkrandom/patches/rb_en_critrate.ips | Bin 0 -> 80 bytes src/com/pkrandom/patches/rb_en_xaccnerf.ips | Bin 0 -> 48 bytes .../patches/shedinja/black2_ovl284_shedinja.ips | Bin 0 -> 31 bytes .../pkrandom/patches/shedinja/black2_shedinja.ips | Bin 0 -> 69 bytes .../patches/shedinja/black_ovl195_shedinja.ips | Bin 0 -> 31 bytes .../pkrandom/patches/shedinja/black_shedinja.ips | Bin 0 -> 61 bytes .../patches/shedinja/white2_ovl284_shedinja.ips | Bin 0 -> 31 bytes .../pkrandom/patches/shedinja/white2_shedinja.ips | Bin 0 -> 69 bytes .../patches/shedinja/white_ovl195_shedinja.ips | Bin 0 -> 31 bytes .../pkrandom/patches/shedinja/white_shedinja.ips | Bin 0 -> 61 bytes src/com/pkrandom/patches/yellow_en_critrate.ips | Bin 0 -> 80 bytes src/com/pkrandom/patches/yellow_en_xaccnerf.ips | Bin 0 -> 48 bytes src/com/pkrandom/pokemon/Aura.java | 80 + src/com/pkrandom/pokemon/CriticalChance.java | 31 + src/com/pkrandom/pokemon/Effectiveness.java | 178 + src/com/pkrandom/pokemon/Encounter.java | 48 + src/com/pkrandom/pokemon/EncounterSet.java | 43 + src/com/pkrandom/pokemon/Evolution.java | 83 + src/com/pkrandom/pokemon/EvolutionType.java | 111 + src/com/pkrandom/pokemon/EvolutionUpdate.java | 72 + src/com/pkrandom/pokemon/ExpCurve.java | 85 + src/com/pkrandom/pokemon/FormeInfo.java | 36 + src/com/pkrandom/pokemon/Gen1Pokemon.java | 149 + src/com/pkrandom/pokemon/GenRestrictions.java | 144 + src/com/pkrandom/pokemon/IngameTrade.java | 40 + src/com/pkrandom/pokemon/ItemList.java | 105 + src/com/pkrandom/pokemon/MegaEvolution.java | 39 + src/com/pkrandom/pokemon/Move.java | 103 + src/com/pkrandom/pokemon/MoveCategory.java | 28 + src/com/pkrandom/pokemon/MoveLearnt.java | 35 + src/com/pkrandom/pokemon/MoveSynergy.java | 1204 ++++ src/com/pkrandom/pokemon/PickupItem.java | 11 + src/com/pkrandom/pokemon/Pokemon.java | 324 + src/com/pkrandom/pokemon/SOSType.java | 28 + src/com/pkrandom/pokemon/Shop.java | 42 + src/com/pkrandom/pokemon/Stat.java | 45 + src/com/pkrandom/pokemon/StatChange.java | 35 + src/com/pkrandom/pokemon/StatChangeMoveType.java | 35 + src/com/pkrandom/pokemon/StatChangeType.java | 38 + src/com/pkrandom/pokemon/StaticEncounter.java | 98 + src/com/pkrandom/pokemon/StatusMoveType.java | 31 + src/com/pkrandom/pokemon/StatusType.java | 37 + src/com/pkrandom/pokemon/TotemPokemon.java | 56 + src/com/pkrandom/pokemon/Trainer.java | 156 + src/com/pkrandom/pokemon/TrainerPokemon.java | 109 + src/com/pkrandom/pokemon/Type.java | 77 + src/com/pkrandom/pokemon/TypeRelationship.java | 37 + .../romhandlers/Abstract3DSRomHandler.java | 350 + .../pkrandom/romhandlers/AbstractDSRomHandler.java | 390 + .../romhandlers/AbstractGBCRomHandler.java | 224 + .../pkrandom/romhandlers/AbstractGBRomHandler.java | 210 + .../pkrandom/romhandlers/AbstractRomHandler.java | 7558 ++++++++++++++++++++ src/com/pkrandom/romhandlers/Gen1RomHandler.java | 2918 ++++++++ src/com/pkrandom/romhandlers/Gen2RomHandler.java | 2999 ++++++++ src/com/pkrandom/romhandlers/Gen3RomHandler.java | 4473 ++++++++++++ src/com/pkrandom/romhandlers/Gen4RomHandler.java | 5841 +++++++++++++++ src/com/pkrandom/romhandlers/Gen5RomHandler.java | 4343 +++++++++++ src/com/pkrandom/romhandlers/Gen6RomHandler.java | 4270 +++++++++++ src/com/pkrandom/romhandlers/Gen7RomHandler.java | 3821 ++++++++++ src/com/pkrandom/romhandlers/RomHandler.java | 660 ++ 200 files changed, 88089 insertions(+) create mode 100644 src/com/pkrandom/CustomNamesSet.java create mode 100755 src/com/pkrandom/FileFunctions.java create mode 100644 src/com/pkrandom/GFXFunctions.java create mode 100755 src/com/pkrandom/MiscTweak.java create mode 100755 src/com/pkrandom/RandomSource.java create mode 100644 src/com/pkrandom/Randomizer.java create mode 100755 src/com/pkrandom/RomFunctions.java create mode 100644 src/com/pkrandom/Settings.java create mode 100644 src/com/pkrandom/SettingsUpdater.java create mode 100644 src/com/pkrandom/SysConstants.java create mode 100644 src/com/pkrandom/Utils.java create mode 100644 src/com/pkrandom/Version.java create mode 100644 src/com/pkrandom/cli/CliRandomizer.java create mode 100755 src/com/pkrandom/config/Generation4.tbl create mode 100755 src/com/pkrandom/config/Generation5.tbl create mode 100644 src/com/pkrandom/config/customnames.rncn create mode 100644 src/com/pkrandom/config/gameboy_jpn.tbl create mode 100755 src/com/pkrandom/config/gba_english.tbl create mode 100644 src/com/pkrandom/config/gba_jpn.tbl create mode 100755 src/com/pkrandom/config/gen1_offsets.ini create mode 100755 src/com/pkrandom/config/gen2_offsets.ini create mode 100755 src/com/pkrandom/config/gen3_offsets.ini create mode 100755 src/com/pkrandom/config/gen4_offsets.ini create mode 100755 src/com/pkrandom/config/gen5_offsets.ini create mode 100644 src/com/pkrandom/config/gen6_offsets.ini create mode 100644 src/com/pkrandom/config/gen7_offsets.ini create mode 100755 src/com/pkrandom/config/green_translation.tbl create mode 100755 src/com/pkrandom/config/gsc_english.tbl create mode 100755 src/com/pkrandom/config/gsc_espita.tbl create mode 100755 src/com/pkrandom/config/gsc_freger.tbl create mode 100755 src/com/pkrandom/config/rby_english.tbl create mode 100755 src/com/pkrandom/config/rby_espita.tbl create mode 100755 src/com/pkrandom/config/rby_freger.tbl create mode 100644 src/com/pkrandom/config/realistic_gen1_english.tbl create mode 100755 src/com/pkrandom/config/vietcrystal.tbl create mode 100644 src/com/pkrandom/constants/Abilities.java create mode 100644 src/com/pkrandom/constants/GBConstants.java create mode 100644 src/com/pkrandom/constants/Gen1Constants.java create mode 100644 src/com/pkrandom/constants/Gen1Items.java create mode 100644 src/com/pkrandom/constants/Gen2Constants.java create mode 100644 src/com/pkrandom/constants/Gen2Items.java create mode 100644 src/com/pkrandom/constants/Gen3Constants.java create mode 100644 src/com/pkrandom/constants/Gen3Items.java create mode 100644 src/com/pkrandom/constants/Gen4Constants.java create mode 100644 src/com/pkrandom/constants/Gen5Constants.java create mode 100644 src/com/pkrandom/constants/Gen6Constants.java create mode 100644 src/com/pkrandom/constants/Gen7Constants.java create mode 100644 src/com/pkrandom/constants/GlobalConstants.java create mode 100644 src/com/pkrandom/constants/Items.java create mode 100644 src/com/pkrandom/constants/Moves.java create mode 100644 src/com/pkrandom/constants/N3DSConstants.java create mode 100644 src/com/pkrandom/constants/Species.java create mode 100644 src/com/pkrandom/ctr/AMX.java create mode 100644 src/com/pkrandom/ctr/BFLIM.java create mode 100644 src/com/pkrandom/ctr/GARCArchive.java create mode 100644 src/com/pkrandom/ctr/Mini.java create mode 100644 src/com/pkrandom/ctr/NCCH.java create mode 100644 src/com/pkrandom/ctr/RomfsFile.java create mode 100644 src/com/pkrandom/ctr/SMDH.java create mode 100644 src/com/pkrandom/exceptions/CannotWriteToLocationException.java create mode 100644 src/com/pkrandom/exceptions/EncryptedROMException.java create mode 100755 src/com/pkrandom/exceptions/InvalidSupplementFilesException.java create mode 100644 src/com/pkrandom/exceptions/RandomizationException.java create mode 100644 src/com/pkrandom/exceptions/RandomizerIOException.java create mode 100644 src/com/pkrandom/newgui/Bundle.properties create mode 100644 src/com/pkrandom/newgui/CustomNamesEditorDialog.java create mode 100644 src/com/pkrandom/newgui/GameUpdateFilter.java create mode 100644 src/com/pkrandom/newgui/NewGenerationLimitDialog.form create mode 100644 src/com/pkrandom/newgui/NewGenerationLimitDialog.java create mode 100644 src/com/pkrandom/newgui/NewRandomizerGUI.form create mode 100644 src/com/pkrandom/newgui/NewRandomizerGUI.java create mode 100644 src/com/pkrandom/newgui/OperationDialog.java create mode 100644 src/com/pkrandom/newgui/PresetFileFilter.java create mode 100644 src/com/pkrandom/newgui/PresetLoadDialog.java create mode 100644 src/com/pkrandom/newgui/PresetMakeDialog.java create mode 100644 src/com/pkrandom/newgui/QSFileFilter.java create mode 100644 src/com/pkrandom/newgui/ROMFilter.java create mode 100644 src/com/pkrandom/newgui/emptyIcon.png create mode 100644 src/com/pkrandom/newgui/loading.gif create mode 100755 src/com/pkrandom/newnds/CRC16.java create mode 100644 src/com/pkrandom/newnds/NARCArchive.java create mode 100755 src/com/pkrandom/newnds/NDSFile.java create mode 100755 src/com/pkrandom/newnds/NDSRom.java create mode 100755 src/com/pkrandom/newnds/NDSY9Entry.java create mode 100644 src/com/pkrandom/patches/bwexp/crystal_en_bwxp.ips create mode 100644 src/com/pkrandom/patches/bwexp/gs_en_bwxp.ips create mode 100644 src/com/pkrandom/patches/bwexp/rb_en_bwxp.ips create mode 100644 src/com/pkrandom/patches/bwexp/yellow_en_bwxp.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/em_firstbattle.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/fr_marowak_10.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/fr_marowak_11.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/lg_marowak_10.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/lg_marowak_11.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/roamers/fr_roamers_10.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/roamers/fr_roamers_11.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/roamers/hgss_roamers.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/roamers/lg_roamers_10.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/roamers/lg_roamers_11.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/roamers/plat_roamers.ips create mode 100644 src/com/pkrandom/patches/hardcoded_statics/rs_firstbattle.ips create mode 100644 src/com/pkrandom/patches/hgss_catching_tutorialfix.ips create mode 100644 src/com/pkrandom/patches/instant_text/b1_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/b2_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/dp_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/em_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/fr_10_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/fr_11_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/hgss_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/lg_10_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/lg_11_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/plat_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/ruby_10_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/ruby_11_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/ruby_12_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/sapphire_10_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/sapphire_11_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/sapphire_12_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/w1_instant_text.ips create mode 100644 src/com/pkrandom/patches/instant_text/w2_instant_text.ips create mode 100644 src/com/pkrandom/patches/musicfix/black2_musicfix.ips create mode 100644 src/com/pkrandom/patches/musicfix/black2_ovl36_musicfix.ips create mode 100644 src/com/pkrandom/patches/musicfix/black_musicfix.ips create mode 100644 src/com/pkrandom/patches/musicfix/black_ovl21_musicfix.ips create mode 100644 src/com/pkrandom/patches/musicfix/diamond_musicfix.ips create mode 100644 src/com/pkrandom/patches/musicfix/em_musicfix.ips create mode 100644 src/com/pkrandom/patches/musicfix/fr_musicfix_10.ips create mode 100644 src/com/pkrandom/patches/musicfix/fr_musicfix_11.ips create mode 100644 src/com/pkrandom/patches/musicfix/lg_musicfix_10.ips create mode 100644 src/com/pkrandom/patches/musicfix/lg_musicfix_11.ips create mode 100644 src/com/pkrandom/patches/musicfix/plat_musicfix.ips create mode 100644 src/com/pkrandom/patches/musicfix/white2_musicfix.ips create mode 100644 src/com/pkrandom/patches/musicfix/white2_ovl36_musicfix.ips create mode 100644 src/com/pkrandom/patches/musicfix/white_musicfix.ips create mode 100644 src/com/pkrandom/patches/musicfix/white_ovl21_musicfix.ips create mode 100644 src/com/pkrandom/patches/national_dex/bw1_national_dex.ips create mode 100644 src/com/pkrandom/patches/national_dex/bw2_national_dex.ips create mode 100644 src/com/pkrandom/patches/national_dex/dp_national_dex.ips create mode 100644 src/com/pkrandom/patches/national_dex/hgss_national_dex.ips create mode 100644 src/com/pkrandom/patches/national_dex/plat_national_dex.ips create mode 100644 src/com/pkrandom/patches/pt_fast_distortion_world.ips create mode 100755 src/com/pkrandom/patches/rb_en_critrate.ips create mode 100755 src/com/pkrandom/patches/rb_en_xaccnerf.ips create mode 100644 src/com/pkrandom/patches/shedinja/black2_ovl284_shedinja.ips create mode 100644 src/com/pkrandom/patches/shedinja/black2_shedinja.ips create mode 100644 src/com/pkrandom/patches/shedinja/black_ovl195_shedinja.ips create mode 100644 src/com/pkrandom/patches/shedinja/black_shedinja.ips create mode 100644 src/com/pkrandom/patches/shedinja/white2_ovl284_shedinja.ips create mode 100644 src/com/pkrandom/patches/shedinja/white2_shedinja.ips create mode 100644 src/com/pkrandom/patches/shedinja/white_ovl195_shedinja.ips create mode 100644 src/com/pkrandom/patches/shedinja/white_shedinja.ips create mode 100755 src/com/pkrandom/patches/yellow_en_critrate.ips create mode 100755 src/com/pkrandom/patches/yellow_en_xaccnerf.ips create mode 100644 src/com/pkrandom/pokemon/Aura.java create mode 100644 src/com/pkrandom/pokemon/CriticalChance.java create mode 100644 src/com/pkrandom/pokemon/Effectiveness.java create mode 100755 src/com/pkrandom/pokemon/Encounter.java create mode 100755 src/com/pkrandom/pokemon/EncounterSet.java create mode 100755 src/com/pkrandom/pokemon/Evolution.java create mode 100755 src/com/pkrandom/pokemon/EvolutionType.java create mode 100644 src/com/pkrandom/pokemon/EvolutionUpdate.java create mode 100755 src/com/pkrandom/pokemon/ExpCurve.java create mode 100644 src/com/pkrandom/pokemon/FormeInfo.java create mode 100644 src/com/pkrandom/pokemon/Gen1Pokemon.java create mode 100755 src/com/pkrandom/pokemon/GenRestrictions.java create mode 100755 src/com/pkrandom/pokemon/IngameTrade.java create mode 100755 src/com/pkrandom/pokemon/ItemList.java create mode 100644 src/com/pkrandom/pokemon/MegaEvolution.java create mode 100755 src/com/pkrandom/pokemon/Move.java create mode 100644 src/com/pkrandom/pokemon/MoveCategory.java create mode 100755 src/com/pkrandom/pokemon/MoveLearnt.java create mode 100644 src/com/pkrandom/pokemon/MoveSynergy.java create mode 100644 src/com/pkrandom/pokemon/PickupItem.java create mode 100755 src/com/pkrandom/pokemon/Pokemon.java create mode 100644 src/com/pkrandom/pokemon/SOSType.java create mode 100644 src/com/pkrandom/pokemon/Shop.java create mode 100644 src/com/pkrandom/pokemon/Stat.java create mode 100644 src/com/pkrandom/pokemon/StatChange.java create mode 100644 src/com/pkrandom/pokemon/StatChangeMoveType.java create mode 100644 src/com/pkrandom/pokemon/StatChangeType.java create mode 100644 src/com/pkrandom/pokemon/StaticEncounter.java create mode 100644 src/com/pkrandom/pokemon/StatusMoveType.java create mode 100644 src/com/pkrandom/pokemon/StatusType.java create mode 100644 src/com/pkrandom/pokemon/TotemPokemon.java create mode 100755 src/com/pkrandom/pokemon/Trainer.java create mode 100755 src/com/pkrandom/pokemon/TrainerPokemon.java create mode 100755 src/com/pkrandom/pokemon/Type.java create mode 100644 src/com/pkrandom/pokemon/TypeRelationship.java create mode 100644 src/com/pkrandom/romhandlers/Abstract3DSRomHandler.java create mode 100755 src/com/pkrandom/romhandlers/AbstractDSRomHandler.java create mode 100644 src/com/pkrandom/romhandlers/AbstractGBCRomHandler.java create mode 100755 src/com/pkrandom/romhandlers/AbstractGBRomHandler.java create mode 100755 src/com/pkrandom/romhandlers/AbstractRomHandler.java create mode 100755 src/com/pkrandom/romhandlers/Gen1RomHandler.java create mode 100755 src/com/pkrandom/romhandlers/Gen2RomHandler.java create mode 100755 src/com/pkrandom/romhandlers/Gen3RomHandler.java create mode 100755 src/com/pkrandom/romhandlers/Gen4RomHandler.java create mode 100755 src/com/pkrandom/romhandlers/Gen5RomHandler.java create mode 100644 src/com/pkrandom/romhandlers/Gen6RomHandler.java create mode 100644 src/com/pkrandom/romhandlers/Gen7RomHandler.java create mode 100755 src/com/pkrandom/romhandlers/RomHandler.java (limited to 'src/com/pkrandom') diff --git a/src/com/pkrandom/CustomNamesSet.java b/src/com/pkrandom/CustomNamesSet.java new file mode 100644 index 0000000..0dac51d --- /dev/null +++ b/src/com/pkrandom/CustomNamesSet.java @@ -0,0 +1,237 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- CustomNamesSet.java - handles functionality related to custom names. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Scanner; + +public class CustomNamesSet { + + private List trainerNames; + private List trainerClasses; + private List doublesTrainerNames; + private List doublesTrainerClasses; + private List pokemonNicknames; + + private static final int CUSTOM_NAMES_VERSION = 1; + + // Standard constructor: read binary data from an input stream. + public CustomNamesSet(InputStream data) throws IOException { + + if (data.read() != CUSTOM_NAMES_VERSION) { + throw new IOException("Invalid custom names file provided."); + } + + trainerNames = readNamesBlock(data); + trainerClasses = readNamesBlock(data); + doublesTrainerNames = readNamesBlock(data); + doublesTrainerClasses = readNamesBlock(data); + pokemonNicknames = readNamesBlock(data); + } + + // Alternate constructor: blank all lists + // Used for importing old names and on the editor dialog. + public CustomNamesSet() { + trainerNames = new ArrayList<>(); + trainerClasses = new ArrayList<>(); + doublesTrainerNames = new ArrayList<>(); + doublesTrainerClasses = new ArrayList<>(); + pokemonNicknames = new ArrayList<>(); + } + + private List readNamesBlock(InputStream in) throws IOException { + // Read the size of the block to come. + byte[] szData = FileFunctions.readFullyIntoBuffer(in, 4); + int size = FileFunctions.readFullIntBigEndian(szData, 0); + if (in.available() < size) { + throw new IOException("Invalid size specified."); + } + + // Read the block and translate it into a list of names. + byte[] namesData = FileFunctions.readFullyIntoBuffer(in, size); + List names = new ArrayList<>(); + Scanner sc = new Scanner(new ByteArrayInputStream(namesData), "UTF-8"); + while (sc.hasNextLine()) { + String name = sc.nextLine().trim(); + if (!name.isEmpty()) { + names.add(name); + } + } + sc.close(); + + return names; + } + + public byte[] getBytes() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + baos.write(CUSTOM_NAMES_VERSION); + + writeNamesBlock(baos, trainerNames); + writeNamesBlock(baos, trainerClasses); + writeNamesBlock(baos, doublesTrainerNames); + writeNamesBlock(baos, doublesTrainerClasses); + writeNamesBlock(baos, pokemonNicknames); + + return baos.toByteArray(); + } + + private void writeNamesBlock(OutputStream out, List names) throws IOException { + String newln = SysConstants.LINE_SEP; + StringBuilder outNames = new StringBuilder(); + boolean first = true; + for (String name : names) { + if (!first) { + outNames.append(newln); + } + first = false; + outNames.append(name); + } + byte[] namesData = outNames.toString().getBytes("UTF-8"); + byte[] szData = new byte[4]; + FileFunctions.writeFullIntBigEndian(szData, 0, namesData.length); + out.write(szData); + out.write(namesData); + } + + public List getTrainerNames() { + return Collections.unmodifiableList(trainerNames); + } + + public List getTrainerClasses() { + return Collections.unmodifiableList(trainerClasses); + } + + public List getDoublesTrainerNames() { + return Collections.unmodifiableList(doublesTrainerNames); + } + + public List getDoublesTrainerClasses() { + return Collections.unmodifiableList(doublesTrainerClasses); + } + + public List getPokemonNicknames() { + return Collections.unmodifiableList(pokemonNicknames); + } + + public void setTrainerNames(List names) { + trainerNames.clear(); + trainerNames.addAll(names); + } + + public void setTrainerClasses(List names) { + trainerClasses.clear(); + trainerClasses.addAll(names); + } + + public void setDoublesTrainerNames(List names) { + doublesTrainerNames.clear(); + doublesTrainerNames.addAll(names); + } + + public void setDoublesTrainerClasses(List names) { + doublesTrainerClasses.clear(); + doublesTrainerClasses.addAll(names); + } + + public void setPokemonNicknames(List names) { + pokemonNicknames.clear(); + pokemonNicknames.addAll(names); + } + + public static CustomNamesSet importOldNames() throws FileNotFoundException { + CustomNamesSet cns = new CustomNamesSet(); + + // Trainer Names + if (FileFunctions.configExists(SysConstants.tnamesFile)) { + Scanner sc = new Scanner(FileFunctions.openConfig(SysConstants.tnamesFile), "UTF-8"); + while (sc.hasNextLine()) { + String trainername = sc.nextLine().trim(); + if (trainername.isEmpty()) { + continue; + } + if (trainername.startsWith("\uFEFF")) { + trainername = trainername.substring(1); + } + if (trainername.contains("&")) { + cns.doublesTrainerNames.add(trainername); + } else { + cns.trainerNames.add(trainername); + } + } + sc.close(); + } + + // Trainer Classes + if (FileFunctions.configExists(SysConstants.tclassesFile)) { + Scanner sc = new Scanner(FileFunctions.openConfig(SysConstants.tclassesFile), "UTF-8"); + while (sc.hasNextLine()) { + String trainerClassName = sc.nextLine().trim(); + if (trainerClassName.isEmpty()) { + continue; + } + if (trainerClassName.startsWith("\uFEFF")) { + trainerClassName = trainerClassName.substring(1); + } + String checkName = trainerClassName.toLowerCase(); + int idx = (checkName.endsWith("couple") || checkName.contains(" and ") || checkName.endsWith("kin") + || checkName.endsWith("team") || checkName.contains("&") || (checkName.endsWith("s") && !checkName + .endsWith("ss"))) ? 1 : 0; + if (idx == 1) { + cns.doublesTrainerClasses.add(trainerClassName); + } else { + cns.trainerClasses.add(trainerClassName); + } + } + sc.close(); + } + + // Nicknames + if (FileFunctions.configExists(SysConstants.nnamesFile)) { + Scanner sc = new Scanner(FileFunctions.openConfig(SysConstants.nnamesFile), "UTF-8"); + while (sc.hasNextLine()) { + String nickname = sc.nextLine().trim(); + if (nickname.isEmpty()) { + continue; + } + if (nickname.startsWith("\uFEFF")) { + nickname = nickname.substring(1); + } + cns.pokemonNicknames.add(nickname); + } + sc.close(); + } + + return cns; + } + +} diff --git a/src/com/pkrandom/FileFunctions.java b/src/com/pkrandom/FileFunctions.java new file mode 100755 index 0000000..83b2112 --- /dev/null +++ b/src/com/pkrandom/FileFunctions.java @@ -0,0 +1,352 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- FileFunctions.java - functions relating to file I/O. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.io.*; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.zip.CRC32; + +public class FileFunctions { + + public static File fixFilename(File original, String defaultExtension) { + return fixFilename(original, defaultExtension, new ArrayList<>()); + } + + // Behavior: + // if file has no extension, add defaultExtension + // if there are banned extensions & file has a banned extension, replace + // with defaultExtension + // else, leave as is + public static File fixFilename(File original, String defaultExtension, List bannedExtensions) { + String absolutePath = original.getAbsolutePath(); + for (String bannedExtension: bannedExtensions) { + if (absolutePath.endsWith("." + bannedExtension)) { + absolutePath = absolutePath.substring(0, absolutePath.lastIndexOf('.') + 1) + defaultExtension; + break; + } + } + if (!absolutePath.endsWith("." + defaultExtension)) { + absolutePath += "." + defaultExtension; + } + return new File(absolutePath); + } + + private static List overrideFiles = Arrays.asList(SysConstants.customNamesFile, + SysConstants.tclassesFile, SysConstants.tnamesFile, SysConstants.nnamesFile); + + public static boolean configExists(String filename) { + if (overrideFiles.contains(filename)) { + File fh = new File(SysConstants.ROOT_PATH + filename); + if (fh.exists() && fh.canRead()) { + return true; + } + fh = new File("./" + filename); + if (fh.exists() && fh.canRead()) { + return true; + } + } + return FileFunctions.class.getResource("/com/pkrandom/config/" + filename) != null; + } + + public static InputStream openConfig(String filename) throws FileNotFoundException { + if (overrideFiles.contains(filename)) { + File fh = new File(SysConstants.ROOT_PATH + filename); + if (fh.exists() && fh.canRead()) { + return new FileInputStream(fh); + } + fh = new File("./" + filename); + if (fh.exists() && fh.canRead()) { + return new FileInputStream(fh); + } + } + return FileFunctions.class.getResourceAsStream("/com/pkrandom/config/" + filename); + } + + public static CustomNamesSet getCustomNames() throws IOException { + InputStream is = openConfig(SysConstants.customNamesFile); + CustomNamesSet cns = new CustomNamesSet(is); + is.close(); + return cns; + } + + public static long readFullLong(byte[] data, int offset) { + ByteBuffer buf = ByteBuffer.allocate(8); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.put(data, offset, 8); + buf.rewind(); + return buf.getLong(); + } + + public static int readFullInt(byte[] data, int offset) { + ByteBuffer buf = ByteBuffer.allocate(4); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.put(data, offset, 4); + buf.rewind(); + return buf.getInt(); + } + + public static int readFullIntBigEndian(byte[] data, int offset) { + ByteBuffer buf = ByteBuffer.allocate(4).put(data, offset, 4); + buf.rewind(); + return buf.getInt(); + } + + public static int read2ByteIntBigEndian(byte[] data, int index) { + return (data[index + 1] & 0xFF) | ((data[index] & 0xFF) << 8); + } + + public static int read2ByteInt(byte[] data, int index) { + return (data[index] & 0xFF) | ((data[index + 1] & 0xFF) << 8); + } + + public static void write2ByteInt(byte[] data, int offset, int value) { + data[offset] = (byte) (value & 0xFF); + data[offset + 1] = (byte) ((value >> 8) & 0xFF); + } + + public static void writeFullInt(byte[] data, int offset, int value) { + byte[] valueBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array(); + System.arraycopy(valueBytes, 0, data, offset, 4); + } + + public static void writeFullIntBigEndian(byte[] data, int offset, int value) { + byte[] valueBytes = ByteBuffer.allocate(4).putInt(value).array(); + System.arraycopy(valueBytes, 0, data, offset, 4); + } + + public static void writeFullLong(byte[] data, int offset, long value) { + byte[] valueBytes = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array(); + System.arraycopy(valueBytes, 0, data, offset, 8); + } + + public static byte[] readFileFullyIntoBuffer(String filename) throws IOException { + File fh = new File(filename); + if (!fh.exists() || !fh.isFile() || !fh.canRead()) { + throw new FileNotFoundException(filename); + } + long fileSize = fh.length(); + if (fileSize > Integer.MAX_VALUE) { + throw new IOException(filename + " is too long to read in as a byte-array."); + } + FileInputStream fis = new FileInputStream(filename); + byte[] buf = readFullyIntoBuffer(fis, (int) fileSize); + fis.close(); + return buf; + } + + public static byte[] readFullyIntoBuffer(InputStream in, int bytes) throws IOException { + byte[] buf = new byte[bytes]; + readFully(in, buf, 0, bytes); + return buf; + } + + private static void readFully(InputStream in, byte[] buf, int offset, int length) throws IOException { + int offs = 0, read; + while (offs < length && (read = in.read(buf, offs + offset, length - offs)) != -1) { + offs += read; + } + } + + public static int read2ByteBigEndianIntFromFile(RandomAccessFile file, long offset) throws IOException { + byte[] buf = new byte[2]; + file.seek(offset); + file.readFully(buf); + return read2ByteIntBigEndian(buf, 0); + } + + public static int readBigEndianIntFromFile(RandomAccessFile file, long offset) throws IOException { + byte[] buf = new byte[4]; + file.seek(offset); + file.readFully(buf); + return readFullIntBigEndian(buf, 0); + } + + public static int readIntFromFile(RandomAccessFile file, long offset) throws IOException { + byte[] buf = new byte[4]; + file.seek(offset); + file.readFully(buf); + return readFullInt(buf, 0); + } + + public static void writeBytesToFile(String filename, byte[] data) throws IOException { + FileOutputStream fos = new FileOutputStream(filename); + fos.write(data); + fos.close(); + } + + public static byte[] getConfigAsBytes(String filename) throws IOException { + InputStream in = openConfig(filename); + byte[] buf = readFullyIntoBuffer(in, in.available()); + in.close(); + return buf; + } + + public static int getFileChecksum(String filename) { + try { + return getFileChecksum(openConfig(filename)); + } catch (IOException e) { + return 0; + } + } + + private static int getFileChecksum(InputStream stream) { + try { + Scanner sc = new Scanner(stream, "UTF-8"); + CRC32 checksum = new CRC32(); + while (sc.hasNextLine()) { + String line = sc.nextLine().trim(); + if (!line.isEmpty()) { + checksum.update(line.getBytes("UTF-8")); + } + } + sc.close(); + return (int) checksum.getValue(); + } catch (IOException e) { + return 0; + } + } + + public static boolean checkOtherCRC(byte[] data, int byteIndex, int switchIndex, String filename, int offsetInData) { + // If the switch at data[byteIndex].switchIndex is on, then check that + // the CRC at data[offsetInData] ... data[offsetInData+3] matches the + // CRC of filename. + // If not, return false. + // If any other case, return true. + int switches = data[byteIndex] & 0xFF; + if (((switches >> switchIndex) & 0x01) == 0x01) { + // have to check the CRC + int crc = readFullIntBigEndian(data, offsetInData); + + return getFileChecksum(filename) == crc; + } + return true; + } + + public static long getCRC32(byte[] data) { + CRC32 checksum = new CRC32(); + checksum.update(data); + return checksum.getValue(); + } + + private static byte[] getCodeTweakFile(String filename) throws IOException { + InputStream is = FileFunctions.class.getResourceAsStream("/com/pkrandom/patches/" + filename); + byte[] buf = readFullyIntoBuffer(is, is.available()); + is.close(); + return buf; + } + + public static byte[] downloadFile(String url) throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[1024]; + int count; + while ((count = in.read(buf, 0, 1024)) != -1) { + out.write(buf, 0, count); + } + in.close(); + return out.toByteArray(); + } + + public static void applyPatch(byte[] rom, String patchName) throws IOException { + byte[] patch = getCodeTweakFile(patchName + ".ips"); + + // check sig + int patchlen = patch.length; + if (patchlen < 8 || patch[0] != 'P' || patch[1] != 'A' || patch[2] != 'T' || patch[3] != 'C' || patch[4] != 'H') { + throw new IOException("not a valid IPS file"); + } + + // records + int offset = 5; + while (offset + 2 < patchlen) { + int writeOffset = readIPSOffset(patch, offset); + if (writeOffset == 0x454f46) { + // eof, done + return; + } + offset += 3; + if (offset + 1 >= patchlen) { + // error + throw new IOException("abrupt ending to IPS file, entry cut off before size"); + } + int size = readIPSSize(patch, offset); + offset += 2; + if (size == 0) { + // RLE + if (offset + 1 >= patchlen) { + // error + throw new IOException("abrupt ending to IPS file, entry cut off before RLE size"); + } + int rleSize = readIPSSize(patch, offset); + if (writeOffset + rleSize > rom.length) { + // error + throw new IOException("trying to patch data past the end of the ROM file"); + } + offset += 2; + if (offset >= patchlen) { + // error + throw new IOException("abrupt ending to IPS file, entry cut off before RLE byte"); + } + byte rleByte = patch[offset++]; + for (int i = writeOffset; i < writeOffset + rleSize; i++) { + rom[i] = rleByte; + } + } else { + if (offset + size > patchlen) { + // error + throw new IOException("abrupt ending to IPS file, entry cut off before end of data block"); + } + if (writeOffset + size > rom.length) { + // error + throw new IOException("trying to patch data past the end of the ROM file"); + } + System.arraycopy(patch, offset, rom, writeOffset, size); + offset += size; + } + } + throw new IOException("improperly terminated IPS file"); + } + + private static int readIPSOffset(byte[] data, int offset) { + return ((data[offset] & 0xFF) << 16) | ((data[offset + 1] & 0xFF) << 8) | (data[offset + 2] & 0xFF); + } + + private static int readIPSSize(byte[] data, int offset) { + return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF); + } + + public static byte[] convIntArrToByteArr(int[] arg) { + byte[] out = new byte[arg.length]; + for (int i = 0; i < arg.length; i++) { + out[i] = (byte) arg[i]; + } + return out; + } +} \ No newline at end of file diff --git a/src/com/pkrandom/GFXFunctions.java b/src/com/pkrandom/GFXFunctions.java new file mode 100644 index 0000000..e5d5426 --- /dev/null +++ b/src/com/pkrandom/GFXFunctions.java @@ -0,0 +1,173 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- GFXFunctions.java - functions relating to graphics rendering. --*/ +/*-- Mainly used for rendering the sprites. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- Contains code based on "pokemon-x-y-icons", copyright (C) CatTrinket --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.awt.image.BufferedImage; +import java.util.LinkedList; +import java.util.Queue; + +public class GFXFunctions { + + public static BufferedImage drawTiledImage(byte[] data, int[] palette, int width, int height, int bpp) { + return drawTiledImage(data, palette, 0, width, height, 8, 8, bpp); + } + + public static BufferedImage drawTiledImage(byte[] data, int[] palette, int offset, int width, int height, int bpp) { + return drawTiledImage(data, palette, offset, width, height, 8, 8, bpp); + } + + private static BufferedImage drawTiledImage(byte[] data, int[] palette, int offset, int width, int height, + int tileWidth, int tileHeight, int bpp) { + if (bpp != 1 && bpp != 2 && bpp != 4 && bpp != 8) { + throw new IllegalArgumentException("Bits per pixel must be a multiple of 2."); + } + int pixelsPerByte = 8 / bpp; + if (width * height / pixelsPerByte + offset > data.length) { + throw new IllegalArgumentException("Invalid input image."); + } + + int bytesPerTile = tileWidth * tileHeight / pixelsPerByte; + int numTiles = width * height / (tileWidth * tileHeight); + int widthInTiles = width / tileWidth; + + BufferedImage bim = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + for (int tile = 0; tile < numTiles; tile++) { + int tileX = tile % widthInTiles; + int tileY = tile / widthInTiles; + for (int yT = 0; yT < tileHeight; yT++) { + for (int xT = 0; xT < tileWidth; xT++) { + int value = data[tile * bytesPerTile + yT * tileWidth / pixelsPerByte + xT / pixelsPerByte + offset] & 0xFF; + if (pixelsPerByte != 1) { + value = (value >>> (xT % pixelsPerByte) * bpp) & ((1 << bpp) - 1); + } + bim.setRGB(tileX * tileWidth + xT, tileY * tileHeight + yT, palette[value]); + } + } + } + + return bim; + } + + public static BufferedImage drawTiledZOrderImage(byte[] data, int[] palette, int offset, int width, int height, int bpp) { + return drawTiledZOrderImage(data, palette, offset, width, height, 8, 8, bpp); + } + + private static BufferedImage drawTiledZOrderImage(byte[] data, int[] palette, int offset, int width, int height, + int tileWidth, int tileHeight, int bpp) { + if (bpp != 1 && bpp != 2 && bpp != 4 && bpp != 8) { + throw new IllegalArgumentException("Bits per pixel must be a multiple of 2."); + } + int pixelsPerByte = 8 / bpp; + if (width * height / pixelsPerByte + offset > data.length) { + throw new IllegalArgumentException("Invalid input image."); + } + + int bytesPerTile = tileWidth * tileHeight / pixelsPerByte; + int numTiles = width * height / (tileWidth * tileHeight); + int widthInTiles = width / tileWidth; + + BufferedImage bim = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + for (int tile = 0; tile < numTiles; tile++) { + int tileX = tile % widthInTiles; + int tileY = tile / widthInTiles; + for (int yT = 0; yT < tileHeight; yT++) { + for (int xT = 0; xT < tileWidth; xT++) { + int value = data[tile * bytesPerTile + yT * tileWidth / pixelsPerByte + xT / pixelsPerByte + offset] & 0xFF; + if (pixelsPerByte != 1) { + value = (value >>> ((xT+1) % pixelsPerByte) * bpp) & ((1 << bpp) - 1); + } + + int withinTile = yT * tileWidth + xT; + int subX = (withinTile & 0b000001) | + (withinTile & 0b000100) >>> 1 | + (withinTile & 0b010000) >>> 2; + int subY = (withinTile & 0b000010) >>> 1 | + (withinTile & 0b001000) >>> 2 | + (withinTile & 0b100000) >>> 3; + bim.setRGB(tileX * tileWidth + subX, tileY * tileHeight + subY, palette[value]); + } + } + } + + return bim; + } + + public static int conv16BitColorToARGB(int palValue) { + int red = (int) ((palValue & 0x1F) * 8.25); + int green = (int) (((palValue & 0x3E0) >> 5) * 8.25); + int blue = (int) (((palValue & 0x7C00) >> 10) * 8.25); + return 0xFF000000 | (red << 16) | (green << 8) | blue; + } + + public static int conv3DS16BitColorToARGB(int palValue) { + int alpha = (int) ((palValue & 0x1) * 0xFF); + int blue = (int) (((palValue & 0x3E) >> 1) * 8.25); + int green = (int) (((palValue & 0x7C0) >> 6) * 8.25); + int red = (int) (((palValue & 0xF800) >> 11) * 8.25); + return (alpha << 24) | (red << 16) | (green << 8) | blue; + } + + public static void pseudoTransparency(BufferedImage img, int transColor) { + int width = img.getWidth(); + int height = img.getHeight(); + Queue visitPixels = new LinkedList<>(); + boolean[][] queued = new boolean[width][height]; + + for (int x = 0; x < width; x++) { + queuePixel(x, 0, width, height, visitPixels, queued); + queuePixel(x, height - 1, width, height, visitPixels, queued); + } + + for (int y = 0; y < height; y++) { + queuePixel(0, y, width, height, visitPixels, queued); + queuePixel(width - 1, y, width, height, visitPixels, queued); + } + + while (!visitPixels.isEmpty()) { + int nextPixel = visitPixels.poll(); + int x = nextPixel % width; + int y = nextPixel / width; + if (img.getRGB(x, y) == transColor) { + img.setRGB(x, y, 0); + queuePixel(x - 1, y, width, height, visitPixels, queued); + queuePixel(x + 1, y, width, height, visitPixels, queued); + queuePixel(x, y - 1, width, height, visitPixels, queued); + queuePixel(x, y + 1, width, height, visitPixels, queued); + } + } + } + + private static void queuePixel(int x, int y, int width, int height, Queue queue, boolean[][] queued) { + if (x >= 0 && x < width && y >= 0 && y < height && !queued[x][y]) { + queue.add((y) * width + (x)); + queued[x][y] = true; + } + } + +} diff --git a/src/com/pkrandom/MiscTweak.java b/src/com/pkrandom/MiscTweak.java new file mode 100755 index 0000000..5640a13 --- /dev/null +++ b/src/com/pkrandom/MiscTweak.java @@ -0,0 +1,98 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- MiscTweak.java - represents a miscellaneous tweak that can be applied --*/ +/*-- to some or all games that the randomizer supports. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; + +public class MiscTweak implements Comparable { + + public static final int NO_MISC_TWEAKS = 0; + + private static final ResourceBundle bundle = ResourceBundle.getBundle("com/pkrandom/newgui/Bundle"); + + public static List allTweaks = new ArrayList<>(); + + /* @formatter:off */ + // Higher priority value (third argument) = run first + public static final MiscTweak BW_EXP_PATCH = new MiscTweak(1, "bwPatch", 0); + public static final MiscTweak NERF_X_ACCURACY = new MiscTweak(1 << 1, "nerfXAcc", 0); + public static final MiscTweak FIX_CRIT_RATE = new MiscTweak(1 << 2, "critRateFix", 0); + public static final MiscTweak FASTEST_TEXT = new MiscTweak(1 << 3, "fastestText", 0); + public static final MiscTweak RUNNING_SHOES_INDOORS = new MiscTweak(1 << 4, "runningShoes", 0); + public static final MiscTweak RANDOMIZE_PC_POTION = new MiscTweak(1 << 5, "pcPotion", 0); + public static final MiscTweak ALLOW_PIKACHU_EVOLUTION = new MiscTweak(1 << 6, "pikachuEvo", 0); + public static final MiscTweak NATIONAL_DEX_AT_START = new MiscTweak(1 << 7, "nationalDex", 0); + public static final MiscTweak UPDATE_TYPE_EFFECTIVENESS = new MiscTweak(1 << 8, "typeEffectiveness", 0); + public static final MiscTweak FORCE_CHALLENGE_MODE = new MiscTweak(1 << 9, "forceChallengeMode", 0); + public static final MiscTweak LOWER_CASE_POKEMON_NAMES = new MiscTweak(1 << 10, "lowerCaseNames", 0); + public static final MiscTweak RANDOMIZE_CATCHING_TUTORIAL = new MiscTweak(1 << 11, "catchingTutorial", 0); + public static final MiscTweak BAN_LUCKY_EGG = new MiscTweak(1 << 12, "luckyEgg", 1); + public static final MiscTweak NO_FREE_LUCKY_EGG = new MiscTweak(1 << 13,"freeLuckyEgg",0); + public static final MiscTweak BAN_BIG_MANIAC_ITEMS = new MiscTweak(1 << 14, "maniacItems",1); + public static final MiscTweak SOS_BATTLES_FOR_ALL = new MiscTweak(1 << 15, "sosBattles",0); + public static final MiscTweak BALANCE_STATIC_LEVELS = new MiscTweak(1 << 16, "balanceStaticLevels",0); + public static final MiscTweak RETAIN_ALT_FORMES = new MiscTweak(1 << 17, "retainAltFormes",0); + public static final MiscTweak RUN_WITHOUT_RUNNING_SHOES = new MiscTweak(1 << 18, "runWithoutRunningShoes", 0); + public static final MiscTweak FASTER_HP_AND_EXP_BARS = new MiscTweak(1 << 19, "fasterHpAndExpBars", 0); + public static final MiscTweak FAST_DISTORTION_WORLD = new MiscTweak(1 << 20, "fastDistortionWorld", 0); + public static final MiscTweak UPDATE_ROTOM_FORME_TYPING = new MiscTweak(1 << 21, "updateRotomFormeTyping", 0); + public static final MiscTweak DISABLE_LOW_HP_MUSIC = new MiscTweak(1 << 22, "disableLowHpMusic", 0); + /* @formatter:on */ + + private final int value; + private final String tweakName; + private final String tooltipText; + private final int priority; + + private MiscTweak(int value, String tweakID, int priority) { + this.value = value; + this.tweakName = bundle.getString("CodeTweaks." + tweakID + ".name"); // this feels really dumb + this.tooltipText = bundle.getString("CodeTweaks." + tweakID + ".toolTipText"); + this.priority = priority; + allTweaks.add(this); + } + + public int getValue() { + return value; + } + + public String getTweakName() { + return tweakName; + } + + public String getTooltipText() { + return tooltipText; + } + + @Override + public int compareTo(MiscTweak o) { + // Order according to reverse priority, so higher priority = earlier in + // ordering + return o.priority - priority; + } + +} diff --git a/src/com/pkrandom/RandomSource.java b/src/com/pkrandom/RandomSource.java new file mode 100755 index 0000000..2ebbcde --- /dev/null +++ b/src/com/pkrandom/RandomSource.java @@ -0,0 +1,233 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- RandomSource.java - functions as a centralized source of randomness --*/ +/*-- to allow the same seed to produce the same random --*/ +/*-- ROM consistently. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.security.SecureRandom; +import java.util.Random; + +public class RandomSource { + + private static Random source = new Random(); + private static Random cosmeticSource = new Random(); + private static int calls = 0; + private static int cosmeticCalls = 0; + private static Random instance = new RandomSourceInstance(); + private static Random cosmeticInstance = new CosmeticRandomSourceInstance(); + + public static void reset() { + source = new Random(); + cosmeticSource = new Random(); + calls = 0; + cosmeticCalls = 0; + } + + public static void seed(long seed) { + source.setSeed(seed); + cosmeticSource.setSeed(seed); + calls = 0; + cosmeticCalls = 0; + } + + public static double random() { + calls++; + return source.nextDouble(); + } + + public static int nextInt(int size) { + calls++; + return source.nextInt(size); + } + + public static int nextIntCosmetic(int size) { + cosmeticCalls++; + return cosmeticSource.nextInt(size); + } + + public static void nextBytes(byte[] bytes) { + calls++; + source.nextBytes(bytes); + } + + public static int nextInt() { + calls++; + return source.nextInt(); + } + + public static long nextLong() { + calls++; + return source.nextLong(); + } + + public static boolean nextBoolean() { + calls++; + return source.nextBoolean(); + } + + public static float nextFloat() { + calls++; + return source.nextFloat(); + } + + public static double nextDouble() { + calls++; + return source.nextDouble(); + } + + public static synchronized double nextGaussian() { + calls++; + return source.nextGaussian(); + } + + public static long pickSeed() { + long value = 0; + byte[] by = SecureRandom.getSeed(6); + for (int i = 0; i < by.length; i++) { + value |= ((long) by[i] & 0xffL) << (8 * i); + } + return value; + } + + public static Random instance() { + return instance; + } + + public static Random cosmeticInstance() { + return cosmeticInstance; + } + + public static int callsSinceSeed() { + return calls + cosmeticCalls; + } + + private static class RandomSourceInstance extends Random { + + /** + * + */ + private static final long serialVersionUID = -4876737183441746322L; + + @Override + public synchronized void setSeed(long seed) { + RandomSource.seed(seed); + } + + @Override + public void nextBytes(byte[] bytes) { + RandomSource.nextBytes(bytes); + } + + @Override + public int nextInt() { + return RandomSource.nextInt(); + } + + @Override + public int nextInt(int n) { + return RandomSource.nextInt(n); + } + + @Override + public long nextLong() { + return RandomSource.nextLong(); + } + + @Override + public boolean nextBoolean() { + return RandomSource.nextBoolean(); + } + + @Override + public float nextFloat() { + return RandomSource.nextFloat(); + } + + @Override + public double nextDouble() { + return RandomSource.nextDouble(); + } + + @Override + public synchronized double nextGaussian() { + return RandomSource.nextGaussian(); + } + + } + + private static class CosmeticRandomSourceInstance extends Random { + + @Override + public synchronized void setSeed(long seed) { + RandomSource.seed(seed); + } + + @Override + @Deprecated + public void nextBytes(byte[] bytes) { + + } + + @Override + @Deprecated + public int nextInt() { + return 0; + } + + @Override + public int nextInt(int n) { + return RandomSource.nextIntCosmetic(n); + } + + @Override + @Deprecated + public long nextLong() { + return 0; + } + + @Override + @Deprecated + public boolean nextBoolean() { + return false; + } + + @Override + @Deprecated + public float nextFloat() { + return 0; + } + + @Override + @Deprecated + public double nextDouble() { + return 0; + } + + @Override + @Deprecated + public synchronized double nextGaussian() { + return 0; + } + } +} diff --git a/src/com/pkrandom/Randomizer.java b/src/com/pkrandom/Randomizer.java new file mode 100644 index 0000000..ac7f932 --- /dev/null +++ b/src/com/pkrandom/Randomizer.java @@ -0,0 +1,1341 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- Randomizer.java - Can randomize a file based on settings. --*/ +/*-- Output varies by seed. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.*; + +import com.pkrandom.pokemon.*; +import com.pkrandom.romhandlers.Gen1RomHandler; +import com.pkrandom.romhandlers.RomHandler; + +// Can randomize a file based on settings. Output varies by seed. +public class Randomizer { + + private static final String NEWLINE = System.getProperty("line.separator"); + + private final Settings settings; + private final RomHandler romHandler; + private final ResourceBundle bundle; + private final boolean saveAsDirectory; + + public Randomizer(Settings settings, RomHandler romHandler, ResourceBundle bundle, boolean saveAsDirectory) { + this.settings = settings; + this.romHandler = romHandler; + this.bundle = bundle; + this.saveAsDirectory = saveAsDirectory; + } + + public int randomize(final String filename) { + return randomize(filename, new PrintStream(new OutputStream() { + @Override + public void write(int b) { + } + })); + } + + public int randomize(final String filename, final PrintStream log) { + long seed = RandomSource.pickSeed(); + // long seed = 123456789; // TESTING + return randomize(filename, log, seed); + } + + public int randomize(final String filename, final PrintStream log, long seed) { + + final long startTime = System.currentTimeMillis(); + RandomSource.seed(seed); + + int checkValue = 0; + + log.println("Randomizer Version: " + Version.VERSION_STRING); + log.println("Random Seed: " + seed); + log.println("Settings String: " + Version.VERSION + settings.toString()); + log.println(); + + // All possible changes that can be logged + boolean movesUpdated = false; + boolean movesChanged = false; + boolean movesetsChanged = false; + boolean pokemonTraitsChanged = false; + boolean startersChanged = false; + boolean evolutionsChanged = false; + boolean trainersChanged = false; + boolean trainerMovesetsChanged = false; + boolean staticsChanged = false; + boolean totemsChanged = false; + boolean wildsChanged = false; + boolean tmMovesChanged = false; + boolean moveTutorMovesChanged = false; + boolean tradesChanged = false; + boolean tmsHmsCompatChanged = false; + boolean tutorCompatChanged = false; + boolean shopsChanged = false; + + // Limit Pokemon + // 1. Set Pokemon pool according to limits (or lack thereof) + // 2. If limited, remove evolutions that are outside of the pool + + romHandler.setPokemonPool(settings); + + if (settings.isLimitPokemon()) { + romHandler.removeEvosForPokemonPool(); + } + + // Move updates & data changes + // 1. Update moves to a future generation + // 2. Randomize move stats + + if (settings.isUpdateMoves()) { + romHandler.initMoveUpdates(); + romHandler.updateMoves(settings); + movesUpdated = true; + } + + if (movesUpdated) { + logMoveUpdates(log); + } + + if (settings.isRandomizeMovePowers()) { + romHandler.randomizeMovePowers(); + movesChanged = true; + } + + if (settings.isRandomizeMoveAccuracies()) { + romHandler.randomizeMoveAccuracies(); + movesChanged = true; + } + + if (settings.isRandomizeMovePPs()) { + romHandler.randomizeMovePPs(); + movesChanged = true; + } + + if (settings.isRandomizeMoveTypes()) { + romHandler.randomizeMoveTypes(); + movesChanged = true; + } + + if (settings.isRandomizeMoveCategory() && romHandler.hasPhysicalSpecialSplit()) { + romHandler.randomizeMoveCategory(); + movesChanged = true; + } + + // Misc Tweaks + if (settings.getCurrentMiscTweaks() != MiscTweak.NO_MISC_TWEAKS) { + romHandler.applyMiscTweaks(settings); + } + + // Update base stats to a future generation + if (settings.isUpdateBaseStats()) { + romHandler.updatePokemonStats(settings); + pokemonTraitsChanged = true; + } + + // Standardize EXP curves + if (settings.isStandardizeEXPCurves()) { + romHandler.standardizeEXPCurves(settings); + } + + // Pokemon Types + if (settings.getTypesMod() != Settings.TypesMod.UNCHANGED) { + romHandler.randomizePokemonTypes(settings); + pokemonTraitsChanged = true; + } + + // Wild Held Items + if (settings.isRandomizeWildPokemonHeldItems()) { + romHandler.randomizeWildHeldItems(settings); + pokemonTraitsChanged = true; + } + + // Random Evos + // Applied after type to pick new evos based on new types. + + if (settings.getEvolutionsMod() == Settings.EvolutionsMod.RANDOM) { + romHandler.randomizeEvolutions(settings); + evolutionsChanged = true; + } else if (settings.getEvolutionsMod() == Settings.EvolutionsMod.RANDOM_EVERY_LEVEL) { + romHandler.randomizeEvolutionsEveryLevel(settings); + evolutionsChanged = true; + } + + if (evolutionsChanged) { + logEvolutionChanges(log); + } + + // Base stat randomization + switch (settings.getBaseStatisticsMod()) { + case SHUFFLE: + romHandler.shufflePokemonStats(settings); + pokemonTraitsChanged = true; + break; + case RANDOM: + romHandler.randomizePokemonStats(settings); + pokemonTraitsChanged = true; + break; + default: + break; + } + + // Abilities + if (settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE) { + romHandler.randomizeAbilities(settings); + pokemonTraitsChanged = true; + } + + // Log Pokemon traits (stats, abilities, etc) if any have changed + if (pokemonTraitsChanged) { + logPokemonTraitChanges(log); + } else { + log.println("Pokemon base stats & type: unchanged" + NEWLINE); + } + + for (Pokemon pkmn : romHandler.getPokemon()) { + if (pkmn != null) { + checkValue = addToCV(checkValue, pkmn.hp, pkmn.attack, pkmn.defense, pkmn.speed, pkmn.spatk, + pkmn.spdef, pkmn.ability1, pkmn.ability2, pkmn.ability3); + } + } + + // Trade evolutions removal + if (settings.isChangeImpossibleEvolutions()) { + romHandler.removeImpossibleEvolutions(settings); + } + + // Easier evolutions + if (settings.isMakeEvolutionsEasier()) { + romHandler.condenseLevelEvolutions(40, 30); + romHandler.makeEvolutionsEasier(settings); + } + + // Remove time-based evolutions + if (settings.isRemoveTimeBasedEvolutions()) { + romHandler.removeTimeBasedEvolutions(); + } + + // Log everything afterwards, so that "impossible evolutions" can account for "easier evolutions" + if (settings.isChangeImpossibleEvolutions()) { + log.println("--Removing Impossible Evolutions--"); + logUpdatedEvolutions(log, romHandler.getImpossibleEvoUpdates(), romHandler.getEasierEvoUpdates()); + } + + if (settings.isMakeEvolutionsEasier()) { + log.println("--Making Evolutions Easier--"); + if (!(romHandler instanceof Gen1RomHandler)) { + log.println("Friendship evolutions now take 160 happiness (was 220)."); + } + logUpdatedEvolutions(log, romHandler.getEasierEvoUpdates(), null); + } + + if (settings.isRemoveTimeBasedEvolutions()) { + log.println("--Removing Timed-Based Evolutions--"); + logUpdatedEvolutions(log, romHandler.getTimeBasedEvoUpdates(), null); + } + + // Starter Pokemon + // Applied after type to update the strings correctly based on new types + switch(settings.getStartersMod()) { + case CUSTOM: + romHandler.customStarters(settings); + startersChanged = true; + break; + case COMPLETELY_RANDOM: + romHandler.randomizeStarters(settings); + startersChanged = true; + break; + case RANDOM_WITH_TWO_EVOLUTIONS: + romHandler.randomizeBasicTwoEvosStarters(settings); + startersChanged = true; + break; + default: + break; + } + if (settings.isRandomizeStartersHeldItems() && !(romHandler instanceof Gen1RomHandler)) { + romHandler.randomizeStarterHeldItems(settings); + } + + if (startersChanged) { + logStarters(log); + } + + // Move Data Log + // Placed here so it matches its position in the randomizer interface + if (movesChanged) { + logMoveChanges(log); + } else if (!movesUpdated) { + log.println("Move Data: Unchanged." + NEWLINE); + } + + // Movesets + // 1. Randomize movesets + // 2. Reorder moves by damage + // Note: "Metronome only" is handled after trainers instead + + if (settings.getMovesetsMod() != Settings.MovesetsMod.UNCHANGED && + settings.getMovesetsMod() != Settings.MovesetsMod.METRONOME_ONLY) { + romHandler.randomizeMovesLearnt(settings); + romHandler.randomizeEggMoves(settings); + movesetsChanged = true; + } + + if (settings.isReorderDamagingMoves()) { + romHandler.orderDamagingMovesByDamage(); + movesetsChanged = true; + } + + // Show the new movesets if applicable + if (movesetsChanged) { + logMovesetChanges(log); + } else if (settings.getMovesetsMod() == Settings.MovesetsMod.METRONOME_ONLY) { + log.println("Pokemon Movesets: Metronome Only." + NEWLINE); + } else { + log.println("Pokemon Movesets: Unchanged." + NEWLINE); + } + + // TMs + + if (!(settings.getMovesetsMod() == Settings.MovesetsMod.METRONOME_ONLY) + && settings.getTmsMod() == Settings.TMsMod.RANDOM) { + romHandler.randomizeTMMoves(settings); + tmMovesChanged = true; + } + + if (tmMovesChanged) { + checkValue = logTMMoves(log, checkValue); + } else if (settings.getMovesetsMod() == Settings.MovesetsMod.METRONOME_ONLY) { + log.println("TM Moves: Metronome Only." + NEWLINE); + } else { + log.println("TM Moves: Unchanged." + NEWLINE); + } + + // TM/HM compatibility + // 1. Randomize TM/HM compatibility + // 2. Ensure levelup move sanity + // 3. Follow evolutions + // 4. Full HM compatibility + // 5. Copy to cosmetic forms + + switch (settings.getTmsHmsCompatibilityMod()) { + case COMPLETELY_RANDOM: + case RANDOM_PREFER_TYPE: + romHandler.randomizeTMHMCompatibility(settings); + tmsHmsCompatChanged = true; + break; + case FULL: + romHandler.fullTMHMCompatibility(); + tmsHmsCompatChanged = true; + break; + default: + break; + } + + if (settings.isTmLevelUpMoveSanity()) { + romHandler.ensureTMCompatSanity(); + if (settings.isTmsFollowEvolutions()) { + romHandler.ensureTMEvolutionSanity(); + } + tmsHmsCompatChanged = true; + } + + if (settings.isFullHMCompat()) { + romHandler.fullHMCompatibility(); + tmsHmsCompatChanged = true; + } + + // Copy TM/HM compatibility to cosmetic formes if it was changed at all, and log changes + if (tmsHmsCompatChanged) { + romHandler.copyTMCompatibilityToCosmeticFormes(); + logTMHMCompatibility(log); + } + + // Move Tutors + if (romHandler.hasMoveTutors()) { + + List oldMtMoves = romHandler.getMoveTutorMoves(); + + if (!(settings.getMovesetsMod() == Settings.MovesetsMod.METRONOME_ONLY) + && settings.getMoveTutorMovesMod() == Settings.MoveTutorMovesMod.RANDOM) { + + romHandler.randomizeMoveTutorMoves(settings); + moveTutorMovesChanged = true; + } + + if (moveTutorMovesChanged) { + checkValue = logMoveTutorMoves(log, checkValue, oldMtMoves); + } else if (settings.getMovesetsMod() == Settings.MovesetsMod.METRONOME_ONLY) { + log.println("Move Tutor Moves: Metronome Only." + NEWLINE); + } else { + log.println("Move Tutor Moves: Unchanged." + NEWLINE); + } + + // Move Tutor Compatibility + // 1. Randomize MT compatibility + // 2. Ensure levelup move sanity + // 3. Follow evolutions + // 4. Copy to cosmetic forms + + switch (settings.getMoveTutorsCompatibilityMod()) { + case COMPLETELY_RANDOM: + case RANDOM_PREFER_TYPE: + romHandler.randomizeMoveTutorCompatibility(settings); + tutorCompatChanged = true; + break; + case FULL: + romHandler.fullMoveTutorCompatibility(); + tutorCompatChanged = true; + break; + default: + break; + } + + if (settings.isTutorLevelUpMoveSanity()) { + romHandler.ensureMoveTutorCompatSanity(); + if (settings.isTutorFollowEvolutions()) { + romHandler.ensureMoveTutorEvolutionSanity(); + } + tutorCompatChanged = true; + } + + // Copy move tutor compatibility to cosmetic formes if it was changed at all + if (tutorCompatChanged) { + romHandler.copyMoveTutorCompatibilityToCosmeticFormes(); + logTutorCompatibility(log); + } + + } + + // Trainer Pokemon + // 1. Add extra Trainer Pokemon + // 2. Set trainers to be double battles and add extra Pokemon if necessary + // 3. Randomize Trainer Pokemon + // 4. Modify rivals to carry starters + // 5. Force Trainer Pokemon to be fully evolved + + if (settings.getAdditionalRegularTrainerPokemon() > 0 + || settings.getAdditionalImportantTrainerPokemon() > 0 + || settings.getAdditionalBossTrainerPokemon() > 0) { + romHandler.addTrainerPokemon(settings); + trainersChanged = true; + } + + + if (settings.isDoubleBattleMode()) { + romHandler.doubleBattleMode(); + trainersChanged = true; + } + + switch(settings.getTrainersMod()) { + case RANDOM: + case DISTRIBUTED: + case MAINPLAYTHROUGH: + case TYPE_THEMED: + case TYPE_THEMED_ELITE4_GYMS: + romHandler.randomizeTrainerPokes(settings); + trainersChanged = true; + break; + default: + if (settings.isTrainersLevelModified()) { + romHandler.onlyChangeTrainerLevels(settings); + trainersChanged = true; + } + break; + } + + if ((settings.getTrainersMod() != Settings.TrainersMod.UNCHANGED + || settings.getStartersMod() != Settings.StartersMod.UNCHANGED) + && settings.isRivalCarriesStarterThroughout()) { + romHandler.rivalCarriesStarter(); + trainersChanged = true; + } + + if (settings.isTrainersForceFullyEvolved()) { + romHandler.forceFullyEvolvedTrainerPokes(settings); + trainersChanged = true; + } + + if (settings.isBetterTrainerMovesets()) { + romHandler.pickTrainerMovesets(settings); + trainersChanged = true; + trainerMovesetsChanged = true; + } + + if (settings.isRandomizeHeldItemsForBossTrainerPokemon() + || settings.isRandomizeHeldItemsForImportantTrainerPokemon() + || settings.isRandomizeHeldItemsForRegularTrainerPokemon()) { + romHandler.randomizeTrainerHeldItems(settings); + trainersChanged = true; + } + + List originalTrainerNames = getTrainerNames(); + boolean trainerNamesChanged = false; + + // Trainer names & class names randomization + if (romHandler.canChangeTrainerText()) { + if (settings.isRandomizeTrainerClassNames()) { + romHandler.randomizeTrainerClassNames(settings); + trainersChanged = true; + trainerNamesChanged = true; + } + + if (settings.isRandomizeTrainerNames()) { + romHandler.randomizeTrainerNames(settings); + trainersChanged = true; + trainerNamesChanged = true; + } + } + + if (trainersChanged) { + maybeLogTrainerChanges(log, originalTrainerNames, trainerNamesChanged, trainerMovesetsChanged); + } else { + log.println("Trainers: Unchanged." + NEWLINE); + } + + // Apply metronome only mode now that trainers have been dealt with + if (settings.getMovesetsMod() == Settings.MovesetsMod.METRONOME_ONLY) { + romHandler.metronomeOnlyMode(); + } + + List trainers = romHandler.getTrainers(); + for (Trainer t : trainers) { + for (TrainerPokemon tpk : t.pokemon) { + checkValue = addToCV(checkValue, tpk.level, tpk.pokemon.number); + } + } + + // Static Pokemon + if (romHandler.canChangeStaticPokemon()) { + List oldStatics = romHandler.getStaticPokemon(); + if (settings.getStaticPokemonMod() != Settings.StaticPokemonMod.UNCHANGED) { // Legendary for L + romHandler.randomizeStaticPokemon(settings); + staticsChanged = true; + } else if (settings.isStaticLevelModified()) { + romHandler.onlyChangeStaticLevels(settings); + staticsChanged = true; + } + + if (staticsChanged) { + checkValue = logStaticPokemon(log, checkValue, oldStatics); + } else { + log.println("Static Pokemon: Unchanged." + NEWLINE); + } + } + + // Totem Pokemon + if (romHandler.generationOfPokemon() == 7) { + List oldTotems = romHandler.getTotemPokemon(); + if (settings.getTotemPokemonMod() != Settings.TotemPokemonMod.UNCHANGED || + settings.getAllyPokemonMod() != Settings.AllyPokemonMod.UNCHANGED || + settings.getAuraMod() != Settings.AuraMod.UNCHANGED || + settings.isRandomizeTotemHeldItems() || + settings.isTotemLevelsModified()) { + + romHandler.randomizeTotemPokemon(settings); + totemsChanged = true; + } + + if (totemsChanged) { + checkValue = logTotemPokemon(log, checkValue, oldTotems); + } else { + log.println("Totem Pokemon: Unchanged." + NEWLINE); + } + } + + // Wild Pokemon + // 1. Update catch rates + // 2. Randomize Wild Pokemon + + if (settings.isUseMinimumCatchRate()) { + romHandler.changeCatchRates(settings); + } + + switch (settings.getWildPokemonMod()) { + case RANDOM: + romHandler.randomEncounters(settings); + wildsChanged = true; + break; + case AREA_MAPPING: + romHandler.area1to1Encounters(settings); + wildsChanged = true; + break; + case GLOBAL_MAPPING: + romHandler.game1to1Encounters(settings); + wildsChanged = true; + break; + default: + if (settings.isWildLevelsModified()) { + romHandler.onlyChangeWildLevels(settings); + wildsChanged = true; + } + break; + } + + if (wildsChanged) { + logWildPokemonChanges(log); + } else { + log.println("Wild Pokemon: Unchanged." + NEWLINE); + } + + boolean useTimeBasedEncounters = settings.isUseTimeBasedEncounters() || + (settings.getWildPokemonMod() == Settings.WildPokemonMod.UNCHANGED && settings.isWildLevelsModified()); + List encounters = romHandler.getEncounters(useTimeBasedEncounters); + for (EncounterSet es : encounters) { + for (Encounter e : es.encounters) { + checkValue = addToCV(checkValue, e.level, e.pokemon.number); + } + } + + + // In-game trades + + List oldTrades = romHandler.getIngameTrades(); + switch(settings.getInGameTradesMod()) { + case RANDOMIZE_GIVEN: + case RANDOMIZE_GIVEN_AND_REQUESTED: + romHandler.randomizeIngameTrades(settings); + tradesChanged = true; + break; + default: + break; + } + + if (tradesChanged) { + logTrades(log, oldTrades); + } + + // Field Items + switch(settings.getFieldItemsMod()) { + case SHUFFLE: + romHandler.shuffleFieldItems(); + break; + case RANDOM: + case RANDOM_EVEN: + romHandler.randomizeFieldItems(settings); + break; + default: + break; + } + + // Shops + + switch(settings.getShopItemsMod()) { + case SHUFFLE: + romHandler.shuffleShopItems(); + shopsChanged = true; + break; + case RANDOM: + romHandler.randomizeShopItems(settings); + shopsChanged = true; + break; + default: + break; + } + + if (shopsChanged) { + logShops(log); + } + + // Pickup Items + if (settings.getPickupItemsMod() == Settings.PickupItemsMod.RANDOM) { + romHandler.randomizePickupItems(settings); + logPickupItems(log); + } + + // Test output for placement history + // romHandler.renderPlacementHistory(); + + // Intro Pokemon... + romHandler.randomizeIntroPokemon(); + + // Record check value? + romHandler.writeCheckValueToROM(checkValue); + + // Save + if (saveAsDirectory) { + romHandler.saveRomDirectory(filename); + } else { + romHandler.saveRomFile(filename, seed); + } + + // Log tail + String gameName = romHandler.getROMName(); + if (romHandler.hasGameUpdateLoaded()) { + gameName = gameName + " (" + romHandler.getGameUpdateVersion() + ")"; + } + log.println("------------------------------------------------------------------"); + log.println("Randomization of " + gameName + " completed."); + log.println("Time elapsed: " + (System.currentTimeMillis() - startTime) + "ms"); + log.println("RNG Calls: " + RandomSource.callsSinceSeed()); + log.println("------------------------------------------------------------------"); + log.println(); + + // Diagnostics + log.println("--ROM Diagnostics--"); + if (!romHandler.isRomValid()) { + log.println(bundle.getString("Log.InvalidRomLoaded")); + } + romHandler.printRomDiagnostics(log); + + return checkValue; + } + + private int logMoveTutorMoves(PrintStream log, int checkValue, List oldMtMoves) { + log.println("--Move Tutor Moves--"); + List newMtMoves = romHandler.getMoveTutorMoves(); + List moves = romHandler.getMoves(); + for (int i = 0; i < newMtMoves.size(); i++) { + log.printf("%-10s -> %-10s" + NEWLINE, moves.get(oldMtMoves.get(i)).name, + moves.get(newMtMoves.get(i)).name); + checkValue = addToCV(checkValue, newMtMoves.get(i)); + } + log.println(); + return checkValue; + } + + private int logTMMoves(PrintStream log, int checkValue) { + log.println("--TM Moves--"); + List tmMoves = romHandler.getTMMoves(); + List moves = romHandler.getMoves(); + for (int i = 0; i < tmMoves.size(); i++) { + log.printf("TM%02d %s" + NEWLINE, i + 1, moves.get(tmMoves.get(i)).name); + checkValue = addToCV(checkValue, tmMoves.get(i)); + } + log.println(); + return checkValue; + } + + private void logTrades(PrintStream log, List oldTrades) { + log.println("--In-Game Trades--"); + List newTrades = romHandler.getIngameTrades(); + int size = oldTrades.size(); + for (int i = 0; i < size; i++) { + IngameTrade oldT = oldTrades.get(i); + IngameTrade newT = newTrades.get(i); + log.printf("Trade %-11s -> %-11s the %-11s -> %-11s -> %-15s the %s" + NEWLINE, + oldT.requestedPokemon != null ? oldT.requestedPokemon.fullName() : "Any", + oldT.nickname, oldT.givenPokemon.fullName(), + newT.requestedPokemon != null ? newT.requestedPokemon.fullName() : "Any", + newT.nickname, newT.givenPokemon.fullName()); + } + log.println(); + } + + private void logMovesetChanges(PrintStream log) { + log.println("--Pokemon Movesets--"); + List movesets = new ArrayList<>(); + Map> moveData = romHandler.getMovesLearnt(); + Map> eggMoves = romHandler.getEggMoves(); + List moves = romHandler.getMoves(); + List pkmnList = romHandler.getPokemonInclFormes(); + int i = 1; + for (Pokemon pkmn : pkmnList) { + if (pkmn == null || pkmn.actuallyCosmetic) { + continue; + } + StringBuilder evoStr = new StringBuilder(); + try { + evoStr.append(" -> ").append(pkmn.evolutionsFrom.get(0).to.fullName()); + } catch (Exception e) { + evoStr.append(" (no evolution)"); + } + + StringBuilder sb = new StringBuilder(); + + if (romHandler instanceof Gen1RomHandler) { + sb.append(String.format("%03d %s", i, pkmn.fullName())) + .append(evoStr).append(System.getProperty("line.separator")) + .append(String.format("HP %-3d", pkmn.hp)).append(System.getProperty("line.separator")) + .append(String.format("ATK %-3d", pkmn.attack)).append(System.getProperty("line.separator")) + .append(String.format("DEF %-3d", pkmn.defense)).append(System.getProperty("line.separator")) + .append(String.format("SPEC %-3d", pkmn.special)).append(System.getProperty("line.separator")) + .append(String.format("SPE %-3d", pkmn.speed)).append(System.getProperty("line.separator")); + } else { + sb.append(String.format("%03d %s", i, pkmn.fullName())) + .append(evoStr).append(System.getProperty("line.separator")) + .append(String.format("HP %-3d", pkmn.hp)).append(System.getProperty("line.separator")) + .append(String.format("ATK %-3d", pkmn.attack)).append(System.getProperty("line.separator")) + .append(String.format("DEF %-3d", pkmn.defense)).append(System.getProperty("line.separator")) + .append(String.format("SPA %-3d", pkmn.spatk)).append(System.getProperty("line.separator")) + .append(String.format("SPD %-3d", pkmn.spdef)).append(System.getProperty("line.separator")) + .append(String.format("SPE %-3d", pkmn.speed)).append(System.getProperty("line.separator")); + } + + i++; + + List data = moveData.get(pkmn.number); + for (MoveLearnt ml : data) { + try { + if (ml.level == 0) { + sb.append("Learned upon evolution: ") + .append(moves.get(ml.move).name).append(System.getProperty("line.separator")); + } else { + sb.append("Level ") + .append(String.format("%-2d", ml.level)) + .append(": ") + .append(moves.get(ml.move).name).append(System.getProperty("line.separator")); + } + } catch (NullPointerException ex) { + sb.append("invalid move at level").append(ml.level); + } + } + List eggMove = eggMoves.get(pkmn.number); + if (eggMove != null && eggMove.size() != 0) { + sb.append("Egg Moves:").append(System.getProperty("line.separator")); + for (Integer move : eggMove) { + sb.append(" - ").append(moves.get(move).name).append(System.getProperty("line.separator")); + } + } + + movesets.add(sb.toString()); + } + Collections.sort(movesets); + for (String moveset : movesets) { + log.println(moveset); + } + log.println(); + } + + private void logMoveUpdates(PrintStream log) { + log.println("--Move Updates--"); + List moves = romHandler.getMoves(); + Map moveUpdates = romHandler.getMoveUpdates(); + for (int moveID : moveUpdates.keySet()) { + boolean[] changes = moveUpdates.get(moveID); + Move mv = moves.get(moveID); + List nonTypeChanges = new ArrayList<>(); + if (changes[0]) { + nonTypeChanges.add(String.format("%d power", mv.power)); + } + if (changes[1]) { + nonTypeChanges.add(String.format("%d PP", mv.pp)); + } + if (changes[2]) { + nonTypeChanges.add(String.format("%.00f%% accuracy", mv.hitratio)); + } + String logStr = "Made " + mv.name; + // type or not? + if (changes[3]) { + logStr += " be " + mv.type + "-type"; + if (nonTypeChanges.size() > 0) { + logStr += " and"; + } + } + if (changes[4]) { + if (mv.category == MoveCategory.PHYSICAL) { + logStr += " a Physical move"; + } else if (mv.category == MoveCategory.SPECIAL) { + logStr += " a Special move"; + } else if (mv.category == MoveCategory.STATUS) { + logStr += " a Status move"; + } + } + if (nonTypeChanges.size() > 0) { + logStr += " have "; + if (nonTypeChanges.size() == 3) { + logStr += nonTypeChanges.get(0) + ", " + nonTypeChanges.get(1) + " and " + nonTypeChanges.get(2); + } else if (nonTypeChanges.size() == 2) { + logStr += nonTypeChanges.get(0) + " and " + nonTypeChanges.get(1); + } else { + logStr += nonTypeChanges.get(0); + } + } + log.println(logStr); + } + log.println(); + } + + private void logEvolutionChanges(PrintStream log) { + log.println("--Randomized Evolutions--"); + List allPokes = romHandler.getPokemonInclFormes(); + for (Pokemon pk : allPokes) { + if (pk != null && !pk.actuallyCosmetic) { + int numEvos = pk.evolutionsFrom.size(); + if (numEvos > 0) { + StringBuilder evoStr = new StringBuilder(pk.evolutionsFrom.get(0).toFullName()); + for (int i = 1; i < numEvos; i++) { + if (i == numEvos - 1) { + evoStr.append(" and ").append(pk.evolutionsFrom.get(i).toFullName()); + } else { + evoStr.append(", ").append(pk.evolutionsFrom.get(i).toFullName()); + } + } + log.printf("%-15s -> %-15s" + NEWLINE, pk.fullName(), evoStr.toString()); + } + } + } + + log.println(); + } + + private void logPokemonTraitChanges(final PrintStream log) { + List allPokes = romHandler.getPokemonInclFormes(); + String[] itemNames = romHandler.getItemNames(); + // Log base stats & types + log.println("--Pokemon Base Stats & Types--"); + if (romHandler instanceof Gen1RomHandler) { + log.println("NUM|NAME |TYPE | HP| ATK| DEF| SPE|SPEC"); + for (Pokemon pkmn : allPokes) { + if (pkmn != null) { + String typeString = pkmn.primaryType == null ? "???" : pkmn.primaryType.toString(); + if (pkmn.secondaryType != null) { + typeString += "/" + pkmn.secondaryType.toString(); + } + log.printf("%3d|%-10s|%-17s|%4d|%4d|%4d|%4d|%4d" + NEWLINE, pkmn.number, pkmn.fullName(), typeString, + pkmn.hp, pkmn.attack, pkmn.defense, pkmn.speed, pkmn.special ); + } + + } + } else { + String nameSp = " "; + String nameSpFormat = "%-13s"; + String abSp = " "; + String abSpFormat = "%-12s"; + if (romHandler.generationOfPokemon() == 5) { + nameSp = " "; + } else if (romHandler.generationOfPokemon() == 6) { + nameSp = " "; + nameSpFormat = "%-16s"; + abSp = " "; + abSpFormat = "%-14s"; + } else if (romHandler.generationOfPokemon() >= 7) { + nameSp = " "; + nameSpFormat = "%-16s"; + abSp = " "; + abSpFormat = "%-16s"; + } + + log.print("NUM|NAME" + nameSp + "|TYPE | HP| ATK| DEF|SATK|SDEF| SPD"); + int abils = romHandler.abilitiesPerPokemon(); + for (int i = 0; i < abils; i++) { + log.print("|ABILITY" + (i + 1) + abSp); + } + log.print("|ITEM"); + log.println(); + int i = 0; + for (Pokemon pkmn : allPokes) { + if (pkmn != null && !pkmn.actuallyCosmetic) { + i++; + String typeString = pkmn.primaryType == null ? "???" : pkmn.primaryType.toString(); + if (pkmn.secondaryType != null) { + typeString += "/" + pkmn.secondaryType.toString(); + } + log.printf("%3d|" + nameSpFormat + "|%-17s|%4d|%4d|%4d|%4d|%4d|%4d", i, pkmn.fullName(), typeString, + pkmn.hp, pkmn.attack, pkmn.defense, pkmn.spatk, pkmn.spdef, pkmn.speed); + if (abils > 0) { + log.printf("|" + abSpFormat + "|" + abSpFormat, romHandler.abilityName(pkmn.ability1), + pkmn.ability1 == pkmn.ability2 ? "--" : romHandler.abilityName(pkmn.ability2)); + if (abils > 2) { + log.printf("|" + abSpFormat, romHandler.abilityName(pkmn.ability3)); + } + } + log.print("|"); + if (pkmn.guaranteedHeldItem > 0) { + log.print(itemNames[pkmn.guaranteedHeldItem] + " (100%)"); + } else { + int itemCount = 0; + if (pkmn.commonHeldItem > 0) { + itemCount++; + log.print(itemNames[pkmn.commonHeldItem] + " (common)"); + } + if (pkmn.rareHeldItem > 0) { + if (itemCount > 0) { + log.print(", "); + } + itemCount++; + log.print(itemNames[pkmn.rareHeldItem] + " (rare)"); + } + if (pkmn.darkGrassHeldItem > 0) { + if (itemCount > 0) { + log.print(", "); + } + log.print(itemNames[pkmn.darkGrassHeldItem] + " (dark grass only)"); + } + } + log.println(); + } + + } + } + log.println(); + } + + private void logTMHMCompatibility(final PrintStream log) { + log.println("--TM Compatibility--"); + Map compat = romHandler.getTMHMCompatibility(); + List tmHMs = new ArrayList<>(romHandler.getTMMoves()); + tmHMs.addAll(romHandler.getHMMoves()); + List moveData = romHandler.getMoves(); + + logCompatibility(log, compat, tmHMs, moveData, true); + } + + private void logTutorCompatibility(final PrintStream log) { + log.println("--Move Tutor Compatibility--"); + Map compat = romHandler.getMoveTutorCompatibility(); + List tutorMoves = romHandler.getMoveTutorMoves(); + List moveData = romHandler.getMoves(); + + logCompatibility(log, compat, tutorMoves, moveData, false); + } + + private void logCompatibility(final PrintStream log, Map compat, List moveList, + List moveData, boolean includeTMNumber) { + int tmCount = romHandler.getTMCount(); + for (Map.Entry entry : compat.entrySet()) { + Pokemon pkmn = entry.getKey(); + if (pkmn.actuallyCosmetic) continue; + boolean[] flags = entry.getValue(); + + String nameSpFormat = "%-14s"; + if (romHandler.generationOfPokemon() >= 6) { + nameSpFormat = "%-17s"; + } + log.printf("%3d " + nameSpFormat, pkmn.number, pkmn.fullName() + " "); + + for (int i = 1; i < flags.length; i++) { + String moveName = moveData.get(moveList.get(i - 1)).name; + if (moveName.length() == 0) { + moveName = "(BLANK)"; + } + int moveNameLength = moveName.length(); + if (flags[i]) { + if (includeTMNumber) { + if (i <= tmCount) { + log.printf("|TM%02d %" + moveNameLength + "s ", i, moveName); + } else { + log.printf("|HM%02d %" + moveNameLength + "s ", i-tmCount, moveName); + } + } else { + log.printf("|%" + moveNameLength + "s ", moveName); + } + } else { + if (includeTMNumber) { + log.printf("| %" + (moveNameLength+4) + "s ", "-"); + } else { + log.printf("| %" + (moveNameLength-1) + "s ", "-"); + } + } + } + log.println("|"); + } + log.println(""); + } + + private void logUpdatedEvolutions(final PrintStream log, Set updatedEvolutions, + Set otherUpdatedEvolutions) { + for (EvolutionUpdate evo: updatedEvolutions) { + if (otherUpdatedEvolutions != null && otherUpdatedEvolutions.contains(evo)) { + log.println(evo.toString() + " (Overwritten by \"Make Evolutions Easier\", see below)"); + } else { + log.println(evo.toString()); + } + } + log.println(); + } + + private void logStarters(final PrintStream log) { + + switch(settings.getStartersMod()) { + case CUSTOM: + log.println("--Custom Starters--"); + break; + case COMPLETELY_RANDOM: + log.println("--Random Starters--"); + break; + case RANDOM_WITH_TWO_EVOLUTIONS: + log.println("--Random 2-Evolution Starters--"); + break; + default: + break; + } + + List starters = romHandler.getPickedStarters(); + int i = 1; + for (Pokemon starter: starters) { + log.println("Set starter " + i + " to " + starter.fullName()); + i++; + } + log.println(); + } + + private void logWildPokemonChanges(final PrintStream log) { + + log.println("--Wild Pokemon--"); + boolean useTimeBasedEncounters = settings.isUseTimeBasedEncounters() || + (settings.getWildPokemonMod() == Settings.WildPokemonMod.UNCHANGED && settings.isWildLevelsModified()); + List encounters = romHandler.getEncounters(useTimeBasedEncounters); + int idx = 0; + for (EncounterSet es : encounters) { + idx++; + log.print("Set #" + idx + " "); + if (es.displayName != null) { + log.print("- " + es.displayName + " "); + } + log.print("(rate=" + es.rate + ")"); + log.println(); + for (Encounter e : es.encounters) { + StringBuilder sb = new StringBuilder(); + if (e.isSOS) { + String stringToAppend; + switch (e.sosType) { + case RAIN: + stringToAppend = "Rain SOS: "; + break; + case HAIL: + stringToAppend = "Hail SOS: "; + break; + case SAND: + stringToAppend = "Sand SOS: "; + break; + default: + stringToAppend = " SOS: "; + break; + } + sb.append(stringToAppend); + } + sb.append(e.pokemon.fullName()).append(" Lv"); + if (e.maxLevel > 0 && e.maxLevel != e.level) { + sb.append("s ").append(e.level).append("-").append(e.maxLevel); + } else { + sb.append(e.level); + } + String whitespaceFormat = romHandler.generationOfPokemon() == 7 ? "%-31s" : "%-25s"; + log.print(String.format(whitespaceFormat, sb)); + StringBuilder sb2 = new StringBuilder(); + if (romHandler instanceof Gen1RomHandler) { + sb2.append(String.format("HP %-3d ATK %-3d DEF %-3d SPECIAL %-3d SPEED %-3d", e.pokemon.hp, e.pokemon.attack, e.pokemon.defense, e.pokemon.special, e.pokemon.speed)); + } else { + sb2.append(String.format("HP %-3d ATK %-3d DEF %-3d SPATK %-3d SPDEF %-3d SPEED %-3d", e.pokemon.hp, e.pokemon.attack, e.pokemon.defense, e.pokemon.spatk, e.pokemon.spdef, e.pokemon.speed)); + } + log.print(sb2); + log.println(); + } + log.println(); + } + log.println(); + } + + private void maybeLogTrainerChanges(final PrintStream log, List originalTrainerNames, boolean trainerNamesChanged, boolean logTrainerMovesets) { + log.println("--Trainers Pokemon--"); + List trainers = romHandler.getTrainers(); + for (Trainer t : trainers) { + log.print("#" + t.index + " "); + String originalTrainerName = originalTrainerNames.get(t.index); + String currentTrainerName = ""; + if (t.fullDisplayName != null) { + currentTrainerName = t.fullDisplayName; + } else if (t.name != null) { + currentTrainerName = t.name; + } + if (!currentTrainerName.isEmpty()) { + if (trainerNamesChanged) { + log.printf("(%s => %s)", originalTrainerName, currentTrainerName); + } else { + log.printf("(%s)", currentTrainerName); + } + } + if (t.offset != 0) { + log.printf("@%X", t.offset); + } + + String[] itemNames = romHandler.getItemNames(); + if (logTrainerMovesets) { + log.println(); + for (TrainerPokemon tpk : t.pokemon) { + List moves = romHandler.getMoves(); + log.printf(tpk.toString(), itemNames[tpk.heldItem]); + log.print(", Ability: " + romHandler.abilityName(romHandler.getAbilityForTrainerPokemon(tpk))); + log.print(" - "); + boolean first = true; + for (int move : tpk.moves) { + if (move != 0) { + if (!first) { + log.print(", "); + } + log.print(moves.get(move).name); + first = false; + } + } + log.println(); + } + } else { + log.print(" - "); + boolean first = true; + for (TrainerPokemon tpk : t.pokemon) { + if (!first) { + log.print(", "); + } + log.printf(tpk.toString(), itemNames[tpk.heldItem]); + first = false; + } + } + log.println(); + } + log.println(); + } + + private int logStaticPokemon(final PrintStream log, int checkValue, List oldStatics) { + + List newStatics = romHandler.getStaticPokemon(); + + log.println("--Static Pokemon--"); + Map seenPokemon = new TreeMap<>(); + for (int i = 0; i < oldStatics.size(); i++) { + StaticEncounter oldP = oldStatics.get(i); + StaticEncounter newP = newStatics.get(i); + checkValue = addToCV(checkValue, newP.pkmn.number); + String oldStaticString = oldP.toString(settings.isStaticLevelModified()); + log.print(oldStaticString); + if (seenPokemon.containsKey(oldStaticString)) { + int amount = seenPokemon.get(oldStaticString); + log.print("(" + (++amount) + ")"); + seenPokemon.put(oldStaticString, amount); + } else { + seenPokemon.put(oldStaticString, 1); + } + log.println(" => " + newP.toString(settings.isStaticLevelModified())); + } + log.println(); + + return checkValue; + } + + private int logTotemPokemon(final PrintStream log, int checkValue, List oldTotems) { + + List newTotems = romHandler.getTotemPokemon(); + + String[] itemNames = romHandler.getItemNames(); + log.println("--Totem Pokemon--"); + for (int i = 0; i < oldTotems.size(); i++) { + TotemPokemon oldP = oldTotems.get(i); + TotemPokemon newP = newTotems.get(i); + checkValue = addToCV(checkValue, newP.pkmn.number); + log.println(oldP.pkmn.fullName() + " =>"); + log.printf(newP.toString(),itemNames[newP.heldItem]); + } + log.println(); + + return checkValue; + } + + private void logMoveChanges(final PrintStream log) { + + log.println("--Move Data--"); + log.print("NUM|NAME |TYPE |POWER|ACC.|PP"); + if (romHandler.hasPhysicalSpecialSplit()) { + log.print(" |CATEGORY"); + } + log.println(); + List allMoves = romHandler.getMoves(); + for (Move mv : allMoves) { + if (mv != null) { + String mvType = (mv.type == null) ? "???" : mv.type.toString(); + log.printf("%3d|%-15s|%-8s|%5d|%4d|%3d", mv.internalId, mv.name, mvType, mv.power, + (int) mv.hitratio, mv.pp); + if (romHandler.hasPhysicalSpecialSplit()) { + log.printf("| %s", mv.category.toString()); + } + log.println(); + } + } + log.println(); + } + + private void logShops(final PrintStream log) { + String[] itemNames = romHandler.getItemNames(); + log.println("--Shops--"); + Map shopsDict = romHandler.getShopItems(); + for (int shopID : shopsDict.keySet()) { + Shop shop = shopsDict.get(shopID); + log.printf("%s", shop.name); + log.println(); + List shopItems = shop.items; + for (int shopItemID : shopItems) { + log.printf("- %5s", itemNames[shopItemID]); + log.println(); + } + + log.println(); + } + log.println(); + } + + private void logPickupItems(final PrintStream log) { + List pickupItems = romHandler.getPickupItems(); + String[] itemNames = romHandler.getItemNames(); + log.println("--Pickup Items--"); + for (int levelRange = 0; levelRange < 10; levelRange++) { + int startingLevel = (levelRange * 10) + 1; + int endingLevel = (levelRange + 1) * 10; + log.printf("Level %s-%s", startingLevel, endingLevel); + log.println(); + TreeMap> itemListPerProbability = new TreeMap<>(); + for (PickupItem pickupItem : pickupItems) { + int probability = pickupItem.probabilities[levelRange]; + if (itemListPerProbability.containsKey(probability)) { + itemListPerProbability.get(probability).add(itemNames[pickupItem.item]); + } else if (probability > 0) { + List itemList = new ArrayList<>(); + itemList.add(itemNames[pickupItem.item]); + itemListPerProbability.put(probability, itemList); + } + } + for (Map.Entry> itemListPerProbabilityEntry : itemListPerProbability.descendingMap().entrySet()) { + int probability = itemListPerProbabilityEntry.getKey(); + List itemList = itemListPerProbabilityEntry.getValue(); + String itemsString = String.join(", ", itemList); + log.printf("%d%%: %s", probability, itemsString); + log.println(); + } + log.println(); + } + log.println(); + } + + private List getTrainerNames() { + List trainerNames = new ArrayList<>(); + trainerNames.add(""); // for index 0 + List trainers = romHandler.getTrainers(); + for (Trainer t : trainers) { + if (t.fullDisplayName != null) { + trainerNames.add(t.fullDisplayName); + } else if (t.name != null) { + trainerNames.add(t.name); + } else { + trainerNames.add(""); + } + } + return trainerNames; + } + + + private static int addToCV(int checkValue, int... values) { + for (int value : values) { + checkValue = Integer.rotateLeft(checkValue, 3); + checkValue ^= value; + } + return checkValue; + } +} \ No newline at end of file diff --git a/src/com/pkrandom/RomFunctions.java b/src/com/pkrandom/RomFunctions.java new file mode 100755 index 0000000..fb123dd --- /dev/null +++ b/src/com/pkrandom/RomFunctions.java @@ -0,0 +1,475 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- RomFunctions.java - contains functions useful throughout the program. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import com.pkrandom.pokemon.Evolution; +import com.pkrandom.pokemon.MoveLearnt; +import com.pkrandom.pokemon.Pokemon; +import com.pkrandom.romhandlers.RomHandler; + +public class RomFunctions { + + public static Set getBasicPokemon(RomHandler baseRom) { + List allPokes = baseRom.getPokemonInclFormes(); + Set basicPokes = new TreeSet<>(); + for (Pokemon pkmn : allPokes) { + if (pkmn != null) { + if (pkmn.evolutionsTo.size() < 1) { + basicPokes.add(pkmn); + } + } + } + return basicPokes; + } + + public static Set getSplitEvolutions(RomHandler baseRom) { + List allPokes = baseRom.getPokemonInclFormes(); + Set splitEvos = new TreeSet<>(); + for (Pokemon pkmn : allPokes) { + if (pkmn != null) { + if (pkmn.evolutionsTo.size() > 0) { + Evolution onlyEvo = pkmn.evolutionsTo.get(0); + if (!onlyEvo.carryStats) { + splitEvos.add(pkmn); + } + } + } + } + return splitEvos; + } + + public static Set getMiddleEvolutions(RomHandler baseRom, boolean includeSplitEvos) { + List allPokes = baseRom.getPokemon(); + Set middleEvolutions = new TreeSet<>(); + for (Pokemon pkmn : allPokes) { + if (pkmn != null) { + if (pkmn.evolutionsTo.size() == 1 && pkmn.evolutionsFrom.size() > 0) { + Evolution onlyEvo = pkmn.evolutionsTo.get(0); + if (onlyEvo.carryStats || includeSplitEvos) { + middleEvolutions.add(pkmn); + } + } + } + } + return middleEvolutions; + } + + public static Set getFinalEvolutions(RomHandler baseRom, boolean includeSplitEvos) { + List allPokes = baseRom.getPokemon(); + Set finalEvolutions = new TreeSet<>(); + for (Pokemon pkmn : allPokes) { + if (pkmn != null) { + if (pkmn.evolutionsTo.size() == 1 && pkmn.evolutionsFrom.size() == 0) { + Evolution onlyEvo = pkmn.evolutionsTo.get(0); + if (onlyEvo.carryStats || includeSplitEvos) { + finalEvolutions.add(pkmn); + } + } + } + } + return finalEvolutions; + } + + /** + * Get the 4 moves known by a Pokemon at a particular level. + * + * @param pkmn Pokemon index to get moves for. + * @param movesets Map of Pokemon indices mapped to movesets. + * @param level Level to get at. + * @return Array with move indices. + */ + public static int[] getMovesAtLevel(int pkmn, Map> movesets, int level) { + return getMovesAtLevel(pkmn, movesets, level, 0); + } + + public static int[] getMovesAtLevel(int pkmn, Map> movesets, int level, int emptyValue) { + int[] curMoves = new int[4]; + + if (emptyValue != 0) { + Arrays.fill(curMoves, emptyValue); + } + + int moveCount = 0; + List movepool = movesets.get(pkmn); + for (MoveLearnt ml : movepool) { + if (ml.level > level) { + // we're done + break; + } + + boolean alreadyKnownMove = false; + for (int i = 0; i < moveCount; i++) { + if (curMoves[i] == ml.move) { + alreadyKnownMove = true; + break; + } + } + + if (!alreadyKnownMove) { + // add this move to the moveset + if (moveCount == 4) { + // shift moves up and add to last slot + System.arraycopy(curMoves, 1, curMoves, 0, 3); + curMoves[3] = ml.move; + } else { + // add to next available slot + curMoves[moveCount++] = ml.move; + } + } + } + + return curMoves; + } + + public static String camelCase(String original) { + char[] string = original.toLowerCase().toCharArray(); + boolean docap = true; + for (int j = 0; j < string.length; j++) { + char current = string[j]; + if (docap && Character.isLetter(current)) { + string[j] = Character.toUpperCase(current); + docap = false; + } else { + if (!docap && !Character.isLetter(current) && current != '\'' && current != '’') { + docap = true; + } + } + } + return new String(string); + } + + public static int freeSpaceFinder(byte[] rom, byte freeSpace, int amount, int offset) { + // by default align to 4 bytes to make sure things don't break + return freeSpaceFinder(rom, freeSpace, amount, offset, true); + } + + public static int freeSpaceFinder(byte[] rom, byte freeSpace, int amount, int offset, boolean longAligned) { + if (!longAligned) { + // Find 2 more than necessary and return 2 into it, + // to preserve stuff like FF terminators for strings + // 161: and FFFF terminators for movesets + byte[] searchNeedle = new byte[amount + 2]; + for (int i = 0; i < amount + 2; i++) { + searchNeedle[i] = freeSpace; + } + return searchForFirst(rom, offset, searchNeedle) + 2; + } else { + // Find 5 more than necessary and return into it as necessary for + // 4-alignment, + // to preserve stuff like FF terminators for strings + // 161: and FFFF terminators for movesets + byte[] searchNeedle = new byte[amount + 5]; + for (int i = 0; i < amount + 5; i++) { + searchNeedle[i] = freeSpace; + } + return (searchForFirst(rom, offset, searchNeedle) + 5) & ~3; + } + } + + public static List search(byte[] haystack, byte[] needle) { + return search(haystack, 0, haystack.length, needle); + } + + public static List search(byte[] haystack, int beginOffset, byte[] needle) { + return search(haystack, beginOffset, haystack.length, needle); + } + + public static List search(byte[] haystack, int beginOffset, int endOffset, byte[] needle) { + int currentMatchStart = beginOffset; + int currentCharacterPosition = 0; + + int needleSize = needle.length; + + int[] toFillTable = buildKMPSearchTable(needle); + List results = new ArrayList<>(); + + while ((currentMatchStart + currentCharacterPosition) < endOffset) { + + if (needle[currentCharacterPosition] == (haystack[currentCharacterPosition + currentMatchStart])) { + currentCharacterPosition = currentCharacterPosition + 1; + + if (currentCharacterPosition == (needleSize)) { + results.add(currentMatchStart); + currentCharacterPosition = 0; + currentMatchStart = currentMatchStart + needleSize; + + } + + } else { + currentMatchStart = currentMatchStart + currentCharacterPosition + - toFillTable[currentCharacterPosition]; + + if (toFillTable[currentCharacterPosition] > -1) { + currentCharacterPosition = toFillTable[currentCharacterPosition]; + } + + else { + currentCharacterPosition = 0; + + } + + } + } + return results; + } + + private static int searchForFirst(byte[] haystack, int beginOffset, byte[] needle) { + int currentMatchStart = beginOffset; + int currentCharacterPosition = 0; + + int docSize = haystack.length; + int needleSize = needle.length; + + int[] toFillTable = buildKMPSearchTable(needle); + + while ((currentMatchStart + currentCharacterPosition) < docSize) { + + if (needle[currentCharacterPosition] == (haystack[currentCharacterPosition + currentMatchStart])) { + currentCharacterPosition = currentCharacterPosition + 1; + + if (currentCharacterPosition == (needleSize)) { + return currentMatchStart; + } + + } else { + currentMatchStart = currentMatchStart + currentCharacterPosition + - toFillTable[currentCharacterPosition]; + + if (toFillTable[currentCharacterPosition] > -1) { + currentCharacterPosition = toFillTable[currentCharacterPosition]; + } + + else { + currentCharacterPosition = 0; + + } + + } + } + return -1; + } + + private static int[] buildKMPSearchTable(byte[] needle) { + int[] stable = new int[needle.length]; + int pos = 2; + int j = 0; + stable[0] = -1; + stable[1] = 0; + while (pos < needle.length) { + if (needle[pos - 1] == needle[j]) { + stable[pos] = j + 1; + pos++; + j++; + } else if (j > 0) { + j = stable[j]; + } else { + stable[pos] = 0; + pos++; + } + } + return stable; + } + + public static String rewriteDescriptionForNewLineSize(String moveDesc, String newline, int lineSize, + StringSizeDeterminer ssd) { + // We rewrite the description we're given based on some new chars per + // line. + moveDesc = moveDesc.replace("-" + newline, "").replace(newline, " "); + // Keep spatk/spdef as one word on one line + moveDesc = moveDesc.replace("Sp. Atk", "Sp__Atk"); + moveDesc = moveDesc.replace("Sp. Def", "Sp__Def"); + moveDesc = moveDesc.replace("SP. ATK", "SP__ATK"); + moveDesc = moveDesc.replace("SP. DEF", "SP__DEF"); + String[] words = moveDesc.split(" "); + StringBuilder fullDesc = new StringBuilder(); + StringBuilder thisLine = new StringBuilder(); + int currLineWC = 0; + int currLineCC = 0; + int linesWritten = 0; + for (int i = 0; i < words.length; i++) { + // Reverse the spatk/spdef preservation from above + words[i] = words[i].replace("SP__", "SP. "); + words[i] = words[i].replace("Sp__", "Sp. "); + int reqLength = ssd.lengthFor(words[i]); + if (currLineWC > 0) { + reqLength++; + } + if (currLineCC + reqLength <= lineSize) { + // add to current line + if (currLineWC > 0) { + thisLine.append(' '); + } + thisLine.append(words[i]); + currLineWC++; + currLineCC += reqLength; + } else { + // Save current line, if applicable + if (currLineWC > 0) { + if (linesWritten > 0) { + fullDesc.append(newline); + } + fullDesc.append(thisLine.toString()); + linesWritten++; + thisLine = new StringBuilder(); + } + // Start the new line + thisLine.append(words[i]); + currLineWC = 1; + currLineCC = ssd.lengthFor(words[i]); + } + } + + // If the last line has anything add it + if (currLineWC > 0) { + if (linesWritten > 0) { + fullDesc.append(newline); + } + fullDesc.append(thisLine.toString()); + } + + return fullDesc.toString(); + } + + public static String formatTextWithReplacements(String text, Map replacements, String newline, + String extraline, String newpara, int maxLineLength, StringSizeDeterminer ssd) { + // Ends with a paragraph indicator? + boolean endsWithPara = false; + if (text.endsWith(newpara)) { + endsWithPara = true; + text = text.substring(0, text.length() - newpara.length()); + } + // Replace current line endings with spaces + text = text.replace(newline, " ").replace(extraline, " "); + // Replace words if replacements are set + // do it in two stages so the rules don't conflict + if (replacements != null) { + int index = 0; + for (Map.Entry toReplace : replacements.entrySet()) { + index++; + text = text.replace(toReplace.getKey(), ""); + } + index = 0; + for (Map.Entry toReplace : replacements.entrySet()) { + index++; + text = text.replace("", toReplace.getValue()); + } + } + // Split on paragraphs and deal with each one individually + String[] oldParagraphs = text.split(newpara.replace("\\", "\\\\")); + StringBuilder finalResult = new StringBuilder(); + int sentenceNewLineSize = Math.max(10, maxLineLength / 2); + for (int para = 0; para < oldParagraphs.length; para++) { + String[] words = oldParagraphs[para].split(" "); + StringBuilder fullPara = new StringBuilder(); + StringBuilder thisLine = new StringBuilder(); + int currLineWC = 0; + int currLineCC = 0; + int linesWritten = 0; + char currLineLastChar = 0; + for (String word : words) { + int reqLength = ssd.lengthFor(word); + if (currLineWC > 0) { + reqLength++; + } + if ((currLineCC + reqLength > maxLineLength) + || (currLineCC >= sentenceNewLineSize && (currLineLastChar == '.' || currLineLastChar == '?' + || currLineLastChar == '!' || currLineLastChar == '…' || currLineLastChar == ','))) { + // new line + // Save current line, if applicable + if (currLineWC > 0) { + if (linesWritten > 1) { + fullPara.append(extraline); + } else if (linesWritten == 1) { + fullPara.append(newline); + } + fullPara.append(thisLine.toString()); + linesWritten++; + thisLine = new StringBuilder(); + } + // Start the new line + thisLine.append(word); + currLineWC = 1; + currLineCC = ssd.lengthFor(word); + if (word.length() == 0) { + currLineLastChar = 0; + } else { + currLineLastChar = word.charAt(word.length() - 1); + } + } else { + // add to current line + if (currLineWC > 0) { + thisLine.append(' '); + } + thisLine.append(word); + currLineWC++; + currLineCC += reqLength; + if (word.length() == 0) { + currLineLastChar = 0; + } else { + currLineLastChar = word.charAt(word.length() - 1); + } + } + } + + // If the last line has anything add it + if (currLineWC > 0) { + if (linesWritten > 1) { + fullPara.append(extraline); + } else if (linesWritten == 1) { + fullPara.append(newline); + } + fullPara.append(thisLine.toString()); + } + if (para > 0) { + finalResult.append(newpara); + } + finalResult.append(fullPara.toString()); + } + if (endsWithPara) { + finalResult.append(newpara); + } + return finalResult.toString(); + } + + public interface StringSizeDeterminer { + int lengthFor(String encodedText); + } + + public static class StringLengthSD implements StringSizeDeterminer { + + @Override + public int lengthFor(String encodedText) { + return encodedText.length(); + } + + } + +} diff --git a/src/com/pkrandom/Settings.java b/src/com/pkrandom/Settings.java new file mode 100644 index 0000000..eb2cf36 --- /dev/null +++ b/src/com/pkrandom/Settings.java @@ -0,0 +1,2388 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- Settings.java - encapsulates a configuration of settings used by the --*/ +/*-- randomizer to determine how to randomize the --*/ +/*-- target game. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.Base64; +import java.util.List; +import java.util.zip.CRC32; + +import com.pkrandom.pokemon.ExpCurve; +import com.pkrandom.pokemon.GenRestrictions; +import com.pkrandom.pokemon.Pokemon; +import com.pkrandom.romhandlers.Gen1RomHandler; +import com.pkrandom.romhandlers.Gen2RomHandler; +import com.pkrandom.romhandlers.Gen3RomHandler; +import com.pkrandom.romhandlers.Gen5RomHandler; +import com.pkrandom.romhandlers.RomHandler; + +public class Settings { + + public static final int VERSION = Version.VERSION; + + public static final int LENGTH_OF_SETTINGS_DATA = 51; + + private CustomNamesSet customNames; + + private String romName; + private boolean updatedFromOldVersion = false; + private GenRestrictions currentRestrictions; + private int currentMiscTweaks; + + private boolean changeImpossibleEvolutions; + private boolean makeEvolutionsEasier; + private boolean removeTimeBasedEvolutions; + private boolean raceMode; + private boolean blockBrokenMoves; + private boolean limitPokemon; + private boolean banIrregularAltFormes; + private boolean dualTypeOnly; + + public enum BaseStatisticsMod { + UNCHANGED, SHUFFLE, RANDOM, + } + + public enum ExpCurveMod { + LEGENDARIES, STRONG_LEGENDARIES, ALL + } + + private BaseStatisticsMod baseStatisticsMod = BaseStatisticsMod.UNCHANGED; + private boolean baseStatsFollowEvolutions; + private boolean baseStatsFollowMegaEvolutions; + private boolean assignEvoStatsRandomly; + private boolean updateBaseStats; + private int updateBaseStatsToGeneration; + private boolean standardizeEXPCurves; + private ExpCurve selectedEXPCurve; + private ExpCurveMod expCurveMod = ExpCurveMod.LEGENDARIES; + + public enum AbilitiesMod { + UNCHANGED, RANDOMIZE + } + + private AbilitiesMod abilitiesMod = AbilitiesMod.UNCHANGED; + private boolean allowWonderGuard = true; + private boolean abilitiesFollowEvolutions; + private boolean abilitiesFollowMegaEvolutions; + private boolean banTrappingAbilities; + private boolean banNegativeAbilities; + private boolean banBadAbilities; + private boolean weighDuplicateAbilitiesTogether; + private boolean ensureTwoAbilities; + + public enum StartersMod { + UNCHANGED, CUSTOM, COMPLETELY_RANDOM, RANDOM_WITH_TWO_EVOLUTIONS + } + + private StartersMod startersMod = StartersMod.UNCHANGED; + private boolean allowStarterAltFormes; + + // index in the rom's list of pokemon + // offset from the dropdown index from RandomizerGUI by 1 + private int[] customStarters = new int[3]; + private boolean randomizeStartersHeldItems; + private boolean limitMainGameLegendaries; + private boolean limit600; + private boolean banBadRandomStarterHeldItems; + + public enum TypesMod { + UNCHANGED, RANDOM_FOLLOW_EVOLUTIONS, COMPLETELY_RANDOM + } + + private TypesMod typesMod = TypesMod.UNCHANGED; + + private boolean typesFollowMegaEvolutions; + + // Evolutions + public enum EvolutionsMod { + UNCHANGED, RANDOM, RANDOM_EVERY_LEVEL + } + + private EvolutionsMod evolutionsMod = EvolutionsMod.UNCHANGED; + private boolean evosSimilarStrength; + private boolean evosSameTyping; + private boolean evosMaxThreeStages; + private boolean evosForceChange; + private boolean evosAllowAltFormes; + + // Move data + private boolean randomizeMovePowers; + private boolean randomizeMoveAccuracies; + private boolean randomizeMovePPs; + private boolean randomizeMoveTypes; + private boolean randomizeMoveCategory; + private boolean updateMoves; + private int updateMovesToGeneration; + private boolean updateMovesLegacy; + + public enum MovesetsMod { + UNCHANGED, RANDOM_PREFER_SAME_TYPE, COMPLETELY_RANDOM, METRONOME_ONLY + } + + private MovesetsMod movesetsMod = MovesetsMod.UNCHANGED; + private boolean startWithGuaranteedMoves; + private int guaranteedMoveCount = 2; + private boolean reorderDamagingMoves; + private boolean movesetsForceGoodDamaging; + private int movesetsGoodDamagingPercent = 0; + private boolean blockBrokenMovesetMoves; + private boolean evolutionMovesForAll; + + public enum TrainersMod { + UNCHANGED, RANDOM, DISTRIBUTED, MAINPLAYTHROUGH, TYPE_THEMED, TYPE_THEMED_ELITE4_GYMS + } + + private TrainersMod trainersMod = TrainersMod.UNCHANGED; + private boolean rivalCarriesStarterThroughout; + private boolean trainersUsePokemonOfSimilarStrength; + private boolean trainersMatchTypingDistribution; + private boolean trainersBlockLegendaries = true; + private boolean trainersBlockEarlyWonderGuard = true; + private boolean trainersEnforceDistribution; + private boolean trainersEnforceMainPlaythrough; + private boolean randomizeTrainerNames; + private boolean randomizeTrainerClassNames; + private boolean trainersForceFullyEvolved; + private int trainersForceFullyEvolvedLevel = 30; + private boolean trainersLevelModified; + private int trainersLevelModifier = 0; // -50 ~ 50 + private int eliteFourUniquePokemonNumber = 0; // 0 ~ 2 + private boolean allowTrainerAlternateFormes; + private boolean swapTrainerMegaEvos; + private int additionalBossTrainerPokemon = 0; + private int additionalImportantTrainerPokemon = 0; + private int additionalRegularTrainerPokemon = 0; + private boolean randomizeHeldItemsForBossTrainerPokemon; + private boolean randomizeHeldItemsForImportantTrainerPokemon; + private boolean randomizeHeldItemsForRegularTrainerPokemon; + private boolean consumableItemsOnlyForTrainerPokemon; + private boolean sensibleItemsOnlyForTrainerPokemon; + private boolean highestLevelOnlyGetsItemsForTrainerPokemon; + private boolean doubleBattleMode; + private boolean shinyChance; + private boolean betterTrainerMovesets; + + public enum WildPokemonMod { + UNCHANGED, RANDOM, AREA_MAPPING, GLOBAL_MAPPING + } + + public enum WildPokemonRestrictionMod { + NONE, SIMILAR_STRENGTH, CATCH_EM_ALL, TYPE_THEME_AREAS + } + + private WildPokemonMod wildPokemonMod = WildPokemonMod.UNCHANGED; + private WildPokemonRestrictionMod wildPokemonRestrictionMod = WildPokemonRestrictionMod.NONE; + private boolean useTimeBasedEncounters; + private boolean blockWildLegendaries = true; + private boolean useMinimumCatchRate; + private int minimumCatchRateLevel = 1; + private boolean randomizeWildPokemonHeldItems; + private boolean banBadRandomWildPokemonHeldItems; + private boolean balanceShakingGrass; + private boolean wildLevelsModified; + private int wildLevelModifier = 0; + private boolean allowWildAltFormes; + + public enum StaticPokemonMod { + UNCHANGED, RANDOM_MATCHING, COMPLETELY_RANDOM, SIMILAR_STRENGTH + } + + private StaticPokemonMod staticPokemonMod = StaticPokemonMod.UNCHANGED; + + private boolean allowStaticAltFormes; + private boolean swapStaticMegaEvos; + private boolean staticLevelModified; + private int staticLevelModifier = 0; // -50 ~ 50 + private boolean correctStaticMusic; + + public enum TotemPokemonMod { + UNCHANGED, RANDOM, SIMILAR_STRENGTH + } + + public enum AllyPokemonMod { + UNCHANGED, RANDOM, SIMILAR_STRENGTH + } + + public enum AuraMod { + UNCHANGED, RANDOM, SAME_STRENGTH + } + + private TotemPokemonMod totemPokemonMod = TotemPokemonMod.UNCHANGED; + private AllyPokemonMod allyPokemonMod = AllyPokemonMod.UNCHANGED; + private AuraMod auraMod = AuraMod.UNCHANGED; + private boolean randomizeTotemHeldItems; + private boolean totemLevelsModified; + private int totemLevelModifier = 0; + private boolean allowTotemAltFormes; + + public enum TMsMod { + UNCHANGED, RANDOM + } + + private TMsMod tmsMod = TMsMod.UNCHANGED; + private boolean tmLevelUpMoveSanity; + private boolean keepFieldMoveTMs; + private boolean fullHMCompat; + private boolean tmsForceGoodDamaging; + private int tmsGoodDamagingPercent = 0; + private boolean blockBrokenTMMoves; + private boolean tmsFollowEvolutions; + + public enum TMsHMsCompatibilityMod { + UNCHANGED, RANDOM_PREFER_TYPE, COMPLETELY_RANDOM, FULL + } + + private TMsHMsCompatibilityMod tmsHmsCompatibilityMod = TMsHMsCompatibilityMod.UNCHANGED; + + public enum MoveTutorMovesMod { + UNCHANGED, RANDOM + } + + private MoveTutorMovesMod moveTutorMovesMod = MoveTutorMovesMod.UNCHANGED; + private boolean tutorLevelUpMoveSanity; + private boolean keepFieldMoveTutors; + private boolean tutorsForceGoodDamaging; + private int tutorsGoodDamagingPercent = 0; + private boolean blockBrokenTutorMoves; + private boolean tutorFollowEvolutions; + + public enum MoveTutorsCompatibilityMod { + UNCHANGED, RANDOM_PREFER_TYPE, COMPLETELY_RANDOM, FULL + } + + private MoveTutorsCompatibilityMod moveTutorsCompatibilityMod = MoveTutorsCompatibilityMod.UNCHANGED; + + public enum InGameTradesMod { + UNCHANGED, RANDOMIZE_GIVEN, RANDOMIZE_GIVEN_AND_REQUESTED + } + + private InGameTradesMod inGameTradesMod = InGameTradesMod.UNCHANGED; + private boolean randomizeInGameTradesNicknames; + private boolean randomizeInGameTradesOTs; + private boolean randomizeInGameTradesIVs; + private boolean randomizeInGameTradesItems; + + public enum FieldItemsMod { + UNCHANGED, SHUFFLE, RANDOM, RANDOM_EVEN + } + + private FieldItemsMod fieldItemsMod = FieldItemsMod.UNCHANGED; + private boolean banBadRandomFieldItems; + + public enum ShopItemsMod { + UNCHANGED, SHUFFLE, RANDOM + } + + private ShopItemsMod shopItemsMod = ShopItemsMod.UNCHANGED; + private boolean banBadRandomShopItems; + private boolean banRegularShopItems; + private boolean banOPShopItems; + private boolean balanceShopPrices; + private boolean guaranteeEvolutionItems; + private boolean guaranteeXItems; + + public enum PickupItemsMod { + UNCHANGED, RANDOM + } + + private PickupItemsMod pickupItemsMod = PickupItemsMod.UNCHANGED; + private boolean banBadRandomPickupItems; + + // to and from strings etc + public void write(FileOutputStream out) throws IOException { + byte[] settings = toString().getBytes("UTF-8"); + ByteBuffer buf = ByteBuffer.allocate(settings.length + 8); + buf.putInt(VERSION); + buf.putInt(settings.length); + buf.put(settings); + out.write(buf.array()); + } + + public static Settings read(FileInputStream in) throws IOException, UnsupportedOperationException { + byte[] versionBytes = new byte[4]; + byte[] lengthBytes = new byte[4]; + int nread = in.read(versionBytes); + if (nread < 4) { + throw new UnsupportedOperationException("Error reading version number from settings string."); + } + int version = ByteBuffer.wrap(versionBytes).getInt(); + if (((version >> 24) & 0xFF) > 0 && ((version >> 24) & 0xFF) <= 172) { + throw new UnsupportedOperationException("The settings file is too old to update. Please download v4.0.2 of the randomizer (or earlier) to update it."); + } + if (version > VERSION) { + throw new UnsupportedOperationException("Cannot read settings from a newer version of the randomizer."); + } + nread = in.read(lengthBytes); + if (nread < 4) { + throw new UnsupportedOperationException("Error reading settings length from settings string."); + } + int length = ByteBuffer.wrap(lengthBytes).getInt(); + byte[] buffer = FileFunctions.readFullyIntoBuffer(in, length); + String settings = new String(buffer, "UTF-8"); + boolean oldUpdate = false; + + if (version < VERSION) { + oldUpdate = true; + settings = new SettingsUpdater().update(version, settings); + } + + Settings settingsObj = fromString(settings); + settingsObj.setUpdatedFromOldVersion(oldUpdate); + return settingsObj; + } + + @Override + public String toString() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + // 0: general options #1 + trainer/class names + out.write(makeByteSelected(changeImpossibleEvolutions, updateMoves, updateMovesLegacy, randomizeTrainerNames, + randomizeTrainerClassNames, makeEvolutionsEasier, removeTimeBasedEvolutions)); + + // 1: pokemon base stats & abilities + out.write(makeByteSelected(baseStatsFollowEvolutions, baseStatisticsMod == BaseStatisticsMod.RANDOM, + baseStatisticsMod == BaseStatisticsMod.SHUFFLE, baseStatisticsMod == BaseStatisticsMod.UNCHANGED, + standardizeEXPCurves, updateBaseStats, baseStatsFollowMegaEvolutions, assignEvoStatsRandomly)); + + // 2: pokemon types & more general options + out.write(makeByteSelected(typesMod == TypesMod.RANDOM_FOLLOW_EVOLUTIONS, + typesMod == TypesMod.COMPLETELY_RANDOM, typesMod == TypesMod.UNCHANGED, raceMode, blockBrokenMoves, + limitPokemon, typesFollowMegaEvolutions, dualTypeOnly)); + + // 3: v171: changed to the abilities byte + out.write(makeByteSelected(abilitiesMod == AbilitiesMod.UNCHANGED, abilitiesMod == AbilitiesMod.RANDOMIZE, + allowWonderGuard, abilitiesFollowEvolutions, banTrappingAbilities, banNegativeAbilities, banBadAbilities, + abilitiesFollowMegaEvolutions)); + + // 4: starter pokemon stuff + out.write(makeByteSelected(startersMod == StartersMod.CUSTOM, startersMod == StartersMod.COMPLETELY_RANDOM, + startersMod == StartersMod.UNCHANGED, startersMod == StartersMod.RANDOM_WITH_TWO_EVOLUTIONS, + randomizeStartersHeldItems, banBadRandomStarterHeldItems, allowStarterAltFormes)); + + // 5 - 10: dropdowns + write2ByteInt(out, customStarters[0] - 1); + write2ByteInt(out, customStarters[1] - 1); + write2ByteInt(out, customStarters[2] - 1); + + // 11 movesets + out.write(makeByteSelected(movesetsMod == MovesetsMod.COMPLETELY_RANDOM, + movesetsMod == MovesetsMod.RANDOM_PREFER_SAME_TYPE, movesetsMod == MovesetsMod.UNCHANGED, + movesetsMod == MovesetsMod.METRONOME_ONLY, startWithGuaranteedMoves, reorderDamagingMoves) + | ((guaranteedMoveCount - 2) << 6)); + + // 12 movesets good damaging + out.write((movesetsForceGoodDamaging ? 0x80 : 0) | movesetsGoodDamagingPercent); + + // 13 trainer pokemon + out.write(makeByteSelected(trainersMod == TrainersMod.UNCHANGED, + trainersMod == TrainersMod.RANDOM, + trainersMod == TrainersMod.DISTRIBUTED, + trainersMod == TrainersMod.MAINPLAYTHROUGH, + trainersMod == TrainersMod.TYPE_THEMED, + trainersMod == TrainersMod.TYPE_THEMED_ELITE4_GYMS)); + + // 14 trainer pokemon force evolutions + out.write((trainersForceFullyEvolved ? 0x80 : 0) | trainersForceFullyEvolvedLevel); + + // 15 wild pokemon + out.write(makeByteSelected(wildPokemonRestrictionMod == WildPokemonRestrictionMod.CATCH_EM_ALL, + wildPokemonMod == WildPokemonMod.AREA_MAPPING, + wildPokemonRestrictionMod == WildPokemonRestrictionMod.NONE, + wildPokemonRestrictionMod == WildPokemonRestrictionMod.TYPE_THEME_AREAS, + wildPokemonMod == WildPokemonMod.GLOBAL_MAPPING, wildPokemonMod == WildPokemonMod.RANDOM, + wildPokemonMod == WildPokemonMod.UNCHANGED, useTimeBasedEncounters)); + + // 16 wild pokemon 2 + out.write(makeByteSelected(useMinimumCatchRate, blockWildLegendaries, + wildPokemonRestrictionMod == WildPokemonRestrictionMod.SIMILAR_STRENGTH, randomizeWildPokemonHeldItems, + banBadRandomWildPokemonHeldItems, false, false, balanceShakingGrass)); + + // 17 static pokemon + out.write(makeByteSelected(staticPokemonMod == StaticPokemonMod.UNCHANGED, + staticPokemonMod == StaticPokemonMod.RANDOM_MATCHING, + staticPokemonMod == StaticPokemonMod.COMPLETELY_RANDOM, + staticPokemonMod == StaticPokemonMod.SIMILAR_STRENGTH, + limitMainGameLegendaries, limit600, allowStaticAltFormes, swapStaticMegaEvos)); + + // 18 tm randomization + out.write(makeByteSelected(tmsHmsCompatibilityMod == TMsHMsCompatibilityMod.COMPLETELY_RANDOM, + tmsHmsCompatibilityMod == TMsHMsCompatibilityMod.RANDOM_PREFER_TYPE, + tmsHmsCompatibilityMod == TMsHMsCompatibilityMod.UNCHANGED, tmsMod == TMsMod.RANDOM, + tmsMod == TMsMod.UNCHANGED, tmLevelUpMoveSanity, keepFieldMoveTMs, + tmsHmsCompatibilityMod == TMsHMsCompatibilityMod.FULL)); + + // 19 tms part 2 + out.write(makeByteSelected(fullHMCompat, tmsFollowEvolutions, tutorFollowEvolutions)); + + // 20 tms good damaging + out.write((tmsForceGoodDamaging ? 0x80 : 0) | tmsGoodDamagingPercent); + + // 21 move tutor randomization + out.write(makeByteSelected(moveTutorsCompatibilityMod == MoveTutorsCompatibilityMod.COMPLETELY_RANDOM, + moveTutorsCompatibilityMod == MoveTutorsCompatibilityMod.RANDOM_PREFER_TYPE, + moveTutorsCompatibilityMod == MoveTutorsCompatibilityMod.UNCHANGED, + moveTutorMovesMod == MoveTutorMovesMod.RANDOM, moveTutorMovesMod == MoveTutorMovesMod.UNCHANGED, + tutorLevelUpMoveSanity, keepFieldMoveTutors, + moveTutorsCompatibilityMod == MoveTutorsCompatibilityMod.FULL)); + + // 22 tutors good damaging + out.write((tutorsForceGoodDamaging ? 0x80 : 0) | tutorsGoodDamagingPercent); + + // 23 in game trades + out.write(makeByteSelected(inGameTradesMod == InGameTradesMod.RANDOMIZE_GIVEN_AND_REQUESTED, + inGameTradesMod == InGameTradesMod.RANDOMIZE_GIVEN, randomizeInGameTradesItems, + randomizeInGameTradesIVs, randomizeInGameTradesNicknames, randomizeInGameTradesOTs, + inGameTradesMod == InGameTradesMod.UNCHANGED)); + + // 24 field items + out.write(makeByteSelected(fieldItemsMod == FieldItemsMod.RANDOM, fieldItemsMod == FieldItemsMod.SHUFFLE, + fieldItemsMod == FieldItemsMod.UNCHANGED, banBadRandomFieldItems, fieldItemsMod == FieldItemsMod.RANDOM_EVEN)); + + // 25 move randomizers + // + static music + out.write(makeByteSelected(randomizeMovePowers, randomizeMoveAccuracies, randomizeMovePPs, randomizeMoveTypes, + randomizeMoveCategory, correctStaticMusic)); + + // 26 evolutions + out.write(makeByteSelected(evolutionsMod == EvolutionsMod.UNCHANGED, evolutionsMod == EvolutionsMod.RANDOM, + evosSimilarStrength, evosSameTyping, evosMaxThreeStages, evosForceChange, evosAllowAltFormes, + evolutionsMod == EvolutionsMod.RANDOM_EVERY_LEVEL)); + + // 27 pokemon trainer misc + out.write(makeByteSelected(trainersUsePokemonOfSimilarStrength, + rivalCarriesStarterThroughout, + trainersMatchTypingDistribution, + trainersBlockLegendaries, + trainersBlockEarlyWonderGuard, + swapTrainerMegaEvos, + shinyChance, + betterTrainerMovesets)); + + // 28 - 31: pokemon restrictions + try { + if (currentRestrictions != null) { + writeFullInt(out, currentRestrictions.toInt()); + } else { + writeFullInt(out, 0); + } + } catch (IOException e) { + e.printStackTrace(); // better than nothing + } + + // 32 - 35: misc tweaks + try { + writeFullInt(out, currentMiscTweaks); + } catch (IOException e) { + e.printStackTrace(); // better than nothing + } + + // 36 trainer pokemon level modifier + out.write((trainersLevelModified ? 0x80 : 0) | (trainersLevelModifier+50)); + + // 37 shop items + out.write(makeByteSelected(shopItemsMod == ShopItemsMod.RANDOM, shopItemsMod == ShopItemsMod.SHUFFLE, + shopItemsMod == ShopItemsMod.UNCHANGED, banBadRandomShopItems, banRegularShopItems, banOPShopItems, + balanceShopPrices, guaranteeEvolutionItems)); + + // 38 wild level modifier + out.write((wildLevelsModified ? 0x80 : 0) | (wildLevelModifier+50)); + + // 39 EXP curve mod, block broken moves, alt forme stuff + out.write(makeByteSelected( + expCurveMod == ExpCurveMod.LEGENDARIES, + expCurveMod == ExpCurveMod.STRONG_LEGENDARIES, + expCurveMod == ExpCurveMod.ALL, + blockBrokenMovesetMoves, + blockBrokenTMMoves, + blockBrokenTutorMoves, + allowTrainerAlternateFormes, + allowWildAltFormes)); + + // 40 Double Battle Mode, Additional Boss/Important Trainer Pokemon, Weigh Duplicate Abilities + out.write((doubleBattleMode ? 0x1 : 0) | + (additionalBossTrainerPokemon << 1) | + (additionalImportantTrainerPokemon << 4) | + (weighDuplicateAbilitiesTogether ? 0x80 : 0)); + + // 41 Additional Regular Trainer Pokemon, Aura modification, evolution moves, guarantee X items + out.write(additionalRegularTrainerPokemon | + ((auraMod == AuraMod.UNCHANGED) ? 0x8 : 0) | + ((auraMod == AuraMod.RANDOM) ? 0x10 : 0) | + ((auraMod == AuraMod.SAME_STRENGTH) ? 0x20 : 0) | + (evolutionMovesForAll ? 0x40 : 0) | + (guaranteeXItems ? 0x80 : 0)); + + // 42 Totem Pokemon settings + out.write(makeByteSelected( + totemPokemonMod == TotemPokemonMod.UNCHANGED, + totemPokemonMod == TotemPokemonMod.RANDOM, + totemPokemonMod == TotemPokemonMod.SIMILAR_STRENGTH, + allyPokemonMod == AllyPokemonMod.UNCHANGED, + allyPokemonMod == AllyPokemonMod.RANDOM, + allyPokemonMod == AllyPokemonMod.SIMILAR_STRENGTH, + randomizeTotemHeldItems, + allowTotemAltFormes)); + + // 43 Totem level modifier + out.write((totemLevelsModified ? 0x80 : 0) | (totemLevelModifier+50)); + + // 44 - 45: These two get a byte each for future proofing + out.write(updateBaseStatsToGeneration); + out.write(updateMovesToGeneration); + + // 46 Selected EXP curve + out.write(selectedEXPCurve.toByte()); + + // 47 Static level modifier + out.write((staticLevelModified ? 0x80 : 0) | (staticLevelModifier+50)); + + // 48 trainer pokemon held items / pokemon ensure two abilities + out.write(makeByteSelected(randomizeHeldItemsForBossTrainerPokemon, + randomizeHeldItemsForImportantTrainerPokemon, + randomizeHeldItemsForRegularTrainerPokemon, + consumableItemsOnlyForTrainerPokemon, + sensibleItemsOnlyForTrainerPokemon, + highestLevelOnlyGetsItemsForTrainerPokemon, + ensureTwoAbilities)); + + // 49 pickup item randomization + out.write(makeByteSelected(pickupItemsMod == PickupItemsMod.RANDOM, + pickupItemsMod == PickupItemsMod.UNCHANGED, banBadRandomPickupItems, + banIrregularAltFormes)); + + // 50 elite four unique pokemon (3 bits) + catch rate level (3 bits) + out.write(eliteFourUniquePokemonNumber | ((minimumCatchRateLevel - 1) << 3)); + + try { + byte[] romName = this.romName.getBytes("US-ASCII"); + out.write(romName.length); + out.write(romName); + } catch (IOException e) { + out.write(0); + } + + byte[] current = out.toByteArray(); + CRC32 checksum = new CRC32(); + checksum.update(current); + + try { + writeFullInt(out, (int) checksum.getValue()); + writeFullInt(out, FileFunctions.getFileChecksum(SysConstants.customNamesFile)); + } catch (IOException e) { + e.printStackTrace(); // better than nothing + } + + return Base64.getEncoder().encodeToString(out.toByteArray()); + } + + public static Settings fromString(String settingsString) throws UnsupportedEncodingException, IllegalArgumentException { + byte[] data = Base64.getDecoder().decode(settingsString); + checkChecksum(data); + + Settings settings = new Settings(); + + // Restore the actual controls + settings.setChangeImpossibleEvolutions(restoreState(data[0], 0)); + settings.setUpdateMoves(restoreState(data[0], 1)); + settings.setUpdateMovesLegacy(restoreState(data[0], 2)); + settings.setRandomizeTrainerNames(restoreState(data[0], 3)); + settings.setRandomizeTrainerClassNames(restoreState(data[0], 4)); + settings.setMakeEvolutionsEasier(restoreState(data[0], 5)); + settings.setRemoveTimeBasedEvolutions(restoreState(data[0], 6)); + + settings.setBaseStatisticsMod(restoreEnum(BaseStatisticsMod.class, data[1], 3, // UNCHANGED + 2, // SHUFFLE + 1 // RANDOM + )); + settings.setStandardizeEXPCurves(restoreState(data[1], 4)); + settings.setBaseStatsFollowEvolutions(restoreState(data[1], 0)); + settings.setUpdateBaseStats(restoreState(data[1], 5)); + settings.setBaseStatsFollowMegaEvolutions(restoreState(data[1],6)); + settings.setAssignEvoStatsRandomly(restoreState(data[1],7)); + + settings.setTypesMod(restoreEnum(TypesMod.class, data[2], 2, // UNCHANGED + 0, // RANDOM_FOLLOW_EVOLUTIONS + 1 // COMPLETELY_RANDOM + )); + settings.setRaceMode(restoreState(data[2], 3)); + settings.setBlockBrokenMoves(restoreState(data[2], 4)); + settings.setLimitPokemon(restoreState(data[2], 5)); + settings.setTypesFollowMegaEvolutions(restoreState(data[2],6)); + settings.setDualTypeOnly(restoreState(data[2], 7)); + settings.setAbilitiesMod(restoreEnum(AbilitiesMod.class, data[3], 0, // UNCHANGED + 1 // RANDOMIZE + )); + settings.setAllowWonderGuard(restoreState(data[3], 2)); + settings.setAbilitiesFollowEvolutions(restoreState(data[3], 3)); + settings.setBanTrappingAbilities(restoreState(data[3], 4)); + settings.setBanNegativeAbilities(restoreState(data[3], 5)); + settings.setBanBadAbilities(restoreState(data[3], 6)); + settings.setAbilitiesFollowMegaEvolutions(restoreState(data[3],7)); + + settings.setStartersMod(restoreEnum(StartersMod.class, data[4], 2, // UNCHANGED + 0, // CUSTOM + 1, // COMPLETELY_RANDOM + 3 // RANDOM_WITH_TWO_EVOLUTIONS + )); + settings.setRandomizeStartersHeldItems(restoreState(data[4], 4)); + settings.setBanBadRandomStarterHeldItems(restoreState(data[4], 5)); + settings.setAllowStarterAltFormes(restoreState(data[4],6)); + + settings.setCustomStarters(new int[] { FileFunctions.read2ByteInt(data, 5) + 1, + FileFunctions.read2ByteInt(data, 7) + 1, FileFunctions.read2ByteInt(data, 9) + 1 }); + + settings.setMovesetsMod(restoreEnum(MovesetsMod.class, data[11], 2, // UNCHANGED + 1, // RANDOM_PREFER_SAME_TYPE + 0, // COMPLETELY_RANDOM + 3 // METRONOME_ONLY + )); + settings.setStartWithGuaranteedMoves(restoreState(data[11], 4)); + settings.setReorderDamagingMoves(restoreState(data[11], 5)); + settings.setGuaranteedMoveCount(((data[11] & 0xC0) >> 6) + 2); + + settings.setMovesetsForceGoodDamaging(restoreState(data[12], 7)); + settings.setMovesetsGoodDamagingPercent(data[12] & 0x7F); + + // changed 160 + settings.setTrainersMod(restoreEnum(TrainersMod.class, data[13], 0, // UNCHANGED + 1, // RANDOM + 2, // DISTRIBUTED + 3, // MAINPLAYTHROUGH + 4, // TYPE_THEMED + 5 // TYPE_THEMED_ELITE4_GYMS + )); + + settings.setTrainersForceFullyEvolved(restoreState(data[14], 7)); + settings.setTrainersForceFullyEvolvedLevel(data[14] & 0x7F); + + settings.setWildPokemonMod(restoreEnum(WildPokemonMod.class, data[15], 6, // UNCHANGED + 5, // RANDOM + 1, // AREA_MAPPING + 4 // GLOBAL_MAPPING + )); + settings.setWildPokemonRestrictionMod(getEnum(WildPokemonRestrictionMod.class, restoreState(data[15], 2), // NONE + restoreState(data[16], 2), // SIMILAR_STRENGTH + restoreState(data[15], 0), // CATCH_EM_ALL + restoreState(data[15], 3) // TYPE_THEME_AREAS + )); + settings.setUseTimeBasedEncounters(restoreState(data[15], 7)); + + settings.setUseMinimumCatchRate(restoreState(data[16], 0)); + settings.setBlockWildLegendaries(restoreState(data[16], 1)); + settings.setRandomizeWildPokemonHeldItems(restoreState(data[16], 3)); + settings.setBanBadRandomWildPokemonHeldItems(restoreState(data[16], 4)); + settings.setBalanceShakingGrass(restoreState(data[16], 7)); + + settings.setStaticPokemonMod(restoreEnum(StaticPokemonMod.class, data[17], 0, // UNCHANGED + 1, // RANDOM_MATCHING + 2, // COMPLETELY_RANDOM + 3 // SIMILAR_STRENGTH + )); + + settings.setLimitMainGameLegendaries(restoreState(data[17], 4)); + settings.setLimit600(restoreState(data[17], 5)); + settings.setAllowStaticAltFormes(restoreState(data[17], 6)); + settings.setSwapStaticMegaEvos(restoreState(data[17], 7)); + + settings.setTmsMod(restoreEnum(TMsMod.class, data[18], 4, // UNCHANGED + 3 // RANDOM + )); + settings.setTmsHmsCompatibilityMod(restoreEnum(TMsHMsCompatibilityMod.class, data[18], 2, // UNCHANGED + 1, // RANDOM_PREFER_TYPE + 0, // COMPLETELY_RANDOM + 7 // FULL + )); + settings.setTmLevelUpMoveSanity(restoreState(data[18], 5)); + settings.setKeepFieldMoveTMs(restoreState(data[18], 6)); + + settings.setFullHMCompat(restoreState(data[19], 0)); + settings.setTmsFollowEvolutions(restoreState(data[19], 1)); + settings.setTutorFollowEvolutions(restoreState(data[19], 2)); + + settings.setTmsForceGoodDamaging(restoreState(data[20], 7)); + settings.setTmsGoodDamagingPercent(data[20] & 0x7F); + + settings.setMoveTutorMovesMod(restoreEnum(MoveTutorMovesMod.class, data[21], 4, // UNCHANGED + 3 // RANDOM + )); + settings.setMoveTutorsCompatibilityMod(restoreEnum(MoveTutorsCompatibilityMod.class, data[21], 2, // UNCHANGED + 1, // RANDOM_PREFER_TYPE + 0, // COMPLETELY_RANDOM + 7 // FULL + )); + settings.setTutorLevelUpMoveSanity(restoreState(data[21], 5)); + settings.setKeepFieldMoveTutors(restoreState(data[21], 6)); + + settings.setTutorsForceGoodDamaging(restoreState(data[22], 7)); + settings.setTutorsGoodDamagingPercent(data[22] & 0x7F); + + // new 150 + settings.setInGameTradesMod(restoreEnum(InGameTradesMod.class, data[23], 6, // UNCHANGED + 1, // RANDOMIZE_GIVEN + 0 // RANDOMIZE_GIVEN_AND_REQUESTED + )); + settings.setRandomizeInGameTradesItems(restoreState(data[23], 2)); + settings.setRandomizeInGameTradesIVs(restoreState(data[23], 3)); + settings.setRandomizeInGameTradesNicknames(restoreState(data[23], 4)); + settings.setRandomizeInGameTradesOTs(restoreState(data[23], 5)); + + settings.setFieldItemsMod(restoreEnum(FieldItemsMod.class, data[24], + 2, // UNCHANGED + 1, // SHUFFLE + 0, // RANDOM + 4 // RANDOM_EVEN + )); + settings.setBanBadRandomFieldItems(restoreState(data[24], 3)); + + // new 170 + settings.setRandomizeMovePowers(restoreState(data[25], 0)); + settings.setRandomizeMoveAccuracies(restoreState(data[25], 1)); + settings.setRandomizeMovePPs(restoreState(data[25], 2)); + settings.setRandomizeMoveTypes(restoreState(data[25], 3)); + settings.setRandomizeMoveCategory(restoreState(data[25], 4)); + settings.setCorrectStaticMusic(restoreState(data[25], 5)); + + settings.setEvolutionsMod(restoreEnum(EvolutionsMod.class, data[26], 0, // UNCHANGED + 1, // RANDOM + 7 // RANDOM_EVERY_LEVEL + )); + settings.setEvosSimilarStrength(restoreState(data[26], 2)); + settings.setEvosSameTyping(restoreState(data[26], 3)); + settings.setEvosMaxThreeStages(restoreState(data[26], 4)); + settings.setEvosForceChange(restoreState(data[26], 5)); + settings.setEvosAllowAltFormes(restoreState(data[26],6)); + + // new pokemon trainer misc + settings.setTrainersUsePokemonOfSimilarStrength(restoreState(data[27], 0)); + settings.setRivalCarriesStarterThroughout(restoreState(data[27], 1)); + settings.setTrainersMatchTypingDistribution(restoreState(data[27], 2)); + settings.setTrainersBlockLegendaries(restoreState(data[27], 3)); + settings.setTrainersBlockEarlyWonderGuard(restoreState(data[27], 4)); + settings.setSwapTrainerMegaEvos(restoreState(data[27], 5)); + settings.setShinyChance(restoreState(data[27], 6)); + settings.setBetterTrainerMovesets(restoreState(data[27], 7)); + + // gen restrictions + int genLimit = FileFunctions.readFullIntBigEndian(data, 28); + GenRestrictions restrictions = null; + if (genLimit != 0) { + restrictions = new GenRestrictions(genLimit); + } + settings.setCurrentRestrictions(restrictions); + + int codeTweaks = FileFunctions.readFullIntBigEndian(data, 32); + + settings.setCurrentMiscTweaks(codeTweaks); + + settings.setTrainersLevelModified(restoreState(data[36], 7)); + settings.setTrainersLevelModifier((data[36] & 0x7F) - 50); + //settings.setTrainersLevelModifier((data[36] & 0x7F)); + settings.setShopItemsMod(restoreEnum(ShopItemsMod.class,data[37], + 2, + 1, + 0)); + settings.setBanBadRandomShopItems(restoreState(data[37],3)); + settings.setBanRegularShopItems(restoreState(data[37],4)); + settings.setBanOPShopItems(restoreState(data[37],5)); + settings.setBalanceShopPrices(restoreState(data[37],6)); + settings.setGuaranteeEvolutionItems(restoreState(data[37],7)); + + settings.setWildLevelsModified(restoreState(data[38],7)); + settings.setWildLevelModifier((data[38] & 0x7F) - 50); + + settings.setExpCurveMod(restoreEnum(ExpCurveMod.class,data[39],0,1,2)); + + settings.setBlockBrokenMovesetMoves(restoreState(data[39],3)); + settings.setBlockBrokenTMMoves(restoreState(data[39],4)); + settings.setBlockBrokenTutorMoves(restoreState(data[39],5)); + + settings.setAllowTrainerAlternateFormes(restoreState(data[39],6)); + settings.setAllowWildAltFormes(restoreState(data[39],7)); + + settings.setDoubleBattleMode(restoreState(data[40], 0)); + settings.setAdditionalBossTrainerPokemon((data[40] & 0xE) >> 1); + settings.setAdditionalImportantTrainerPokemon((data[40] & 0x70) >> 4); + settings.setWeighDuplicateAbilitiesTogether(restoreState(data[40], 7)); + + settings.setAdditionalRegularTrainerPokemon((data[41] & 0x7)); + settings.setAuraMod(restoreEnum(AuraMod.class,data[41],3,4,5)); + settings.setEvolutionMovesForAll(restoreState(data[41],6)); + settings.setGuaranteeXItems(restoreState(data[41],7)); + + settings.setTotemPokemonMod(restoreEnum(TotemPokemonMod.class,data[42],0,1,2)); + settings.setAllyPokemonMod(restoreEnum(AllyPokemonMod.class,data[42],3,4,5)); + settings.setRandomizeTotemHeldItems(restoreState(data[42],6)); + settings.setAllowTotemAltFormes(restoreState(data[42],7)); + settings.setTotemLevelsModified(restoreState(data[43],7)); + settings.setTotemLevelModifier((data[43] & 0x7F) - 50); + + settings.setUpdateBaseStatsToGeneration(data[44]); + + settings.setUpdateMovesToGeneration(data[45]); + + settings.setSelectedEXPCurve(ExpCurve.fromByte(data[46])); + + settings.setStaticLevelModified(restoreState(data[47],7)); + settings.setStaticLevelModifier((data[47] & 0x7F) - 50); + + settings.setRandomizeHeldItemsForBossTrainerPokemon(restoreState(data[48], 0)); + settings.setRandomizeHeldItemsForImportantTrainerPokemon(restoreState(data[48], 1)); + settings.setRandomizeHeldItemsForRegularTrainerPokemon(restoreState(data[48], 2)); + settings.setConsumableItemsOnlyForTrainers(restoreState(data[48], 3)); + settings.setSensibleItemsOnlyForTrainers(restoreState(data[48], 4)); + settings.setHighestLevelGetsItemsForTrainers(restoreState(data[48], 5)); + settings.setEnsureTwoAbilities(restoreState(data[48], 6)); + + settings.setPickupItemsMod(restoreEnum(PickupItemsMod.class, data[49], + 1, // UNCHANGED + 0)); // RANDOMIZE + settings.setBanBadRandomPickupItems(restoreState(data[49], 2)); + settings.setBanIrregularAltFormes(restoreState(data[49], 3)); + + settings.setEliteFourUniquePokemonNumber(data[50] & 0x7); + settings.setMinimumCatchRateLevel(((data[50] & 0x38) >> 3) + 1); + + int romNameLength = data[LENGTH_OF_SETTINGS_DATA] & 0xFF; + String romName = new String(data, LENGTH_OF_SETTINGS_DATA + 1, romNameLength, "US-ASCII"); + settings.setRomName(romName); + + return settings; + } + + public static class TweakForROMFeedback { + private boolean changedStarter; + private boolean removedCodeTweaks; + + public boolean isChangedStarter() { + return changedStarter; + } + + public TweakForROMFeedback setChangedStarter(boolean changedStarter) { + this.changedStarter = changedStarter; + return this; + } + + public boolean isRemovedCodeTweaks() { + return removedCodeTweaks; + } + + public TweakForROMFeedback setRemovedCodeTweaks(boolean removedCodeTweaks) { + this.removedCodeTweaks = removedCodeTweaks; + return this; + } + } + + public TweakForROMFeedback tweakForRom(RomHandler rh) { + + TweakForROMFeedback feedback = new TweakForROMFeedback(); + + // move update check + if (this.isUpdateMovesLegacy() && rh instanceof Gen5RomHandler) { + // don't actually update moves + this.setUpdateMovesLegacy(false); + this.setUpdateMoves(false); + } + + // starters + List romPokemon; + if (rh.hasStarterAltFormes()) { + romPokemon = rh.getPokemonInclFormes(); + } else { + romPokemon = rh.getPokemon(); + } + List romStarters = rh.getStarters(); + for (int starter = 0; starter < 3; starter++) { + if (this.customStarters[starter] < 0 || this.customStarters[starter] >= romPokemon.size()) { + // invalid starter for this game + feedback.setChangedStarter(true); + if (starter >= romStarters.size()) { + this.customStarters[starter] = 1; + } else { + this.customStarters[starter] = romPokemon.indexOf(romStarters.get(starter)); + } + } + } + + // gen restrictions + if (rh instanceof Gen1RomHandler || (rh instanceof Gen3RomHandler && !rh.isRomValid())) { + this.currentRestrictions = null; + this.setLimitPokemon(false); + } else if (this.currentRestrictions != null) { + this.currentRestrictions.limitToGen(rh.generationOfPokemon()); + } + + // gen 5 exclusive stuff + if (rh.generationOfPokemon() != 5) { + if (trainersMod == TrainersMod.MAINPLAYTHROUGH) { + trainersMod = TrainersMod.RANDOM; + } + } + + // misc tweaks + int oldMiscTweaks = this.currentMiscTweaks; + this.currentMiscTweaks &= rh.miscTweaksAvailable(); + + if (oldMiscTweaks != this.currentMiscTweaks) { + feedback.setRemovedCodeTweaks(true); + } + + if (rh.abilitiesPerPokemon() == 0) { + this.setAbilitiesMod(AbilitiesMod.UNCHANGED); + this.setAllowWonderGuard(false); + } + + if (!rh.supportsStarterHeldItems()) { + // starter held items don't exist + this.setRandomizeStartersHeldItems(false); + this.setBanBadRandomStarterHeldItems(false); + } + + if (!rh.supportsFourStartingMoves()) { + this.setStartWithGuaranteedMoves(false); + } + + if (rh instanceof Gen1RomHandler || rh instanceof Gen2RomHandler) { + this.setTrainersBlockEarlyWonderGuard(false); + } + + if (!rh.hasTimeBasedEncounters()) { + this.setUseTimeBasedEncounters(false); + } + + if (rh instanceof Gen1RomHandler) { + this.setRandomizeWildPokemonHeldItems(false); + this.setBanBadRandomWildPokemonHeldItems(false); + } + + if (!rh.canChangeStaticPokemon()) { + this.setStaticPokemonMod(StaticPokemonMod.UNCHANGED); + } + + if (!rh.hasMoveTutors()) { + this.setMoveTutorMovesMod(MoveTutorMovesMod.UNCHANGED); + this.setMoveTutorsCompatibilityMod(MoveTutorsCompatibilityMod.UNCHANGED); + this.setTutorLevelUpMoveSanity(false); + this.setKeepFieldMoveTutors(false); + } + + if (rh instanceof Gen1RomHandler) { + // missing some ingame trade fields + this.setRandomizeInGameTradesItems(false); + this.setRandomizeInGameTradesIVs(false); + this.setRandomizeInGameTradesOTs(false); + } + + if (!rh.hasPhysicalSpecialSplit()) { + this.setRandomizeMoveCategory(false); + } + + if (!rh.hasShopRandomization()) { + this.setShopItemsMod(ShopItemsMod.UNCHANGED); + } + + // done + return feedback; + } + + // getters and setters + + public CustomNamesSet getCustomNames() { + return customNames; + } + + public Settings setCustomNames(CustomNamesSet customNames) { + this.customNames = customNames; + return this; + } + + public String getRomName() { + return romName; + } + + public void setRomName(String romName) { + this.romName = romName; + } + + public boolean isUpdatedFromOldVersion() { + return updatedFromOldVersion; + } + + private void setUpdatedFromOldVersion(boolean updatedFromOldVersion) { + this.updatedFromOldVersion = updatedFromOldVersion; + } + + public GenRestrictions getCurrentRestrictions() { + return currentRestrictions; + } + + public void setCurrentRestrictions(GenRestrictions currentRestrictions) { + this.currentRestrictions = currentRestrictions; + } + + public int getCurrentMiscTweaks() { + return currentMiscTweaks; + } + + public void setCurrentMiscTweaks(int currentMiscTweaks) { + this.currentMiscTweaks = currentMiscTweaks; + } + + public boolean isUpdateMoves() { + return updateMoves; + } + + public void setUpdateMoves(boolean updateMoves) { + this.updateMoves = updateMoves; + } + + public boolean isUpdateMovesLegacy() { + return updateMovesLegacy; + } + + public void setUpdateMovesLegacy(boolean updateMovesLegacy) { + this.updateMovesLegacy = updateMovesLegacy; + } + + public int getUpdateMovesToGeneration() { + return updateMovesToGeneration; + } + + public void setUpdateMovesToGeneration(int generation) { + updateMovesToGeneration = generation; + } + + public boolean isChangeImpossibleEvolutions() { + return changeImpossibleEvolutions; + } + + public boolean isDualTypeOnly(){ + return dualTypeOnly; + } + + public void setDualTypeOnly(boolean dualTypeOnly){ + this.dualTypeOnly = dualTypeOnly; + } + + public void setChangeImpossibleEvolutions(boolean changeImpossibleEvolutions) { + this.changeImpossibleEvolutions = changeImpossibleEvolutions; + } + + public boolean isMakeEvolutionsEasier() { + return makeEvolutionsEasier; + } + + public void setMakeEvolutionsEasier(boolean makeEvolutionsEasier) { + this.makeEvolutionsEasier = makeEvolutionsEasier; + } + + public boolean isRemoveTimeBasedEvolutions() { + return removeTimeBasedEvolutions; + } + + public void setRemoveTimeBasedEvolutions(boolean removeTimeBasedEvolutions) { + this.removeTimeBasedEvolutions = removeTimeBasedEvolutions; + } + + public boolean isEvosAllowAltFormes() { + return evosAllowAltFormes; + } + + public void setEvosAllowAltFormes(boolean evosAllowAltFormes) { + this.evosAllowAltFormes = evosAllowAltFormes; + } + + public boolean isRaceMode() { + return raceMode; + } + + public void setRaceMode(boolean raceMode) { + this.raceMode = raceMode; + } + + public boolean isBanIrregularAltFormes() { + return banIrregularAltFormes; + } + + public void setBanIrregularAltFormes(boolean banIrregularAltFormes) { + this.banIrregularAltFormes = banIrregularAltFormes; + } + + public boolean doBlockBrokenMoves() { + return blockBrokenMoves; + } + + public void setBlockBrokenMoves(boolean blockBrokenMoves) { + blockBrokenMovesetMoves = blockBrokenMoves; + blockBrokenTMMoves = blockBrokenMoves; + blockBrokenTutorMoves = blockBrokenMoves; + } + + public boolean isLimitPokemon() { + return limitPokemon; + } + + public void setLimitPokemon(boolean limitPokemon) { + this.limitPokemon = limitPokemon; + } + + public BaseStatisticsMod getBaseStatisticsMod() { + return baseStatisticsMod; + } + + public void setBaseStatisticsMod(boolean... bools) { + setBaseStatisticsMod(getEnum(BaseStatisticsMod.class, bools)); + } + + private void setBaseStatisticsMod(BaseStatisticsMod baseStatisticsMod) { + this.baseStatisticsMod = baseStatisticsMod; + } + + public boolean isBaseStatsFollowEvolutions() { + return baseStatsFollowEvolutions; + } + + public void setBaseStatsFollowEvolutions(boolean baseStatsFollowEvolutions) { + this.baseStatsFollowEvolutions = baseStatsFollowEvolutions; + } + + public boolean isBaseStatsFollowMegaEvolutions() { + return baseStatsFollowMegaEvolutions; + } + + public void setBaseStatsFollowMegaEvolutions(boolean baseStatsFollowMegaEvolutions) { + this.baseStatsFollowMegaEvolutions = baseStatsFollowMegaEvolutions; + } + + public boolean isAssignEvoStatsRandomly() { + return assignEvoStatsRandomly; + } + + public void setAssignEvoStatsRandomly(boolean assignEvoStatsRandomly) { + this.assignEvoStatsRandomly = assignEvoStatsRandomly; + } + + + public boolean isStandardizeEXPCurves() { + return standardizeEXPCurves; + } + + public void setStandardizeEXPCurves(boolean standardizeEXPCurves) { + this.standardizeEXPCurves = standardizeEXPCurves; + } + + public ExpCurveMod getExpCurveMod() { + return expCurveMod; + } + + public void setExpCurveMod(boolean... bools) { + setExpCurveMod(getEnum(ExpCurveMod.class, bools)); + } + + private void setExpCurveMod(ExpCurveMod expCurveMod) { + this.expCurveMod = expCurveMod; + } + + public ExpCurve getSelectedEXPCurve() { + return selectedEXPCurve; + } + + public void setSelectedEXPCurve(ExpCurve expCurve) { + this.selectedEXPCurve = expCurve; + } + + public boolean isUpdateBaseStats() { + return updateBaseStats; + } + + public void setUpdateBaseStats(boolean updateBaseStats) { + this.updateBaseStats = updateBaseStats; + } + + public int getUpdateBaseStatsToGeneration() { + return updateBaseStatsToGeneration; + } + + public void setUpdateBaseStatsToGeneration(int generation) { + this.updateBaseStatsToGeneration = generation; + } + + public AbilitiesMod getAbilitiesMod() { + return abilitiesMod; + } + + public void setAbilitiesMod(boolean... bools) { + setAbilitiesMod(getEnum(AbilitiesMod.class, bools)); + } + + private void setAbilitiesMod(AbilitiesMod abilitiesMod) { + this.abilitiesMod = abilitiesMod; + } + + public boolean isAllowWonderGuard() { + return allowWonderGuard; + } + + public void setAllowWonderGuard(boolean allowWonderGuard) { + this.allowWonderGuard = allowWonderGuard; + } + + public boolean isAbilitiesFollowEvolutions() { + return abilitiesFollowEvolutions; + } + + public void setAbilitiesFollowEvolutions(boolean abilitiesFollowEvolutions) { + this.abilitiesFollowEvolutions = abilitiesFollowEvolutions; + } + + public boolean isAbilitiesFollowMegaEvolutions() { + return abilitiesFollowMegaEvolutions; + } + + public void setAbilitiesFollowMegaEvolutions(boolean abilitiesFollowMegaEvolutions) { + this.abilitiesFollowMegaEvolutions = abilitiesFollowMegaEvolutions; + } + + public boolean isBanTrappingAbilities() { + return banTrappingAbilities; + } + + public void setBanTrappingAbilities(boolean banTrappingAbilities) { + this.banTrappingAbilities = banTrappingAbilities; + } + + public boolean isBanNegativeAbilities() { + return banNegativeAbilities; + } + + public void setBanNegativeAbilities(boolean banNegativeAbilities) { + this.banNegativeAbilities = banNegativeAbilities; + } + + public boolean isBanBadAbilities() { + return banBadAbilities; + } + + public void setBanBadAbilities(boolean banBadAbilities) { + this.banBadAbilities = banBadAbilities; + } + + public boolean isWeighDuplicateAbilitiesTogether() { + return weighDuplicateAbilitiesTogether; + } + + public void setWeighDuplicateAbilitiesTogether(boolean weighDuplicateAbilitiesTogether) { + this.weighDuplicateAbilitiesTogether = weighDuplicateAbilitiesTogether; + } + + public boolean isEnsureTwoAbilities() { return ensureTwoAbilities; } + + public void setEnsureTwoAbilities(boolean ensureTwoAbilities) { + this.ensureTwoAbilities = ensureTwoAbilities; + } + + public StartersMod getStartersMod() { + return startersMod; + } + + public void setStartersMod(boolean... bools) { + setStartersMod(getEnum(StartersMod.class, bools)); + } + + private void setStartersMod(StartersMod startersMod) { + this.startersMod = startersMod; + } + + public int[] getCustomStarters() { + return customStarters; + } + + public void setCustomStarters(int[] customStarters) { + this.customStarters = customStarters; + } + + public boolean isRandomizeStartersHeldItems() { + return randomizeStartersHeldItems; + } + + public void setRandomizeStartersHeldItems(boolean randomizeStartersHeldItems) { + this.randomizeStartersHeldItems = randomizeStartersHeldItems; + } + + public boolean isBanBadRandomStarterHeldItems() { + return banBadRandomStarterHeldItems; + } + + public void setBanBadRandomStarterHeldItems(boolean banBadRandomStarterHeldItems) { + this.banBadRandomStarterHeldItems = banBadRandomStarterHeldItems; + } + + public boolean isAllowStarterAltFormes() { + return allowStarterAltFormes; + } + + public void setAllowStarterAltFormes(boolean allowStarterAltFormes) { + this.allowStarterAltFormes = allowStarterAltFormes; + } + + + public TypesMod getTypesMod() { + return typesMod; + } + + public void setTypesMod(boolean... bools) { + setTypesMod(getEnum(TypesMod.class, bools)); + } + + private void setTypesMod(TypesMod typesMod) { + this.typesMod = typesMod; + } + + public boolean isTypesFollowMegaEvolutions() { + return typesFollowMegaEvolutions; + } + + public void setTypesFollowMegaEvolutions(boolean typesFollowMegaEvolutions) { + this.typesFollowMegaEvolutions = typesFollowMegaEvolutions; + } + + public EvolutionsMod getEvolutionsMod() { + return evolutionsMod; + } + + public void setEvolutionsMod(boolean... bools) { + setEvolutionsMod(getEnum(EvolutionsMod.class, bools)); + } + + private void setEvolutionsMod(EvolutionsMod evolutionsMod) { + this.evolutionsMod = evolutionsMod; + } + + public boolean isEvosSimilarStrength() { + return evosSimilarStrength; + } + + public void setEvosSimilarStrength(boolean evosSimilarStrength) { + this.evosSimilarStrength = evosSimilarStrength; + } + + public boolean isEvosSameTyping() { + return evosSameTyping; + } + + public void setEvosSameTyping(boolean evosSameTyping) { + this.evosSameTyping = evosSameTyping; + } + + public boolean isEvosMaxThreeStages() { + return evosMaxThreeStages; + } + + public void setEvosMaxThreeStages(boolean evosMaxThreeStages) { + this.evosMaxThreeStages = evosMaxThreeStages; + } + + public boolean isEvosForceChange() { + return evosForceChange; + } + + public void setEvosForceChange(boolean evosForceChange) { + this.evosForceChange = evosForceChange; + } + + public boolean isRandomizeMovePowers() { + return randomizeMovePowers; + } + + public void setRandomizeMovePowers(boolean randomizeMovePowers) { + this.randomizeMovePowers = randomizeMovePowers; + } + + public boolean isRandomizeMoveAccuracies() { + return randomizeMoveAccuracies; + } + + public void setRandomizeMoveAccuracies(boolean randomizeMoveAccuracies) { + this.randomizeMoveAccuracies = randomizeMoveAccuracies; + } + + public boolean isRandomizeMovePPs() { + return randomizeMovePPs; + } + + public void setRandomizeMovePPs(boolean randomizeMovePPs) { + this.randomizeMovePPs = randomizeMovePPs; + } + + public boolean isRandomizeMoveTypes() { + return randomizeMoveTypes; + } + + public void setRandomizeMoveTypes(boolean randomizeMoveTypes) { + this.randomizeMoveTypes = randomizeMoveTypes; + } + + public boolean isRandomizeMoveCategory() { + return randomizeMoveCategory; + } + + public void setRandomizeMoveCategory(boolean randomizeMoveCategory) { + this.randomizeMoveCategory = randomizeMoveCategory; + } + + public MovesetsMod getMovesetsMod() { + return movesetsMod; + } + + public void setMovesetsMod(boolean... bools) { + setMovesetsMod(getEnum(MovesetsMod.class, bools)); + } + + private void setMovesetsMod(MovesetsMod movesetsMod) { + this.movesetsMod = movesetsMod; + } + + public boolean isStartWithGuaranteedMoves() { + return startWithGuaranteedMoves; + } + + public void setStartWithGuaranteedMoves(boolean startWithGuaranteedMoves) { + this.startWithGuaranteedMoves = startWithGuaranteedMoves; + } + + public int getGuaranteedMoveCount() { + return guaranteedMoveCount; + } + + public void setGuaranteedMoveCount(int guaranteedMoveCount) { + this.guaranteedMoveCount = guaranteedMoveCount; + } + + public boolean isReorderDamagingMoves() { + return reorderDamagingMoves; + } + + public void setReorderDamagingMoves(boolean reorderDamagingMoves) { + this.reorderDamagingMoves = reorderDamagingMoves; + } + + public boolean isMovesetsForceGoodDamaging() { + return movesetsForceGoodDamaging; + } + + public void setMovesetsForceGoodDamaging(boolean movesetsForceGoodDamaging) { + this.movesetsForceGoodDamaging = movesetsForceGoodDamaging; + } + + public int getMovesetsGoodDamagingPercent() { + return movesetsGoodDamagingPercent; + } + + public void setMovesetsGoodDamagingPercent(int movesetsGoodDamagingPercent) { + this.movesetsGoodDamagingPercent = movesetsGoodDamagingPercent; + } + + public boolean isBlockBrokenMovesetMoves() { + return blockBrokenMovesetMoves; + } + + public void setBlockBrokenMovesetMoves(boolean blockBrokenMovesetMoves) { + this.blockBrokenMovesetMoves = blockBrokenMovesetMoves; + } + + public boolean isEvolutionMovesForAll() { + return evolutionMovesForAll; + } + + public void setEvolutionMovesForAll(boolean evolutionMovesForAll) { + this.evolutionMovesForAll = evolutionMovesForAll; + } + + public TrainersMod getTrainersMod() { + return trainersMod; + } + + public void setTrainersMod(boolean... bools) { + setTrainersMod(getEnum(TrainersMod.class, bools)); + } + + private void setTrainersMod(TrainersMod trainersMod) { + this.trainersMod = trainersMod; + } + + public boolean isRivalCarriesStarterThroughout() { + return rivalCarriesStarterThroughout; + } + + public void setRivalCarriesStarterThroughout(boolean rivalCarriesStarterThroughout) { + this.rivalCarriesStarterThroughout = rivalCarriesStarterThroughout; + } + + public boolean isTrainersUsePokemonOfSimilarStrength() { + return trainersUsePokemonOfSimilarStrength; + } + + public void setTrainersUsePokemonOfSimilarStrength(boolean trainersUsePokemonOfSimilarStrength) { + this.trainersUsePokemonOfSimilarStrength = trainersUsePokemonOfSimilarStrength; + } + + public boolean isTrainersMatchTypingDistribution() { + return trainersMatchTypingDistribution; + } + + public void setTrainersMatchTypingDistribution(boolean trainersMatchTypingDistribution) { + this.trainersMatchTypingDistribution = trainersMatchTypingDistribution; + } + + public boolean isTrainersBlockLegendaries() { + return trainersBlockLegendaries; + } + + public void setTrainersBlockLegendaries(boolean trainersBlockLegendaries) { + this.trainersBlockLegendaries = trainersBlockLegendaries; + } + + public boolean isTrainersEnforceDistribution() { + return trainersEnforceDistribution; + } + + public Settings setTrainersEnforceDistribution(boolean trainersEnforceDistribution) { + this.trainersEnforceDistribution = trainersEnforceDistribution; + return this; + } + + public boolean isTrainersEnforceMainPlaythrough() { + return trainersEnforceMainPlaythrough; + } + + public Settings setTrainersEnforceMainPlaythrough(boolean trainersEnforceMainPlaythrough) { + this.trainersEnforceMainPlaythrough = trainersEnforceMainPlaythrough; + return this; + } + + + public boolean isTrainersBlockEarlyWonderGuard() { + return trainersBlockEarlyWonderGuard; + } + + public void setTrainersBlockEarlyWonderGuard(boolean trainersBlockEarlyWonderGuard) { + this.trainersBlockEarlyWonderGuard = trainersBlockEarlyWonderGuard; + } + + public boolean isRandomizeTrainerNames() { + return randomizeTrainerNames; + } + + public void setRandomizeTrainerNames(boolean randomizeTrainerNames) { + this.randomizeTrainerNames = randomizeTrainerNames; + } + + public boolean isRandomizeTrainerClassNames() { + return randomizeTrainerClassNames; + } + + public void setRandomizeTrainerClassNames(boolean randomizeTrainerClassNames) { + this.randomizeTrainerClassNames = randomizeTrainerClassNames; + } + + public boolean isTrainersForceFullyEvolved() { + return trainersForceFullyEvolved; + } + + public void setTrainersForceFullyEvolved(boolean trainersForceFullyEvolved) { + this.trainersForceFullyEvolved = trainersForceFullyEvolved; + } + + public int getTrainersForceFullyEvolvedLevel() { + return trainersForceFullyEvolvedLevel; + } + + public void setTrainersForceFullyEvolvedLevel(int trainersForceFullyEvolvedLevel) { + this.trainersForceFullyEvolvedLevel = trainersForceFullyEvolvedLevel; + } + + public boolean isTrainersLevelModified() { + return trainersLevelModified; + } + + public void setTrainersLevelModified(boolean trainersLevelModified) { + this.trainersLevelModified = trainersLevelModified; + } + + public int getTrainersLevelModifier() { + return trainersLevelModifier; + } + + public void setTrainersLevelModifier(int trainersLevelModifier) { + this.trainersLevelModifier = trainersLevelModifier; + } + + public int getEliteFourUniquePokemonNumber() { + return eliteFourUniquePokemonNumber; + } + + public void setEliteFourUniquePokemonNumber(int eliteFourUniquePokemonNumber) { + this.eliteFourUniquePokemonNumber = eliteFourUniquePokemonNumber; + } + + + public boolean isAllowTrainerAlternateFormes() { + return allowTrainerAlternateFormes; + } + + public void setAllowTrainerAlternateFormes(boolean allowTrainerAlternateFormes) { + this.allowTrainerAlternateFormes = allowTrainerAlternateFormes; + } + + public boolean isSwapTrainerMegaEvos() { + return swapTrainerMegaEvos; + } + + public void setSwapTrainerMegaEvos(boolean swapTrainerMegaEvos) { + this.swapTrainerMegaEvos = swapTrainerMegaEvos; + } + + public int getAdditionalBossTrainerPokemon() { + return additionalBossTrainerPokemon; + } + + public void setAdditionalBossTrainerPokemon(int additional) { + this.additionalBossTrainerPokemon = additional; + } + + public int getAdditionalImportantTrainerPokemon() { + return additionalImportantTrainerPokemon; + } + + public void setAdditionalImportantTrainerPokemon(int additional) { + this.additionalImportantTrainerPokemon = additional; + } + + public int getAdditionalRegularTrainerPokemon() { + return additionalRegularTrainerPokemon; + } + + public void setAdditionalRegularTrainerPokemon(int additional) { + this.additionalRegularTrainerPokemon = additional; + } + + public boolean isRandomizeHeldItemsForBossTrainerPokemon() { + return randomizeHeldItemsForBossTrainerPokemon; + } + + public void setRandomizeHeldItemsForBossTrainerPokemon(boolean bossTrainers) { + this.randomizeHeldItemsForBossTrainerPokemon = bossTrainers; + } + + public boolean isRandomizeHeldItemsForImportantTrainerPokemon() { + return randomizeHeldItemsForImportantTrainerPokemon; + } + + public void setRandomizeHeldItemsForImportantTrainerPokemon(boolean importantTrainers) { + this.randomizeHeldItemsForImportantTrainerPokemon = importantTrainers; + } + + public boolean isRandomizeHeldItemsForRegularTrainerPokemon() { + return randomizeHeldItemsForRegularTrainerPokemon; + } + + public void setRandomizeHeldItemsForRegularTrainerPokemon(boolean regularTrainers) { + this.randomizeHeldItemsForRegularTrainerPokemon = regularTrainers; + } + + public boolean isConsumableItemsOnlyForTrainers() { + return consumableItemsOnlyForTrainerPokemon; + } + + public void setConsumableItemsOnlyForTrainers(boolean consumableOnly) { + this.consumableItemsOnlyForTrainerPokemon = consumableOnly; + } + + public boolean isSensibleItemsOnlyForTrainers() { + return sensibleItemsOnlyForTrainerPokemon; + } + + public void setSensibleItemsOnlyForTrainers(boolean sensibleOnly) { + this.sensibleItemsOnlyForTrainerPokemon = sensibleOnly; + } + + public boolean isHighestLevelGetsItemsForTrainers() { + return highestLevelOnlyGetsItemsForTrainerPokemon; + } + + public void setHighestLevelGetsItemsForTrainers(boolean highestOnly) { + this.highestLevelOnlyGetsItemsForTrainerPokemon = highestOnly; + } + + public boolean isDoubleBattleMode() { + return doubleBattleMode; + } + + public void setDoubleBattleMode(boolean doubleBattleMode) { + this.doubleBattleMode = doubleBattleMode; + } + + public boolean isShinyChance() { + return shinyChance; + } + + public void setShinyChance(boolean shinyChance) { + this.shinyChance = shinyChance; + } + + public boolean isBetterTrainerMovesets() { + return betterTrainerMovesets; + } + + public void setBetterTrainerMovesets(boolean betterTrainerMovesets) { + this.betterTrainerMovesets = betterTrainerMovesets; + } + + public WildPokemonMod getWildPokemonMod() { + return wildPokemonMod; + } + + public void setWildPokemonMod(boolean... bools) { + setWildPokemonMod(getEnum(WildPokemonMod.class, bools)); + } + + private void setWildPokemonMod(WildPokemonMod wildPokemonMod) { + this.wildPokemonMod = wildPokemonMod; + } + + public WildPokemonRestrictionMod getWildPokemonRestrictionMod() { + return wildPokemonRestrictionMod; + } + + public void setWildPokemonRestrictionMod(boolean... bools) { + setWildPokemonRestrictionMod(getEnum(WildPokemonRestrictionMod.class, bools)); + } + + private void setWildPokemonRestrictionMod(WildPokemonRestrictionMod wildPokemonRestrictionMod) { + this.wildPokemonRestrictionMod = wildPokemonRestrictionMod; + } + + public boolean isUseTimeBasedEncounters() { + return useTimeBasedEncounters; + } + + public void setUseTimeBasedEncounters(boolean useTimeBasedEncounters) { + this.useTimeBasedEncounters = useTimeBasedEncounters; + } + + public boolean isBlockWildLegendaries() { + return blockWildLegendaries; + } + + public void setBlockWildLegendaries(boolean blockWildLegendaries) { + this.blockWildLegendaries = blockWildLegendaries; + } + + public boolean isUseMinimumCatchRate() { + return useMinimumCatchRate; + } + + public void setUseMinimumCatchRate(boolean useMinimumCatchRate) { + this.useMinimumCatchRate = useMinimumCatchRate; + } + + public int getMinimumCatchRateLevel() { + return minimumCatchRateLevel; + } + + public void setMinimumCatchRateLevel(int minimumCatchRateLevel) { + this.minimumCatchRateLevel = minimumCatchRateLevel; + } + + public boolean isRandomizeWildPokemonHeldItems() { + return randomizeWildPokemonHeldItems; + } + + public void setRandomizeWildPokemonHeldItems(boolean randomizeWildPokemonHeldItems) { + this.randomizeWildPokemonHeldItems = randomizeWildPokemonHeldItems; + } + + public boolean isBanBadRandomWildPokemonHeldItems() { + return banBadRandomWildPokemonHeldItems; + } + + public void setBanBadRandomWildPokemonHeldItems(boolean banBadRandomWildPokemonHeldItems) { + this.banBadRandomWildPokemonHeldItems = banBadRandomWildPokemonHeldItems; + } + + public boolean isBalanceShakingGrass() { + return balanceShakingGrass; + } + + public void setBalanceShakingGrass(boolean balanceShakingGrass) { + this.balanceShakingGrass = balanceShakingGrass; + } + + public boolean isWildLevelsModified() { + return wildLevelsModified; + } + + public void setWildLevelsModified(boolean wildLevelsModified) { + this.wildLevelsModified = wildLevelsModified; + } + + public int getWildLevelModifier() { + return wildLevelModifier; + } + + public void setWildLevelModifier(int wildLevelModifier) { + this.wildLevelModifier = wildLevelModifier; + } + + public boolean isAllowWildAltFormes() { + return allowWildAltFormes; + } + + public void setAllowWildAltFormes(boolean allowWildAltFormes) { + this.allowWildAltFormes = allowWildAltFormes; + } + + public StaticPokemonMod getStaticPokemonMod() { + return staticPokemonMod; + } + + public void setStaticPokemonMod(boolean... bools) { + setStaticPokemonMod(getEnum(StaticPokemonMod.class, bools)); + } + + private void setStaticPokemonMod(StaticPokemonMod staticPokemonMod) { + this.staticPokemonMod = staticPokemonMod; + } + + public boolean isLimitMainGameLegendaries() { + return limitMainGameLegendaries; + } + + public void setLimitMainGameLegendaries(boolean limitMainGameLegendaries) { + this.limitMainGameLegendaries = limitMainGameLegendaries; + } + + public boolean isLimit600() { + return limit600; + } + + public void setLimit600(boolean limit600) { + this.limit600 = limit600; + } + + public boolean isAllowStaticAltFormes() { + return allowStaticAltFormes; + } + + public void setAllowStaticAltFormes(boolean allowStaticAltFormes) { + this.allowStaticAltFormes = allowStaticAltFormes; + } + + public boolean isSwapStaticMegaEvos() { + return swapStaticMegaEvos; + } + + public void setSwapStaticMegaEvos(boolean swapStaticMegaEvos) { + this.swapStaticMegaEvos = swapStaticMegaEvos; + } + + public boolean isStaticLevelModified() { + return staticLevelModified; + } + + public void setStaticLevelModified(boolean staticLevelModified) { + this.staticLevelModified = staticLevelModified; + } + + public int getStaticLevelModifier() { + return staticLevelModifier; + } + + public void setStaticLevelModifier(int staticLevelModifier) { + this.staticLevelModifier = staticLevelModifier; + } + + public boolean isCorrectStaticMusic() { + return correctStaticMusic; + } + + public void setCorrectStaticMusic(boolean correctStaticMusic) { + this.correctStaticMusic = correctStaticMusic; + } + + + public TotemPokemonMod getTotemPokemonMod() { + return totemPokemonMod; + } + + public void setTotemPokemonMod(boolean... bools) { + setTotemPokemonMod(getEnum(TotemPokemonMod.class, bools)); + } + + private void setTotemPokemonMod(TotemPokemonMod totemPokemonMod) { + this.totemPokemonMod = totemPokemonMod; + } + + public AllyPokemonMod getAllyPokemonMod() { + return allyPokemonMod; + } + + public void setAllyPokemonMod(boolean... bools) { + setAllyPokemonMod(getEnum(AllyPokemonMod.class, bools)); + } + + private void setAllyPokemonMod(AllyPokemonMod allyPokemonMod) { + this.allyPokemonMod = allyPokemonMod; + } + + public AuraMod getAuraMod() { + return auraMod; + } + + public void setAuraMod(boolean... bools) { + setAuraMod(getEnum(AuraMod.class, bools)); + } + + private void setAuraMod(AuraMod auraMod) { + this.auraMod = auraMod; + } + + public boolean isRandomizeTotemHeldItems() { + return randomizeTotemHeldItems; + } + + public void setRandomizeTotemHeldItems(boolean randomizeTotemHeldItems) { + this.randomizeTotemHeldItems = randomizeTotemHeldItems; + } + + public boolean isTotemLevelsModified() { + return totemLevelsModified; + } + + public void setTotemLevelsModified(boolean totemLevelsModified) { + this.totemLevelsModified = totemLevelsModified; + } + + public int getTotemLevelModifier() { + return totemLevelModifier; + } + + public void setTotemLevelModifier(int totemLevelModifier) { + this.totemLevelModifier = totemLevelModifier; + } + + public boolean isAllowTotemAltFormes() { + return allowTotemAltFormes; + } + + public void setAllowTotemAltFormes(boolean allowTotemAltFormes) { + this.allowTotemAltFormes = allowTotemAltFormes; + } + + public TMsMod getTmsMod() { + return tmsMod; + } + + public void setTmsMod(boolean... bools) { + setTmsMod(getEnum(TMsMod.class, bools)); + } + + private void setTmsMod(TMsMod tmsMod) { + this.tmsMod = tmsMod; + } + + public boolean isTmLevelUpMoveSanity() { + return tmLevelUpMoveSanity; + } + + public void setTmLevelUpMoveSanity(boolean tmLevelUpMoveSanity) { + this.tmLevelUpMoveSanity = tmLevelUpMoveSanity; + } + + public boolean isKeepFieldMoveTMs() { + return keepFieldMoveTMs; + } + + public void setKeepFieldMoveTMs(boolean keepFieldMoveTMs) { + this.keepFieldMoveTMs = keepFieldMoveTMs; + } + + public boolean isFullHMCompat() { + return fullHMCompat; + } + + public void setFullHMCompat(boolean fullHMCompat) { + this.fullHMCompat = fullHMCompat; + } + + public boolean isTmsForceGoodDamaging() { + return tmsForceGoodDamaging; + } + + public void setTmsForceGoodDamaging(boolean tmsForceGoodDamaging) { + this.tmsForceGoodDamaging = tmsForceGoodDamaging; + } + + public int getTmsGoodDamagingPercent() { + return tmsGoodDamagingPercent; + } + + public void setTmsGoodDamagingPercent(int tmsGoodDamagingPercent) { + this.tmsGoodDamagingPercent = tmsGoodDamagingPercent; + } + + public boolean isBlockBrokenTMMoves() { + return blockBrokenTMMoves; + } + + public void setBlockBrokenTMMoves(boolean blockBrokenTMMoves) { + this.blockBrokenTMMoves = blockBrokenTMMoves; + } + + public TMsHMsCompatibilityMod getTmsHmsCompatibilityMod() { + return tmsHmsCompatibilityMod; + } + + public void setTmsHmsCompatibilityMod(boolean... bools) { + setTmsHmsCompatibilityMod(getEnum(TMsHMsCompatibilityMod.class, bools)); + } + + private void setTmsHmsCompatibilityMod(TMsHMsCompatibilityMod tmsHmsCompatibilityMod) { + this.tmsHmsCompatibilityMod = tmsHmsCompatibilityMod; + } + + public boolean isTmsFollowEvolutions() { + return tmsFollowEvolutions; + } + + public void setTmsFollowEvolutions(boolean tmsFollowEvolutions) { + this.tmsFollowEvolutions = tmsFollowEvolutions; + } + + public MoveTutorMovesMod getMoveTutorMovesMod() { + return moveTutorMovesMod; + } + + public void setMoveTutorMovesMod(boolean... bools) { + setMoveTutorMovesMod(getEnum(MoveTutorMovesMod.class, bools)); + } + + private void setMoveTutorMovesMod(MoveTutorMovesMod moveTutorMovesMod) { + this.moveTutorMovesMod = moveTutorMovesMod; + } + + public boolean isTutorLevelUpMoveSanity() { + return tutorLevelUpMoveSanity; + } + + public void setTutorLevelUpMoveSanity(boolean tutorLevelUpMoveSanity) { + this.tutorLevelUpMoveSanity = tutorLevelUpMoveSanity; + } + + public boolean isKeepFieldMoveTutors() { + return keepFieldMoveTutors; + } + + public void setKeepFieldMoveTutors(boolean keepFieldMoveTutors) { + this.keepFieldMoveTutors = keepFieldMoveTutors; + } + + public boolean isTutorsForceGoodDamaging() { + return tutorsForceGoodDamaging; + } + + public void setTutorsForceGoodDamaging(boolean tutorsForceGoodDamaging) { + this.tutorsForceGoodDamaging = tutorsForceGoodDamaging; + } + + public int getTutorsGoodDamagingPercent() { + return tutorsGoodDamagingPercent; + } + + public void setTutorsGoodDamagingPercent(int tutorsGoodDamagingPercent) { + this.tutorsGoodDamagingPercent = tutorsGoodDamagingPercent; + } + + public boolean isBlockBrokenTutorMoves() { + return blockBrokenTutorMoves; + } + + public void setBlockBrokenTutorMoves(boolean blockBrokenTutorMoves) { + this.blockBrokenTutorMoves = blockBrokenTutorMoves; + } + + public MoveTutorsCompatibilityMod getMoveTutorsCompatibilityMod() { + return moveTutorsCompatibilityMod; + } + + public void setMoveTutorsCompatibilityMod(boolean... bools) { + setMoveTutorsCompatibilityMod(getEnum(MoveTutorsCompatibilityMod.class, bools)); + } + + private void setMoveTutorsCompatibilityMod(MoveTutorsCompatibilityMod moveTutorsCompatibilityMod) { + this.moveTutorsCompatibilityMod = moveTutorsCompatibilityMod; + } + + public boolean isTutorFollowEvolutions() { + return tutorFollowEvolutions; + } + + public void setTutorFollowEvolutions(boolean tutorFollowEvolutions) { + this.tutorFollowEvolutions = tutorFollowEvolutions; + } + + public InGameTradesMod getInGameTradesMod() { + return inGameTradesMod; + } + + public void setInGameTradesMod(boolean... bools) { + setInGameTradesMod(getEnum(InGameTradesMod.class, bools)); + } + + private void setInGameTradesMod(InGameTradesMod inGameTradesMod) { + this.inGameTradesMod = inGameTradesMod; + } + + public boolean isRandomizeInGameTradesNicknames() { + return randomizeInGameTradesNicknames; + } + + public void setRandomizeInGameTradesNicknames(boolean randomizeInGameTradesNicknames) { + this.randomizeInGameTradesNicknames = randomizeInGameTradesNicknames; + } + + public boolean isRandomizeInGameTradesOTs() { + return randomizeInGameTradesOTs; + } + + public void setRandomizeInGameTradesOTs(boolean randomizeInGameTradesOTs) { + this.randomizeInGameTradesOTs = randomizeInGameTradesOTs; + } + + public boolean isRandomizeInGameTradesIVs() { + return randomizeInGameTradesIVs; + } + + public void setRandomizeInGameTradesIVs(boolean randomizeInGameTradesIVs) { + this.randomizeInGameTradesIVs = randomizeInGameTradesIVs; + } + + public boolean isRandomizeInGameTradesItems() { + return randomizeInGameTradesItems; + } + + public void setRandomizeInGameTradesItems(boolean randomizeInGameTradesItems) { + this.randomizeInGameTradesItems = randomizeInGameTradesItems; + } + + public FieldItemsMod getFieldItemsMod() { + return fieldItemsMod; + } + + public void setFieldItemsMod(boolean... bools) { + setFieldItemsMod(getEnum(FieldItemsMod.class, bools)); + } + + private void setFieldItemsMod(FieldItemsMod fieldItemsMod) { + this.fieldItemsMod = fieldItemsMod; + } + + public boolean isBanBadRandomFieldItems() { + return banBadRandomFieldItems; + } + + + public void setBanBadRandomFieldItems(boolean banBadRandomFieldItems) { + this.banBadRandomFieldItems = banBadRandomFieldItems; + } + + public ShopItemsMod getShopItemsMod() { + return shopItemsMod; + } + + public void setShopItemsMod(boolean... bools) { + setShopItemsMod(getEnum(ShopItemsMod.class, bools)); + } + + private void setShopItemsMod(ShopItemsMod shopItemsMod) { + this.shopItemsMod = shopItemsMod; + } + + public boolean isBanBadRandomShopItems() { + return banBadRandomShopItems; + } + + public void setBanBadRandomShopItems(boolean banBadRandomShopItems) { + this.banBadRandomShopItems = banBadRandomShopItems; + } + + public boolean isBanRegularShopItems() { + return banRegularShopItems; + } + + public void setBanRegularShopItems(boolean banRegularShopItems) { + this.banRegularShopItems = banRegularShopItems; + } + + public boolean isBanOPShopItems() { + return banOPShopItems; + } + + public void setBanOPShopItems(boolean banOPShopItems) { + this.banOPShopItems = banOPShopItems; + } + + public boolean isBalanceShopPrices() { + return balanceShopPrices; + } + + public void setBalanceShopPrices(boolean balanceShopPrices) { + this.balanceShopPrices = balanceShopPrices; + } + + public boolean isGuaranteeEvolutionItems() { + return guaranteeEvolutionItems; + } + + public void setGuaranteeEvolutionItems(boolean guaranteeEvolutionItems) { + this.guaranteeEvolutionItems = guaranteeEvolutionItems; + } + + public boolean isGuaranteeXItems() { + return guaranteeXItems; + } + + public void setGuaranteeXItems(boolean guaranteeXItems) { + this.guaranteeXItems = guaranteeXItems; + } + + public PickupItemsMod getPickupItemsMod() { + return pickupItemsMod; + } + + public void setPickupItemsMod(boolean... bools) { + setPickupItemsMod(getEnum(PickupItemsMod.class, bools)); + } + + private void setPickupItemsMod(PickupItemsMod pickupItemsMod) { + this.pickupItemsMod = pickupItemsMod; + } + + public boolean isBanBadRandomPickupItems() { + return banBadRandomPickupItems; + } + + public void setBanBadRandomPickupItems(boolean banBadRandomPickupItems) { + this.banBadRandomPickupItems = banBadRandomPickupItems; + } + + private static int makeByteSelected(boolean... bools) { + if (bools.length > 8) { + throw new IllegalArgumentException("Can't set more than 8 bits in a byte!"); + } + + int initial = 0; + int state = 1; + for (boolean b : bools) { + initial |= b ? state : 0; + state *= 2; + } + return initial; + } + + private static boolean restoreState(byte b, int index) { + if (index >= 8) { + throw new IllegalArgumentException("Can't read more than 8 bits from a byte!"); + } + + int value = b & 0xFF; + return ((value >> index) & 0x01) == 0x01; + } + + private static void writeFullInt(ByteArrayOutputStream out, int value) throws IOException { + byte[] crc = ByteBuffer.allocate(4).putInt(value).array(); + out.write(crc); + } + + private static void write2ByteInt(ByteArrayOutputStream out, int value) { + out.write(value & 0xFF); + out.write((value >> 8) & 0xFF); + } + + private static > E restoreEnum(Class clazz, byte b, int... indices) { + boolean[] bools = new boolean[indices.length]; + int i = 0; + for (int idx : indices) { + bools[i] = restoreState(b, idx); + i++; + } + return getEnum(clazz, bools); + } + + @SuppressWarnings("unchecked") + private static > E getEnum(Class clazz, boolean... bools) { + int index = getSetEnum(clazz.getSimpleName(), bools); + try { + return ((E[]) clazz.getMethod("values").invoke(null))[index]; + } catch (Exception e) { + throw new IllegalArgumentException(String.format("Unable to parse enum of type %s", clazz.getSimpleName()), + e); + } + } + + private static int getSetEnum(String type, boolean... bools) { + int index = -1; + for (int i = 0; i < bools.length; i++) { + if (bools[i]) { + if (index >= 0) { + throw new IllegalStateException(String.format("Only one value for %s may be chosen!", type)); + } + index = i; + } + } + // We have to return something, so return the default + return index >= 0 ? index : 0; + } + + private static void checkChecksum(byte[] data) { + // Check the checksum + ByteBuffer buf = ByteBuffer.allocate(4).put(data, data.length - 8, 4); + buf.rewind(); + int crc = buf.getInt(); + + CRC32 checksum = new CRC32(); + checksum.update(data, 0, data.length - 8); + + if ((int) checksum.getValue() != crc) { + throw new IllegalArgumentException("Malformed input string"); + } + } + +} diff --git a/src/com/pkrandom/SettingsUpdater.java b/src/com/pkrandom/SettingsUpdater.java new file mode 100644 index 0000000..eabab32 --- /dev/null +++ b/src/com/pkrandom/SettingsUpdater.java @@ -0,0 +1,389 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- SettingsUpdater.java - handles the process of updating a Settings file--*/ +/*-- from an old randomizer version to use the --*/ +/*-- correct binary format so it can be loaded by --*/ +/*-- the current version. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.nio.ByteBuffer; +import java.util.Base64; +import java.util.zip.CRC32; + +public class SettingsUpdater { + + private byte[] dataBlock; + private int actualDataLength; + + /** + * Given a quicksettings config string from an old randomizer version, + * update it to be compatible with the currently running randomizer version. + * + * @param oldVersion + * The PRESET_FILE_VERSION used to generate the given string + * @param configString + * The outdated config string + * @return The updated config string to be applied + */ + public String update(int oldVersion, String configString) { + byte[] data = Base64.getDecoder().decode(configString); + this.dataBlock = new byte[200]; + this.actualDataLength = data.length; + System.arraycopy(data, 0, this.dataBlock, 0, this.actualDataLength); + + // new field values here are written as bitwise ORs + // this is slightly slower in execution, but it makes it clearer + // just what values we actually want to set + // bit fields 1 2 3 4 5 6 7 8 + // are values 0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80 + + // versions prior to 120 didn't have quick settings file, + // they're just included here for completeness' sake + + // versions < 102: add abilities set to unchanged + if (oldVersion < 102) { + dataBlock[1] |= 0x10; + } + + // versions < 110: add move tutor byte (set both to unchanged) + if (oldVersion < 110) { + insertExtraByte(15, (byte) (0x04 | 0x10)); + } + + // version 110-111 no change (only added trainer names/classes to preset + // files, and some checkboxes which it is safe to leave as off) + + // 111-112 no change (another checkbox we leave as off) + + // 112-120 no change (only another checkbox) + + // 120-150 new features + if (oldVersion < 150) { + // trades and field items: both unchanged + insertExtraByte(16, (byte) (0x40)); + insertExtraByte(17, (byte) (0x04)); + // add a fake checksum for nicknames at the very end of the data, + // we can leave it at 0 + actualDataLength += 4; + } + + // 150-160 lots of re-org etc + if (oldVersion < 160) { + // byte 0: + // copy "update moves" to "update legacy moves" + // move the other 3 fields after it up one + int firstByte = dataBlock[0] & 0xFF; + int updateMoves = firstByte & 0x08; + int laterFields = firstByte & (0x10 | 0x20 | 0x40); + dataBlock[0] = (byte) ((firstByte & (0x01 | 0x02 | 0x04 | 0x08)) | (updateMoves << 1) | (laterFields << 1)); + + // byte 1: + // leave as is (don't turn on exp standardization) + + // byte 2: + // retrieve values of bw exp patch & held items + // code tweaks keeps the same value as bw exp patch had + // but turn held items off (it got replaced by pokelimit) + int hasBWPatch = (dataBlock[2] & 0x08) >> 3; + int hasHeldItems = (dataBlock[2] & 0x80) >> 7; + dataBlock[2] &= (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40); + + // byte 3: + // turn on starter held items if held items checkbox was on + if (hasHeldItems > 0) { + dataBlock[3] |= 0x10; + } + + // byte 4-9 are starters + // byte 10 adds "4 moves" but we leave it off + + // byte 11: + // pull out value of WP no legendaries + // replace it with TP no early shedinja + // also get WP catch rate value + int wpNoLegendaries = (dataBlock[11] & 0x80) >> 7; + int tpNoEarlyShedinja = (dataBlock[13] & 0x10) >> 4; + int wpCatchRate = (dataBlock[13] & 0x08) >> 3; + dataBlock[11] = (byte) ((dataBlock[11] & (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40)) | (tpNoEarlyShedinja << 7)); + + // byte 12 unchanged + + // insert a new byte for "extra" WP stuff + // include no legendaries & catch rate + // also include WP held items if overall held items box was on + // leave similar strength off, there's a bugfix a little later on... + insertExtraByte(13, (byte) ((wpCatchRate) | (wpNoLegendaries << 1) | (hasHeldItems << 3))); + + // new byte 14 (was 13 in 150): + // switch off bits 4 and 5 (were for catch rate & no early shedinja) + dataBlock[14] &= 0x07; + + // the rest of the config bytes are unchanged + // but we need to add the fields for pokemon limit & code tweaks + + // no pokemon limit + insertIntField(19, 0); + + // only possible code tweak = bw exp + insertIntField(23, hasBWPatch); + } + + // 160 to 161: no change + // the only changes were in implementation, which broke presets, but + // leaves settings files the same + + // 161 to 162: + // some added fields to tm/move tutors that we can leave blank + // more crucially: a new general options byte @ offset 3 + // set it to all off by default + if (oldVersion < 162) { + insertExtraByte(3, (byte) 0); + } + + // no significant changes from 162 to 163 + + if (oldVersion < 170) { + // 163 to 170: add move data/evolution randoms and 2nd TM byte + insertExtraByte(17, (byte) 0); + insertExtraByte(21, (byte) 0); + insertExtraByte(22, (byte) 1); + + // Move some bits from general options to misc tweaks + int oldTweaks = FileFunctions.readFullIntBigEndian(dataBlock, 27); + if ((dataBlock[0] & 1) != 0) { + oldTweaks |= MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); + } + if ((dataBlock[0] & (1 << 1)) != 0) { + oldTweaks |= MiscTweak.NATIONAL_DEX_AT_START.getValue(); + } + if ((dataBlock[0] & (1 << 5)) != 0) { + oldTweaks |= MiscTweak.UPDATE_TYPE_EFFECTIVENESS.getValue(); + } + if ((dataBlock[2] & (1 << 5)) != 0) { + oldTweaks |= MiscTweak.FORCE_CHALLENGE_MODE.getValue(); + } + FileFunctions.writeFullIntBigEndian(dataBlock, 27, oldTweaks); + + // Now remap the affected bytes + dataBlock[0] = getRemappedByte(dataBlock[0], new int[] { 2, 3, 4, 6, 7 }); + dataBlock[2] = getRemappedByte(dataBlock[2], new int[] { 0, 1, 2, 4, 6, 7 }); + } + + if (oldVersion < 171) { + // 170 to 171: base stats follow evolutions is now a checkbox + // so if it's set in the settings file (byte 1 bit 0), turn on the + // "random" radiobox (byte 1 bit 1) + if ((dataBlock[1] & 1) != 0) { + dataBlock[1] |= (1 << 1); + } + + // shift around stuff to give abilities their own byte. + + // move byte 3 bit 0 to byte 0 bit 5 + // (byte 0 got cleared out by things becoming Tweaks in 170) + if ((dataBlock[3] & 1) != 0) { + dataBlock[0] |= (1 << 5); + } + + // move bits 4-6 from byte 1 to byte 3 + dataBlock[3] = (byte) ((dataBlock[1] & 0x70) >> 4); + + // clean up byte 1 (keep bits 0-3, move bit 7 to 4, clear 5-7) + dataBlock[1] = (byte) ((dataBlock[1] & 0x0F) | ((dataBlock[1] & 0x80) >> 3)); + + // empty byte for fully evolved trainer mon setting + insertExtraByte(13, (byte) 30); + + // bytes for "good damaging moves" settings + insertExtraByte(12, (byte) 0); + insertExtraByte(20, (byte) 0); + insertExtraByte(22, (byte) 0); + } + + if(oldVersion < 172) { + // 171 to 172: removed separate names files in favor of one unified file + // so two of the trailing checksums are gone + actualDataLength -= 8; + + // fix wild legendaries + dataBlock[16] = (byte) (dataBlock[16] ^ (1 << 1)); + + // add space for the trainer level modifier + insertExtraByte(35, (byte) 50); // 50 in the settings file = +0% after adjustment + } + + if (oldVersion < 300) { + // wild level modifier + insertExtraByte(38, (byte) 50); + + // exp curve modifier + insertExtraByte(39, (byte) 1); + } + + if (oldVersion < 311) { + // double battle mode + boss/important extra pokemon + insertExtraByte(40, (byte) 0); + + // regular extra pokemon + aura mod + insertExtraByte(41, (byte) 8); + + // Totem/Ally mod + totem items/alt formes + insertExtraByte(42, (byte) 9); + + // totem level modifier + insertExtraByte(43, (byte) 50); + + // base stat generation + insertExtraByte(44, (byte) 0); + + // move generation + insertExtraByte(45, (byte) 0); + } + + if (oldVersion < 314) { + // exp curve + insertExtraByte(46, (byte) 0); + + // static level modifier + insertExtraByte(47, (byte) 50); + } + + if (oldVersion < 315) { + // This tweak used to be "Randomize Hidden Hollows", which got moved to static Pokemon + // randomization, so the misc tweak became unused in this version. It eventually *was* + // used in a future version for something else, but don't get confused by the new name. + int oldTweaks = FileFunctions.readFullIntBigEndian(dataBlock, 32); + oldTweaks &= ~MiscTweak.FORCE_CHALLENGE_MODE.getValue(); + FileFunctions.writeFullIntBigEndian(dataBlock, 32, oldTweaks); + + // Trainer Pokemon held items + insertExtraByte(48, (byte) 0); + } + + if (oldVersion < 317) { + // Pickup items + insertExtraByte(49, (byte) 0); + + // Clear "assoc" state from GenRestrictions as it doesn't exist any longer + int genRestrictions = FileFunctions.readFullIntBigEndian(dataBlock, 28); + genRestrictions &= 127; + FileFunctions.writeFullIntBigEndian(dataBlock, 28, genRestrictions); + } + + if (oldVersion < 319) { + // 5-10 custom starters, offset by 1 because of new "Random" option + int starter1 = FileFunctions.read2ByteInt(dataBlock, 5); + int starter2 = FileFunctions.read2ByteInt(dataBlock, 7); + int starter3 = FileFunctions.read2ByteInt(dataBlock, 9); + + starter1 += 1; + starter2 += 1; + starter3 += 1; + + FileFunctions.write2ByteInt(dataBlock, 5, starter1); + FileFunctions.write2ByteInt(dataBlock, 7, starter2); + FileFunctions.write2ByteInt(dataBlock, 9, starter3); + + // 50 elite four unique pokemon (3 bits) + insertExtraByte(50, (byte) 0); + } + + if (oldVersion < 321) { + // Minimum Catch Rate got moved around to give it more space for Guaranteed Catch. + // Read the old one, clear it out, then write it to the new location. + int oldMinimumCatchRate = ((dataBlock[16] & 0x60) >> 5) + 1; + dataBlock[16] &= ~0x60; + dataBlock[50] |= ((oldMinimumCatchRate - 1) << 3); + } + + // fix checksum + CRC32 checksum = new CRC32(); + checksum.update(dataBlock, 0, actualDataLength - 8); + + // convert crc32 to int bytes + byte[] crcBuf = ByteBuffer.allocate(4).putInt((int) checksum.getValue()).array(); + System.arraycopy(crcBuf, 0, dataBlock, actualDataLength - 8, 4); + + // have to make a new byte array to convert to base64 + byte[] finalConfigString = new byte[actualDataLength]; + System.arraycopy(dataBlock, 0, finalConfigString, 0, actualDataLength); + return Base64.getEncoder().encodeToString(finalConfigString); + } + + private static byte getRemappedByte(byte old, int[] oldIndexes) { + int newValue = 0; + int oldValue = old & 0xFF; + for (int i = 0; i < oldIndexes.length; i++) { + if ((oldValue & (1 << oldIndexes[i])) != 0) { + newValue |= (1 << i); + } + } + return (byte) newValue; + } + + /** + * Insert a 4-byte int field in the data block at the given position. Shift + * everything else up. Do nothing if there's no room left (should never + * happen) + * + * @param position + * The offset to add the field + * @param value + * The value to give to the field + */ + private void insertIntField(int position, int value) { + if (actualDataLength + 4 > dataBlock.length) { + // can't do + return; + } + for (int j = actualDataLength; j > position + 3; j--) { + dataBlock[j] = dataBlock[j - 4]; + } + byte[] valueBuf = ByteBuffer.allocate(4).putInt(value).array(); + System.arraycopy(valueBuf, 0, dataBlock, position, 4); + actualDataLength += 4; + } + + /** + * Insert a byte-field in the data block at the given position. Shift + * everything else up. Do nothing if there's no room left (should never + * happen) + * + * @param position + * The offset to add the field + * @param value + * The value to give to the field + */ + private void insertExtraByte(int position, byte value) { + if (actualDataLength == dataBlock.length) { + // can't do + return; + } + for (int j = actualDataLength; j > position; j--) { + dataBlock[j] = dataBlock[j - 1]; + } + dataBlock[position] = value; + actualDataLength++; + } + +} diff --git a/src/com/pkrandom/SysConstants.java b/src/com/pkrandom/SysConstants.java new file mode 100644 index 0000000..350151e --- /dev/null +++ b/src/com/pkrandom/SysConstants.java @@ -0,0 +1,56 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- SysConstants.java - contains constants not related to the --*/ +/*-- randomization process itself, such as those --*/ +/*-- relating to file I/O and the updating system. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.io.File; + +public class SysConstants { + + public static final String AUTOUPDATE_URL = "http://pokehacks.dabomstew.com/randomizer/autoupdate/"; + public static final String WEBSITE_URL = "http://pokehacks.dabomstew.com/randomizer/"; + public static final String WEBSITE_URL_ZX = "https://github.com/Ajarmar/universal-pokemon-randomizer-zx/releases"; + public static final String WIKI_URL_ZX = "https://github.com/Ajarmar/universal-pokemon-randomizer-zx/wiki"; + public static final String API_URL_ZX = "https://api.github.com/repos/ajarmar/universal-pokemon-randomizer-zx/releases/latest"; + public static final int UPDATE_VERSION = 1721; + public static final String ROOT_PATH = getRootPath(); + public static final String LINE_SEP = System.getProperty("line.separator"); + public static final String customNamesFile = "customnames.rncn"; + + // OLD custom names files + public static final String tnamesFile = "trainernames.txt"; + public static final String tclassesFile = "trainerclasses.txt"; + public static final String nnamesFile = "nicknames.txt"; + + private static String getRootPath() { + try { + File fh = Utils.getExecutionLocation().getParentFile(); + return fh.getAbsolutePath() + File.separator; + } catch (Exception e) { + return "./"; + } + } + +} diff --git a/src/com/pkrandom/Utils.java b/src/com/pkrandom/Utils.java new file mode 100644 index 0000000..d527faa --- /dev/null +++ b/src/com/pkrandom/Utils.java @@ -0,0 +1,145 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- Utils.java - contains functions related to specific parts of the --*/ +/*-- randomizer's functionality which cannot really be used --*/ +/*-- outside of that function but should still be separated --*/ +/*-- from GUI code. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.Base64; +import java.util.zip.CRC32; + +import com.pkrandom.exceptions.InvalidSupplementFilesException; +import com.pkrandom.newgui.NewRandomizerGUI; + +public class Utils { + + public static void validateRomFile(File fh) throws InvalidROMException { + // first, check for common filetypes that aren't ROMs + // read first 10 bytes of the file to do this + try { + FileInputStream fis = new FileInputStream(fh); + byte[] sig = new byte[10]; + int sigLength = fis.read(sig); + fis.close(); + if (sigLength < 10) { + throw new InvalidROMException(InvalidROMException.Type.LENGTH, String.format( + "%s appears to be a blank or nearly blank file.", fh.getName())); + } + if (sig[0] == 0x50 && sig[1] == 0x4b && sig[2] == 0x03 && sig[3] == 0x04) { + throw new InvalidROMException(InvalidROMException.Type.ZIP_FILE, String.format( + "%s is a ZIP archive, not a ROM.", fh.getName())); + } + if (sig[0] == 0x52 && sig[1] == 0x61 && sig[2] == 0x72 && sig[3] == 0x21 && sig[4] == 0x1A + && sig[5] == 0x07) { + throw new InvalidROMException(InvalidROMException.Type.RAR_FILE, String.format( + "%s is a RAR archive, not a ROM.", fh.getName())); + } + if (sig[0] == 'P' && sig[1] == 'A' && sig[2] == 'T' && sig[3] == 'C' && sig[4] == 'H') { + throw new InvalidROMException(InvalidROMException.Type.IPS_FILE, String.format( + "%s is a IPS patch, not a ROM.", fh.getName())); + } + } catch (IOException ex) { + throw new InvalidROMException(InvalidROMException.Type.UNREADABLE, String.format( + "Could not read %s from disk.", fh.getName())); + } + } + + // RomHandlers implicitly rely on these - call this before creating settings + // etc. + public static void testForRequiredConfigs() throws FileNotFoundException { + String[] required = new String[] { "gameboy_jpn.tbl", "rby_english.tbl", "rby_freger.tbl", "rby_espita.tbl", + "green_translation.tbl", "gsc_english.tbl", "gsc_freger.tbl", "gsc_espita.tbl", "gba_english.tbl", + "gba_jpn.tbl", "Generation4.tbl", "Generation5.tbl", "gen1_offsets.ini", "gen2_offsets.ini", + "gen3_offsets.ini", "gen4_offsets.ini", "gen5_offsets.ini", "gen6_offsets.ini", "gen7_offsets.ini", + SysConstants.customNamesFile }; + for (String filename : required) { + if (!FileFunctions.configExists(filename)) { + throw new FileNotFoundException(filename); + } + } + } + + public static void validatePresetSupplementFiles(String config, CustomNamesSet customNames) + throws InvalidSupplementFilesException { + byte[] data = Base64.getDecoder().decode(config); + + if (data.length < Settings.LENGTH_OF_SETTINGS_DATA + 9) { + throw new InvalidSupplementFilesException(InvalidSupplementFilesException.Type.UNKNOWN, + "The preset config is too short to be valid"); + } + + // Check the checksum + ByteBuffer buf = ByteBuffer.allocate(4).put(data, data.length - 8, 4); + buf.rewind(); + int crc = buf.getInt(); + + CRC32 checksum = new CRC32(); + checksum.update(data, 0, data.length - 8); + if ((int) checksum.getValue() != crc) { + throw new IllegalArgumentException("Checksum failure."); + } + + // Check the trainerclass & trainernames & nicknames crc + if (customNames == null && !FileFunctions.checkOtherCRC(data, 16, 4, SysConstants.customNamesFile, data.length - 4)) { + throw new InvalidSupplementFilesException(InvalidSupplementFilesException.Type.CUSTOM_NAMES, + "Can't use this preset because you have a different set " + "of custom names to the creator."); + } + } + + public static File getExecutionLocation() throws UnsupportedEncodingException { + URL location = NewRandomizerGUI.class.getProtectionDomain().getCodeSource().getLocation(); + String file = location.getFile(); + String plusEncoded = file.replaceAll("\\+", "%2b"); + return new File(java.net.URLDecoder.decode(plusEncoded, "UTF-8")); + } + + public static class InvalidROMException extends Exception { + /** + * + */ + private static final long serialVersionUID = 6568398515886021477L; + + public enum Type { + LENGTH, ZIP_FILE, RAR_FILE, IPS_FILE, UNREADABLE + } + + private final Type type; + + public InvalidROMException(Type type, String message) { + super(message); + this.type = type; + } + + public Type getType() { + return type; + } + } +} \ No newline at end of file diff --git a/src/com/pkrandom/Version.java b/src/com/pkrandom/Version.java new file mode 100644 index 0000000..d30aa83 --- /dev/null +++ b/src/com/pkrandom/Version.java @@ -0,0 +1,95 @@ +package com.pkrandom; + +/*----------------------------------------------------------------------------*/ +/*-- Version.java - contains information about the randomizer's versions --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.HashMap; +import java.util.Map; + +public class Version { + public static final int VERSION = 321; // Increment by 1 for new version. Updated for 4.6.0-dev. + public static final String VERSION_STRING = "4.6.0-dev"; + + public static final Map oldVersions = setupVersionsMap(); + + private static Map setupVersionsMap() { + Map map = new HashMap<>(); + + map.put(100, "1.0.1a"); + map.put(102, "1.0.2a"); + map.put(110, "1.1.0"); + map.put(111, "1.1.1"); + map.put(112, "1.1.2"); + map.put(120, "1.2.0a"); + map.put(150, "1.5.0"); + map.put(160, "1.6.0a"); + map.put(161, "1.6.1"); + map.put(162, "1.6.2"); + map.put(163, "1.6.3b"); + map.put(170, "1.7.0b"); + map.put(171, "1.7.1"); + map.put(172, "1.7.2"); + map.put(310, "3.1.0"); + map.put(311, "4.0.0"); + map.put(312, "4.0.1"); + map.put(313, "4.0.2"); + map.put(314, "4.1.0"); + map.put(315, "4.2.0"); + map.put(316, "4.2.1"); + map.put(317, "4.3.0"); + map.put(318, "4.4.0"); + map.put(319, "4.5.0"); + map.put(320, "4.5.1"); + + // Latest version - when version is updated, add the old version as an explicit put + map.put(VERSION, VERSION_STRING); + + return map; + } + + public static boolean isReleaseVersionNewer(String releaseVersion) { + if (VERSION_STRING.contains("dev")) { + return false; + } + // Chop off leading "v" from release version + try { + String releaseVersionTrimmed = releaseVersion.substring(1); + String[] thisVersionPieces = VERSION_STRING.split("\\."); + String[] releaseVersionPieces = releaseVersionTrimmed.split("\\."); + int smallestLength = Math.min(thisVersionPieces.length, releaseVersionPieces.length); + for (int i = 0; i < smallestLength; i++) { + int thisVersionPiece = Integer.parseInt(thisVersionPieces[i]); + int releaseVersionPiece = Integer.parseInt(releaseVersionPieces[i]); + if (thisVersionPiece < releaseVersionPiece) { + return true; + } else if (thisVersionPiece > releaseVersionPiece) { + return false; + } + } + return false; + } catch (Exception e) { + // Really not a big deal if we fail at this, probably because we can't connect to Github. + return false; + } + } +} diff --git a/src/com/pkrandom/cli/CliRandomizer.java b/src/com/pkrandom/cli/CliRandomizer.java new file mode 100644 index 0000000..e04abbf --- /dev/null +++ b/src/com/pkrandom/cli/CliRandomizer.java @@ -0,0 +1,230 @@ +package com.pkrandom.cli; + +import com.pkrandom.FileFunctions; +import com.pkrandom.RandomSource; +import com.pkrandom.Randomizer; +import com.pkrandom.Settings; +import com.pkrandom.romhandlers.*; + +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.ResourceBundle; + +public class CliRandomizer { + + private final static ResourceBundle bundle = java.util.ResourceBundle.getBundle("com/pkrandom/newgui/Bundle"); + + private static boolean performDirectRandomization(String settingsFilePath, String sourceRomFilePath, + String destinationRomFilePath, boolean saveAsDirectory, + String updateFilePath, boolean saveLog) { + // borrowed directly from NewRandomizerGUI() + RomHandler.Factory[] checkHandlers = new RomHandler.Factory[] { + new Gen1RomHandler.Factory(), + new Gen2RomHandler.Factory(), + new Gen3RomHandler.Factory(), + new Gen4RomHandler.Factory(), + new Gen5RomHandler.Factory(), + new Gen6RomHandler.Factory(), + new Gen7RomHandler.Factory() + }; + + Settings settings; + try { + File fh = new File(settingsFilePath); + FileInputStream fis = new FileInputStream(fh); + settings = Settings.read(fis); + // taken from com.pkrandom.newgui.NewRandomizerGUI.saveROM, set distinctly from all other settings + settings.setCustomNames(FileFunctions.getCustomNames()); + fis.close(); + } catch (UnsupportedOperationException | IllegalArgumentException | IOException ex) { + ex.printStackTrace(); + return false; + } + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream log; + try { + log = new PrintStream(baos, false, "UTF-8"); + } catch (UnsupportedEncodingException e) { + log = new PrintStream(baos); + } + + final PrintStream verboseLog = log; + + try { + File romFileHandler = new File(sourceRomFilePath); + RomHandler romHandler; + + for (RomHandler.Factory rhf : checkHandlers) { + if (rhf.isLoadable(romFileHandler.getAbsolutePath())) { + romHandler = rhf.create(RandomSource.instance()); + romHandler.loadRom(romFileHandler.getAbsolutePath()); + if (updateFilePath != null && (romHandler.generationOfPokemon() == 6 || romHandler.generationOfPokemon() == 7)) { + romHandler.loadGameUpdate(updateFilePath); + if (!saveAsDirectory) { + printWarning("Forcing save as directory since a game update was supplied."); + } + saveAsDirectory = true; + } + if (saveAsDirectory && romHandler.generationOfPokemon() != 6 && romHandler.generationOfPokemon() != 7) { + saveAsDirectory = false; + printWarning("Saving as directory does not make sense for non-3DS games, ignoring \"-d\" flag..."); + } + + CliRandomizer.displaySettingsWarnings(settings, romHandler); + + File fh = new File(destinationRomFilePath); + if (!saveAsDirectory) { + List extensions = new ArrayList<>(Arrays.asList("sgb", "gbc", "gba", "nds", "cxi")); + extensions.remove(romHandler.getDefaultExtension()); + + fh = FileFunctions.fixFilename(fh, romHandler.getDefaultExtension(), extensions); + if (romHandler instanceof AbstractDSRomHandler || romHandler instanceof Abstract3DSRomHandler) { + String currentFN = romHandler.loadedFilename(); + if (currentFN.equals(fh.getAbsolutePath())) { + printError(bundle.getString("GUI.cantOverwriteDS")); + return false; + } + } + } + + String filename = fh.getAbsolutePath(); + + Randomizer randomizer = new Randomizer(settings, romHandler, bundle, saveAsDirectory); + randomizer.randomize(filename, verboseLog); + verboseLog.close(); + byte[] out = baos.toByteArray(); + if (saveLog) { + try { + FileOutputStream fos = new FileOutputStream(filename + ".log"); + fos.write(0xEF); + fos.write(0xBB); + fos.write(0xBF); + fos.write(out); + fos.close(); + } catch (IOException e) { + printWarning("Could not write log."); + } + } + System.out.println("Randomized successfully!"); + // this is the only successful exit, everything else will return false at the end of the function + return true; + } + } + // if we get here it means no rom handlers matched the ROM file + System.err.printf(bundle.getString("GUI.unsupportedRom") + "%n", romFileHandler.getName()); + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + private static void displaySettingsWarnings(Settings settings, RomHandler romHandler) { + Settings.TweakForROMFeedback feedback = settings.tweakForRom(romHandler); + if (feedback.isChangedStarter() && settings.getStartersMod() == Settings.StartersMod.CUSTOM) { + printWarning(bundle.getString("GUI.starterUnavailable")); + } + if (settings.isUpdatedFromOldVersion()) { + printWarning(bundle.getString("GUI.settingsFileOlder")); + } + } + + public static int invoke(String[] args) { + String settingsFilePath = null; + String sourceRomFilePath = null; + String outputRomFilePath = null; + boolean saveAsDirectory = false; + String updateFilePath = null; + boolean saveLog = false; + + List allowedFlags = Arrays.asList("-i", "-o", "-s", "-d", "-u", "-l", "--help"); + for (int i = 0; i < args.length; i++) { + if (allowedFlags.contains(args[i])) { + switch(args[i]) { + case "-i": + sourceRomFilePath = args[i + 1]; + break; + case "-o": + outputRomFilePath = args[i + 1]; + break; + case "-s": + settingsFilePath = args[i + 1]; + break; + case "-d": + saveAsDirectory = true; + break; + case "-u": + updateFilePath = args[i+1]; + break; + case "-l": + saveLog = true; + break; + case "--help": + printUsage(); + return 0; + default: + break; + } + } + } + + if (settingsFilePath == null || sourceRomFilePath == null || outputRomFilePath == null) { + printError("Missing required argument"); + CliRandomizer.printUsage(); + return 1; + + } + + // now we know we have the right number of args... + if (!new File(settingsFilePath).exists()) { + printError("Could not read settings file"); + CliRandomizer.printUsage(); + return 1; + } + + // check that everything is readable/writable as appropriate + if (!new File(sourceRomFilePath).exists()) { + printError("Could not read source ROM file"); + CliRandomizer.printUsage(); + return 1; + } + + // java will return false for a non-existent file, have to check the parent directory + if (!new File(outputRomFilePath).getAbsoluteFile().getParentFile().canWrite()) { + printError("Destination ROM path not writable"); + CliRandomizer.printUsage(); + return 1; + } + + boolean processResult = CliRandomizer.performDirectRandomization( + settingsFilePath, + sourceRomFilePath, + outputRomFilePath, + saveAsDirectory, + updateFilePath, + saveLog + ); + if (!processResult) { + printError("Randomization failed"); + CliRandomizer.printUsage(); + return 1; + } + return 0; + } + + private static void printError(String text) { + System.err.println("ERROR: " + text); + } + + private static void printWarning(String text) { + System.err.println("WARNING: " + text); + } + + private static void printUsage() { + System.err.println("Usage: java [-Xmx4096M] -jar PokeRandoZX.jar cli -s " + + "-i -o [-d][-u ][-l]"); + System.err.println("-d: Save 3DS game as directory (LayeredFS)"); + } +} diff --git a/src/com/pkrandom/config/Generation4.tbl b/src/com/pkrandom/config/Generation4.tbl new file mode 100755 index 0000000..1354430 --- /dev/null +++ b/src/com/pkrandom/config/Generation4.tbl @@ -0,0 +1,1149 @@ +0000=\x0000 +0001=\x0001 +0002=ぁ +0003=あ +0004=ぃ +0005=い +0006=ぅ +0007=う +0008=ぇ +0009=え +000A=ぉ +000B=お +000C=か +000D=が +000E=き +000F=ぎ +0010=く +0011=ぐ +0012=け +0013=げ +0014=こ +0015=ご +0016=さ +0017=ざ +0018=し +0019=じ +001A=す +001B=ず +001C=せ +001D=ぜ +001E=そ +001F=ぞ +0020=た +0021=だ +0022=ち +0023=ぢ +0024=っ +0025=つ +0026=づ +0027=て +0028=で +0029=と +002A=ど +002B=な +002C=に +002D=ぬ +002E=ね +002F=の +0030=は +0031=ば +0032=ぱ +0033=ひ +0034=び +0035=ぴ +0036=ふ +0037=ぶ +0038=ぷ +0039=へ +003A=べ +003B=ぺ +003C=ほ +003D=ぼ +003E=ぽ +003F=ま +0040=み +0041=む +0042=め +0043=も +0044=ゃ +0045=や +0046=ゅ +0047=ゆ +0048=ょ +0049=よ +004A=ら +004B=り +004C=る +004D=れ +004E=ろ +004F=わ +0050=を +0051=ん +0052=ァ +0053=ア +0054=ィ +0055=イ +0056=ゥ +0057=ウ +0058=ェ +0059=エ +005A=ォ +005B=オ +005C=カ +005D=ガ +005E=キ +005F=ギ +0060=ク +0061=グ +0062=ケ +0063=ゲ +0064=コ +0065=ゴ +0066=サ +0067=ザ +0068=シ +0069=ジ +006A=ス +006B=ズ +006C=セ +006D=ゼ +006E=ソ +006F=ゾ +0070=タ +0071=ダ +0072=チ +0073=ヂ +0074=ッ +0075=ツ +0076=ヅ +0077=テ +0078=デ +0079=ト +007A=ド +007B=ナ +007C=ニ +007D=ヌ +007E=ネ +007F=ノ +0080=ハ +0081=バ +0082=パ +0083=ヒ +0084=ビ +0085=ピ +0086=フ +0087=ブ +0088=プ +0089=ヘ +008A=ベ +008B=ペ +008C=ホ +008D=ボ +008E=ポ +008F=マ +0090=ミ +0091=ム +0092=メ +0093=モ +0094=ャ +0095=ヤ +0096=ュ +0097=ユ +0098=ョ +0099=ヨ +009A=ラ +009B=リ +009C=ル +009D=レ +009E=ロ +009F=ワ +00A0=ヲ +00A1=ン +00A2=0 +00A3=1 +00A4=2 +00A5=3 +00A6=4 +00A7=5 +00A8=6 +00A9=7 +00AA=8 +00AB=9 +00AC=A +00AD=B +00AE=C +00AF=D +00B0=E +00B1=F +00B2=G +00B3=H +00B4=I +00B5=J +00B6=K +00B7=L +00B8=M +00B9=N +00BA=O +00BB=P +00BC=Q +00BD=R +00BE=S +00BF=T +00C0=U +00C1=V +00C2=W +00C3=X +00C4=Y +00C5=Z +00C6=a +00C7=b +00C8=c +00C9=d +00CA=e +00CB=f +00CC=g +00CD=h +00CE=i +00CF=j +00D0=k +00D1=l +00D2=m +00D3=n +00D4=o +00D5=p +00D6=q +00D7=r +00D8=s +00D9=t +00DA=u +00DB=v +00DC=w +00DD=x +00DE=y +00DF=z +00E0= (224) +00E1=! +00E2=? +00E3=、 +00E4=。 +00E5=… +00E6=・ +00E7=/ +00E8=「 +00E9=」 +00EA=『 +00EB=』 +00EC=( +00ED=) +00EE=♂ +00EF=♀ +00F0=+ +00F1=ー +00F2=× +00F3=÷ +00F4== +00F5=~ +00F6=: +00F7=; +00F8=. +00F9=, +00FA=♠ +00FB=♣ +00FC=♥ +00FD=♦ +00FE=★ +00FF=◎ +0100=○ +0101=□ +0102=△ +0103=◇ +0104=@ +0105=♪ +0106=% +0107=☀ +0108=☁ +0109=☂ +010A=☃ +010B=\x010B +010C=\x010C +010D=\x010D +010E=\x010E +010F=⤴ +0110=⤵ +0111=\x0111 +0112=円 +0113=\x0113 +0114=\x0114 +0115=\x0115 +0116=✉ +0117=\x0117 +0118=\x0118 +0119=\x0119 +011A=\x011A +011B=← +011C=↑ +011D=↓ +011E=→ +011F=\x011F +0120=& +0121=0 +0122=1 +0123=2 +0124=3 +0125=4 +0126=5 +0127=6 +0128=7 +0129=8 +012A=9 +012B=A +012C=B +012D=C +012E=D +012F=E +0130=F +0131=G +0132=H +0133=I +0134=J +0135=K +0136=L +0137=M +0138=N +0139=O +013A=P +013B=Q +013C=R +013D=S +013E=T +013F=U +0140=V +0141=W +0142=X +0143=Y +0144=Z +0145=a +0146=b +0147=c +0148=d +0149=e +014A=f +014B=g +014C=h +014D=i +014E=j +014F=k +0150=l +0151=m +0152=n +0153=o +0154=p +0155=q +0156=r +0157=s +0158=t +0159=u +015A=v +015B=w +015C=x +015D=y +015E=z +015F=À +0160=Á +0161= +0162=\x0162 +0163=Ä +0164=\x0164 +0165=\x0165 +0166=Ç +0167=È +0168=É +0169=Ê +016A=Ë +016B=Ì +016C=Í +016D=Î +016E=Ï +016F=\x016F +0170=Ñ +0171=Ò +0172=Ó +0173=Ô +0174=\x0174 +0175=Ö +0176=× +0177=\x0177 +0178=Ù +0179=Ú +017A=Û +017B=Ü +017C=\x017C +017D=\x017D +017E=ß +017F=à +0180=á +0181=â +0182=\x0182 +0183=ä +0184=\x0184 +0185=\x0185 +0186=ç +0187=è +0188=é +0189=ê +018A=ë +018B=ì +018C=í +018D=î +018E=ï +018F=\x018F +0190=ñ +0191=ò +0192=ó +0193=ô +0194=\x0194 +0195=ö +0196=÷ +0197=\x0197 +0198=ù +0199=ú +019A=û +019B=ü +019C=\x019C +019D=\x019D +019E=\x019E +019F=Œ +01A0=œ +01A1=\x01A1 +01A2=\x01A2 +01A3=ª +01A4=º +01A5=ᵉʳ +01A6=ʳᵉ +01A7=ʳ +01A8=¥ +01A9=¡ +01AA=¿ +01AB=! +01AC=? +01AD=, +01AE=. +01AF=… +01B0=· +01B1=/ +01B2=‘ +01B3=' +01B3=’ +01B4=“ +01B5=” +01B6=„ +01B7=« +01B8=» +01B9=( +01BA=) +01BB=♂ +01BC=♀ +01BD=+ +01BE=- +01BF=* +01C0=# +01C1== +01C2=\and +01C3=~ +01C4=: +01C5=; +01C6=♠ +01C7=♣ +01C8=♥ +01C9=♦ +01CA=★ +01CB=◎ +01CC=○ +01CD=□ +01CE=△ +01CF=◇ +01D0=@ +01D1=♪ +01D2=% +01D3=☀ +01D4=☁ +01D5=☂ +01D6=☃ +01D7=\x01D7 +01D8=\x01D8 +01D9=\x01D9 +01DA=\x01DA +01DB=⤴ +01DC=⤵ +01DD=\x01DD +01DE= +01DF=\x01DF +01E0=[PK] +01E1=[MN] +0401=가 +0405=갈 +0409=갑 +040D=강 +0413=개 +041B=갱 +041C=갸 +0425=거 +042B=검 +0434=게 +0436=겔 +0439=겟 +044D=고 +044F=곤 +0451=골 +0455=곰 +0458=공 +0462=광 +0469=괴 +0476=구 +0478=군 +047A=굴 +048B=귀 +0495=그 +0497=근 +0499=글 +04A0=기 +04A9=깅 +04AC=까 +04AD=깍 +04B2=깜 +04B3=깝 +04B8=깨 +04C5=꺽 +04CA=껍 +04DB=꼬 +04DF=꼴 +04E5=꽃 +04F5=꾸 +04F8=꿀 +0524=나 +0529=날 +0535=내 +0539=냄 +0543=냥 +0544=너 +0551=네 +0565=노 +056A=놈 +056D=농 +0580=뇽 +0581=누 +0583=눈 +0598=느 +05A3=늪 +05A7=니 +05B1=다 +05B2=닥 +05B4=단 +05BB=담 +05C3=대 +05CD=더 +05CE=덕 +05D8=덩 +05DB=데 +05DE=델 +05EB=도 +05EC=독 +05ED=돈 +05EF=돌 +05F5=동 +0604=두 +0606=둔 +0608=둠 +060B=둥 +061B=드 +061F=들 +0626=디 +0628=딘 +062A=딜 +062C=딥 +0632=딱 +0634=딸 +065B=또 +0665=뚜 +0666=뚝 +0668=뚤 +0687=라 +0688=락 +0689=란 +068A=랄 +068F=랑 +0693=래 +0695=랜 +0697=램 +0698=랩 +06A1=러 +06A2=럭 +06A3=런 +06A9=렁 +06AB=레 +06AD=렌 +06B1=렛 +06B4=력 +06C0=로 +06C1=록 +06C3=롤 +06C5=롭 +06C7=롱 +06D8=룡 +06D9=루 +06DD=룸 +06EC=륙 +06F3=르 +06FE=리 +0700=린 +0701=릴 +0702=림 +0705=링 +0706=마 +0708=만 +070B=말 +070E=맘 +0711=망 +0715=매 +0717=맨 +0724=먹 +072E=메 +0740=모 +0743=몬 +0749=몽 +0759=무 +075E=물 +0764=뭉 +0770=뮤 +077A=미 +077E=밀 +0787=바 +078D=발 +0791=밤 +0794=방 +0796=배 +079C=뱃 +07A4=버 +07A5=벅 +07A6=번 +07AA=범 +07AF=베 +07B3=벨 +07BC=별 +07C4=보 +07C5=복 +07C8=볼 +07DA=부 +07DB=북 +07DC=분 +07DE=불 +07E1=붐 +07E4=붕 +07F0=뷰 +07F6=브 +07F9=블 +07FD=비 +07FF=빈 +0800=빌 +081F=뻐 +0831=뽀 +083B=뿌 +083E=뿔 +0841=뿡 +0844=쁘 +0849=삐 +0851=사 +0854=산 +0859=삼 +085D=상 +085F=새 +0860=색 +0868=샤 +0879=선 +087B=설 +0880=섯 +0882=성 +0884=세 +0885=섹 +0887=셀 +089A=소 +089D=손 +089E=솔 +08A0=솜 +08A3=송 +08BE=수 +08C2=술 +08C6=숭 +08CC=쉐 +08CF=쉘 +08DA=슈 +08E0=스 +08E3=슬 +08E9=시 +08EA=식 +08EB=신 +08ED=실 +0905=쌩 +0909=썬 +0914=쏘 +0936=쓰 +0942=씨 +094A=아 +094C=안 +094F=알 +0953=암 +095A=애 +095C=앤 +095F=앱 +0963=야 +0972=어 +0977=얼 +097F=엉 +0983=에 +0986=엘 +0987=엠 +098B=여 +098E=연 +0992=염 +0997=영 +09A2=오 +09A4=온 +09AD=옹 +09AF=와 +09B2=왈 +09B7=왕 +09C6=요 +09CD=용 +09CE=우 +09D1=울 +09D4=움 +09DA=원 +09E9=윈 +09EF=유 +09F0=육 +09F1=윤 +09FB=을 +09FD=음 +0A0C=이 +0A0E=인 +0A0F=일 +0A13=임 +0A14=입 +0A17=잉 +0A19=잎 +0A1A=자 +0A21=잠 +0A25=장 +0A27=재 +0A30=쟈 +0A36=쟝 +0A3A=저 +0A3C=전 +0A3F=점 +0A44=제 +0A47=젤 +0A4C=져 +0A54=조 +0A72=죤 +0A74=주 +0A7D=중 +0A88=쥬 +0A8C=즈 +0A94=지 +0A95=직 +0A96=진 +0A98=질 +0AAB=짱 +0AEA=찌 +0AF3=차 +0AF8=참 +0B06=챙 +0B07=챠 +0B10=철 +0B16=체 +0B24=초 +0B2B=총 +0B37=쵸 +0B40=충 +0B4C=츄 +0B51=츠 +0B59=치 +0B5D=칠 +0B5F=침 +0B63=카 +0B65=칸 +0B6B=캐 +0B73=캥 +0B80=컹 +0B81=케 +0B83=켄 +0B84=켈 +0B92=코 +0B94=콘 +0B95=콜 +0BA5=쿠 +0BA7=쿤 +0BB5=퀸 +0BBF=크 +0BC6=키 +0BC9=킬 +0BCD=킹 +0BCE=타 +0BCF=탁 +0BD7=탕 +0BD8=태 +0BE0=탱 +0BE3=터 +0BE5=턴 +0BE8=텀 +0BEC=텅 +0BED=테 +0BFA=토 +0BFB=톡 +0BFC=톤 +0BFF=톱 +0C01=통 +0C0B=투 +0C22=트 +0C28=틈 +0C30=티 +0C31=틱 +0C33=틸 +0C38=파 +0C3E=팜 +0C42=팡 +0C44=패 +0C46=팬 +0C4C=팽 +0C4F=퍼 +0C50=퍽 +0C52=펄 +0C58=페 +0C5E=펫 +0C6B=포 +0C6C=폭 +0C6E=폴 +0C72=퐁 +0C7C=푸 +0C80=풀 +0C85=풍 +0C93=프 +0C95=플 +0C99=피 +0C9A=픽 +0CA0=핑 +0CA1=하 +0CA3=한 +0CA8=핫 +0CAA=해 +0CAC=핸 +0CB7=헌 +0CBE=헤 +0CC1=헬 +0CCE=형 +0CD3=호 +0CDB=홍 +0CDD=화 +0CF4=후 +0D14=흉 +0D17=흔 +0D27=히 +0402=각 +0403=간 +0408=감 +0411=갚 +0414=객 +0429=걸 +042C=겁 +043D=격 +0441=결 +0446=경 +045C=관 +0471=교 +0479=굳 +0485=권 +049B=금 +04A4=길 +04A6=김 +04A8=깃 +04CB=껏 +04FA=꿈 +04FF=꿔 +0503=꿰 +051C=끼 +0527=난 +0534=낳 +053D=냉 +0548=널 +0557=넷 +055E=념 +0566=녹 +0567=논 +0568=놀 +059A=는 +059B=늘 +05A8=닉 +05B6=달 +05BF=당 +05C5=댄 +05D0=던 +0605=둑 +060D=뒀 +0624=등 +062D=딧 +0631=따 +0639=땅 +063B=때 +0647=떨 +0669=뚫 +066D=뛰 +068B=람 +06AC=렉 +06B3=려 +06BB=령 +06BC=례 +06D3=료 +06EB=류 +06F7=름 +06FD=릎 +06FF=릭 +0704=릿 +0707=막 +071D=맹 +0723=머 +0726=멀 +072B=멍 +0734=멧 +0739=면 +073A=멸 +073D=명 +0741=목 +0746=몸 +075A=묵 +075B=묶 +075C=문 +077B=믹 +077C=민 +0788=박 +078B=반 +078C=받 +0790=밟 +0792=밥 +079A=뱀 +07A8=벌 +07AB=법 +07B1=벤 +07BA=벽 +07BB=변 +07C7=본 +07C9=봄 +07CC=봉 +0802=빔 +0805=빙 +0807=빛 +081E=뺨 +082A=뼈 +0835=뽐 +083F=뿜 +0845=쁜 +0856=살 +0867=생 +0870=섀 +0875=서 +0876=석 +089B=속 +08B6=쇼 +08BC=숏 +08C0=순 +08C1=숟 +08C3=숨 +08D2=쉬 +08E6=습 +08E8=승 +08EE=싫 +08EF=심 +08F4=싸 +094B=악 +0954=압 +0959=앞 +095B=액 +0962=앵 +096B=양 +0973=억 +0974=언 +097A=엄 +097B=업 +098C=역 +098F=열 +099B=예 +09A3=옥 +09D0=운 +09D7=웅 +09D8=워 +09E0=웨 +09E5=웹 +09E7=위 +09F8=으 +09FA=은 +0A07=의 +0A1B=작 +0A2B=잼 +0A2F=쟁 +0A3B=적 +0A3D=절 +0A42=정 +0A46=젠 +0A48=젬 +0A75=죽 +0A81=쥐 +0A8E=즌 +0A9B=집 +0A9C=짓 +0A9E=짖 +0AA1=짜 +0AA2=짝 +0AAC=째 +0AC5=쪼 +0AEB=찍 +0AEF=찝 +0AFE=채 +0B02=챔 +0B0D=처 +0B0F=천 +0B15=청 +0B1E=쳐 +0B29=촙 +0B30=최 +0B39=추 +0B3A=축 +0B3C=출 +0B3D=춤 +0B45=취 +0B66=칼 +0B77=커 +0B7B=컬 +0B7E=컷 +0B96=콤 +0BA0=쾌 +0BB3=퀴 +0BC2=클 +0BC7=킥 +0BD0=탄 +0BD1=탈 +0BD3=탐 +0BD9=택 +0BE6=털 +0BEE=텍 +0BF0=텔 +0BFE=톰 +0C16=튀 +0C1D=튜 +0C3B=판 +0C3C=팔 +0C45=팩 +0C51=펀 +0C53=펌 +0C6D=폰 +0C82=품 +0C96=픔 +0C9B=핀 +0C9C=필 +0CA4=할 +0CA5=핥 +0CA6=함 +0CA7=합 +0CA9=항 +0CAE=햄 +0CB0=햇 +0CB4=향 +0CC9=혈 +0CCF=혜 +0CD4=혹 +0CD5=혼 +0CD6=홀 +0CDF=환 +0CE8=회 +0CEF=효 +0D06=휘 +0D0B=휩 +0D16=흑 +0D1B=흙 +0D1D=흡 +0D21=희 +0D22=흰 +0D2B=힘 +0416=갤 +0427=건 +0448=계 +045A=과 +047B=굵 +0492=규 +049C=급 +04A7=깁 +0512=끈 +0526=낚 +052A=낡 +052C=남 +05A1=능 +05A9=닌 +05BD=닷 +0673=뜨 +0680=띠 +069B=랭 +06CC=뢰 +06DC=룰 +06F5=른 +0710=맛 +0712=맞 +0716=맥 +071A=맵 +0730=멘 +0731=멜 +0732=멤 +0797=백 +0798=밴 +07C0=병 +080B=빨 +0863=샘 +088D=셔 +0890=셜 +08B0=쇠 +08BF=숙 +08C9=숲 +08DF=슝 +08F1=싯 +0921=쐐 +094E=않 +0964=약 +097C=없 +0A16=있 +0A1C=잔 +0A1F=잘 +0A5F=좋 +0A77=줄 +0A9D=징 +0AFC=창 +0B12=첩 +0B5B=친 +0B67=캄 +0B87=켓 +0BB4=퀵 +0BC1=큰 +0C24=튼 +0C26=틀 +0C59=펙 +0C5A=펜 +0C61=편 +0C66=평 +0C77=표 +0C7E=푼 +0CA2=학 +0CB2=행 +0CB5=허 +0CBA=험 +0CE0=활 +0D2A=힐 +E000=\n +25BC=\p +25BD=\l \ No newline at end of file diff --git a/src/com/pkrandom/config/Generation5.tbl b/src/com/pkrandom/config/Generation5.tbl new file mode 100755 index 0000000..8c38355 --- /dev/null +++ b/src/com/pkrandom/config/Generation5.tbl @@ -0,0 +1,46 @@ +2467=× +2468=÷ +246C=… +246D=♂ +246E=♀ +246F=♠ +2470=♣ +2471=♥ +2472=♦ +2473=★ +2474=◎ +2475=○ +2476=□ +2477=△ +2478=◇ +2479=♪ +247A=☀ +247B=☁ +247D=☂ +21D2=\[angry] +21D4=\[turnup] +2200=\[turndown] +2203=\[zzz] +2227=\[=)] +2228=\[=D] +2460=\[:)] +2461=\[:D] +2462=\[>:O] +2463=\[:(] +2464=\[turnup2] +2465=\[turndown2] +2466=\[zzz2] +2469=\[er] +246A=\[re] +246B=\[corner] +247E=\[:)s] +247F=\[:Ds] +2480=\[>:Os] +2481=\[:(s] +2482=\[turnups] +2483=\[turndowns] +2484=\[zzzs] +2485=\[e] +2486=\[PK] +2487=\[MN] +FFE2=\[>:O2] diff --git a/src/com/pkrandom/config/customnames.rncn b/src/com/pkrandom/config/customnames.rncn new file mode 100644 index 0000000..7f1caff Binary files /dev/null and b/src/com/pkrandom/config/customnames.rncn differ diff --git a/src/com/pkrandom/config/gameboy_jpn.tbl b/src/com/pkrandom/config/gameboy_jpn.tbl new file mode 100644 index 0000000..e419887 --- /dev/null +++ b/src/com/pkrandom/config/gameboy_jpn.tbl @@ -0,0 +1,180 @@ +05=ガ +06=ギ +07=グ +08=ゲ +09=ゴ +0A=ザ +0B=ジ +0C=ズ +0D=ゼ +0E=ゾ +0F=ダ +10=ヂ +11=ヅ +12=デ +13=ド +19=バ +1A=ビ +1B=ブ +1C=ボ +26=が +27=ぎ +28=ぐ +29=げ +2A=ご +2B=ざ +2C=じ +2D=ず +2E=ぜ +2F=ぞ +30=だ +31=ぢ +32=づ +33=で +34=ど +3A=ば +3B=び +3C=ぶ +3D=べ +3E=ぼ +40=パ +41=ピ +42=プ +43=ポ +44=ぱ +45=ぴ +46=ぷ +47=ぺ +48=ぽ +54=ポケモン +7F= +80=ア +81=イ +82=ウ +83=エ +84=オ +85=カ +86=キ +87=ク +88=ケ +89=コ +8A=サ +8B=シ +8C=ス +8D=セ +8E=ソ +8F=タ +90=チ +91=ツ +92=テ +93=ト +94=ナ +95=ニ +96=ヌ +97=ネ +98=ノ +99=ハ +9A=ヒ +9B=フ +9C=ホ +9D=マ +9E=ミ +9F=ム +A0=メ +A1=モ +A2=ヤ +A3=ユ +A4=ヨ +A5=ラ +A6=ル +A7=レ +A8=ロ +A9=ワ +AA=ヲ +AB=ン +AC=ッ +AD=ャ +AE=ュ +AF=ョ +B0=ィ +B1=あ +B2=い +B3=う +B4=え +B5=お +B6=か +B7=き +B8=く +B9=け +BA=こ +BB=さ +BC=し +BD=す +BE=せ +BF=そ +C0=た +C1=ち +C2=つ +C3=て +C4=と +C5=な +C6=に +C7=ぬ +C8=ね +C9=の +CA=は +CB=ひ +CC=ふ +CD=へ +CE=ほ +CF=ま +D0=み +D1=む +D2=め +D3=も +D4=や +D5=ゆ +D6=よ +D7=ら +D8=り +D9=る +DA=れ +DB=ろ +DC=わ +DD=を +DE=ん +DF=っ +E0=ゃ +E1=ゅ +E2=ょ +E3=ー +E4=。 +E4=゚ +E5=゙​ +E6=? +E7=! +E8=。 +E9=ァ +EB=ェ +EF=♂ +F0=円 +F1=× +F2=[.] +F3=/ +F4=ォ +F5=♀ +F6=0 +F7=1 +F8=2 +F9=3 +FA=4 +FB=5 +FC=6 +FD=7 +FE=8 +FF=9 +4F=\n +51=\p +55=\l +57=\e +58=\r \ No newline at end of file diff --git a/src/com/pkrandom/config/gba_english.tbl b/src/com/pkrandom/config/gba_english.tbl new file mode 100755 index 0000000..82f4e3a --- /dev/null +++ b/src/com/pkrandom/config/gba_english.tbl @@ -0,0 +1,159 @@ +00= +01=À +02=Á +03= +04=Ç +05=È +06=É +07=Ê +08=Ë +09=Ì +0B=Î +0C=Ï +0D=Ò +0E=Ó +0F=Ô +10=Æ +11=Ù +12=Ú +13=Û +14=Ñ +15=ß +16=à +17=á +19=ç +1A=è +1B=é +1C=ê +1D=ë +1E=ì +20=î +21=ï +22=ò +23=ó +24=ô +25=æ +26=ù +27=ú +28=û +29=ñ +2A=º +2B=ª +2C=· +2D=& +2E=+ +34=[Lv] +35== +36=; +51=¿ +52=¡ +53=[PK] +54=[MN] +55=[PO] +56=[Ke] +57=[BL] +58=[OC] +59=[K] +5A=Í +5B=% +5C=( +5D=) +68=â +6F=í +79=[U] +7A=[D] +7B=[L] +7C=[R] +A1=0 +A2=1 +A3=2 +A4=3 +A5=4 +A6=5 +A7=6 +A8=7 +A9=8 +AA=9 +AB=! +AC=? +AD=. +AE=- +AF=· +B0=… +B1=“ +B2=” +B3=‘ +B4=’ +B5=♂ +B6=♀ +B7=$ +B8=, +B9=[x] +BA=/ +BB=A +BC=B +BD=C +BE=D +BF=E +C0=F +C1=G +C2=H +C3=I +C4=J +C5=K +C6=L +C7=M +C8=N +C9=O +CA=P +CB=Q +CC=R +CD=S +CE=T +CF=U +D0=V +D1=W +D2=X +D3=Y +D4=Z +D5=a +D6=b +D7=c +D8=d +D9=e +DA=f +DB=g +DC=h +DD=i +DE=j +DF=k +E0=l +E1=m +E2=n +E3=o +E4=p +E5=q +E6=r +E7=s +E8=t +E9=u +EA=v +EB=w +EC=x +ED=y +EE=z +EF=[>] +F0=: +F1=Ä +F2=Ö +F3=Ü +F4=ä +F5=ö +F6=ü +F7=[u] +F8=[d] +F9=[l] +FA=\l +FB=\p +FC=\c +FE=\n diff --git a/src/com/pkrandom/config/gba_jpn.tbl b/src/com/pkrandom/config/gba_jpn.tbl new file mode 100644 index 0000000..90d1b94 --- /dev/null +++ b/src/com/pkrandom/config/gba_jpn.tbl @@ -0,0 +1,254 @@ +00= +01=あ +02=い +03=う +04=え +05=お +06=か +07=き +08=く +09=け +0A=こ +0B=さ +0C=し +0D=す +0E=せ +0F=そ +10=た +11=ち +12=つ +13=て +14=と +15=な +16=に +17=ぬ +18=ね +19=の +1A=は +1B=ひ +1C=ふ +1D=へ +1E=ほ +1F=ま +20=み +21=む +22=め +23=も +24=や +25=ゆ +26=よ +27=ら +28=り +29=る +2A=れ +2B=ろ +2C=わ +2D=を +2E=ん +2F=ぁ +30=ぃ +31=ぅ +32=ぇ +33=ぉ +34=ゃ +35=ゅ +36=ょ +37=が +38=ぎ +39=ぐ +3A=げ +3B=ご +3C=ざ +3D=じ +3E=ず +3F=ぜ +40=ぞ +41=だ +42=ぢ +43=づ +44=で +45=ど +46=ば +47=び +48=ぶ +49=べ +4A=ぼ +4B=ぱ +4C=ぴ +4D=ぷ +4E=ぺ +4F=ぽ +50=っ +51=ア +52=イ +53=ウ +54=エ +55=オ +56=カ +57=キ +58=ク +59=ケ +5A=コ +5B=サ +5C=シ +5D=ス +5E=セ +5F=ソ +60=タ +61=チ +62=ツ +63=テ +64=ト +65=ナ +66=ニ +67=ヌ +68=ネ +69=ノ +6A=ハ +6B=ヒ +6C=フ +6D=ヘ +6E=ホ +6F=マ +70=ミ +71=ム +72=メ +73=モ +74=ヤ +75=ユ +76=ヨ +77=ラ +78=リ +79=ル +7A=レ +7B=ロ +7C=ワ +7D=ヲ +7E=ン +7F=ァ +80=ィ +81=ゥ +82=ェ +83=ォ +84=ャ +85=ュ +86=ョ +87=ガ +88=ギ +89=グ +8A=ゲ +8B=ゴ +8C=ザ +8D=ジ +8E=ズ +8F=ゼ +90=ゾ +91=ダ +92=ヂ +93=ヅ +94=デ +95=ド +96=バ +97=ビ +98=ブ +99=ベ +9A=ボ +9B=パ +9C=ピ +9D=プ +9E=ペ +9F=ポ +A0=ッ +A1=0 +A2=1 +A3=2 +A4=3 +A5=4 +A6=5 +A7=6 +A8=7 +A9=8 +AA=9 +AB=! +AC=? +AD=. +AE=ー +AF=キ +B0=封 +B1=ォ +B2=サ +B3=< +B4=> +B5=♂ +B6=♀ +B7=$ +B8=, +B9=* +BA=/ +BB=A +BC=B +BD=C +BE=D +BF=E +C0=F +C1=G +C2=H +C3=I +C4=J +C5=K +C6=L +C7=M +C8=N +C9=O +CA=P +CB=Q +CC=R +CD=S +CE=T +CF=U +D0=V +D1=W +D2=X +D3=Y +D4=Z +D5=a +D6=b +D7=c +D8=d +D9=e +DA=f +DB=g +DC=h +DD=i +DE=j +DF=k +E0=l +E1=m +E2=n +E3=o +E4=p +E5=q +E6=r +E7=s +E8=t +E9=u +EA=v +EB=w +EC=x +ED=y +EE=z +EF=[>] +F0=: +F1=ト +F2=ヨ +F3=ワ +F4=ä +F5=ö +F6=ü +F7=[u] +F8=[d] +F9=[l] +FA=\l +FB=\p +FC=\c +FE=\n diff --git a/src/com/pkrandom/config/gen1_offsets.ini b/src/com/pkrandom/config/gen1_offsets.ini new file mode 100755 index 0000000..2598794 --- /dev/null +++ b/src/com/pkrandom/config/gen1_offsets.ini @@ -0,0 +1,1169 @@ +[Red (U)] +Game=POKEMON RED +Version=0 +NonJapanese=1 +Type=RB +ExtraTableFile=rby_english +BWXPTweak=bwexp/rb_en_bwxp +XAccNerfTweak=rb_en_xaccnerf +CritRateTweak=rb_en_critrate +InternalPokemonCount=190 +PokedexOrder=0x41024 +PokemonNamesOffset=0x1C21E +PokemonNamesLength=10 +PokemonStatsOffset=0x383DE +MewStatsOffset=0x425B +WildPokemonTableOffset=0xCEEB +OldRodOffset=0xE252 +GoodRodOffset=0xE27F +SuperRodTableOffset=0xE919 +MapNameTableOffset=0x71313 +MoveCount=165 +MoveDataOffset=0x38000 +MoveNamesOffset=0xB0000 +ItemNamesOffset=0x472B +TypeEffectivenessOffset=0x3E474 +PokemonMovesetsTableOffset=0x3B05C +PokemonMovesetsDataSize=0x814 +PokemonMovesetsExtraSpaceOffset=0x3BBE6 +StarterOffsets1=[0x1D126, 0x1CC84, 0x1D10E, 0x39CF8, 0x50FB3, 0x510DD] +StarterOffsets2=[0x1D104, 0x19591, 0x1CC88, 0x1CDC8, 0x1D11F, 0x50FAF, 0x510D9, 0x51CAF, 0x6060E, 0x61450, 0x75F9E] +StarterOffsets3=[0x1D115, 0x19599, 0x1CDD0, 0x1D130, 0x39CF2, 0x50FB1, 0x510DB, 0x51CB7, 0x60616, 0x61458, 0x75FA6] +PatchPokedex=1 +CanChangeStarterText=1 +CanChangeTrainerText=1 +StarterTextOffsets=[0x94E07, 0x94E30, 0x94E58] +StarterPokedexOnOffset=0x5C0DC +StarterPokedexOffOffset=0x5C0E6 +StarterPokedexBranchOffset=0x5E000 +PokedexRamOffset=0xD2F7 +TrainerDataTableOffset=0x39D3B +TrainerDataClassCounts=[0, 13, 14, 18, 8, 9, 24, 7, 12, 14, 15, 9, 3, 0, 11, 15, 9, 7, 15, 4, 2, 8, 6, 17, 9, 9, 3, 0, 13, 3, 41, 10, 8, 1, 1, 1, 1, 1, 1, 1, 1, 5, 12, 3, 1, 24, 1, 1] +ExtraTrainerMovesTableOffset=0x39D32 +GymLeaderMovesTableOffset=0x39D23 +TMMovesOffset=0x13773 +TrainerClassNamesOffsets=[0x27EC2, 0x399FF] +IntroPokemonOffset=0x616D +IntroCryOffset=0x1C73 +MapBanks=0xC23D +MapAddresses=0x01AE +SpecialMapList=0x46A40 +SpecialMapPointerTable=0x46A96 +HiddenItemRoutine=0x76688 +TradeTableOffset=0x71B7B +TradeTableSize=10 +TradeNameLength=11 +TradesUnused=[2] +TextDelayFunctionOffset=0x38D3 +PCPotionOffset=0x6134 +CatchingTutorialMonOffset=0x19085 +MonPaletteIndicesOffset=0x725C8 +SGBPalettesOffset=0x72660 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x49320], Level=[0x4931F]} // Magikarp +StaticPokemon{}={Species=[0x1DD49], Level=[0x1DD48]} // Eevee +StaticPokemon{}={Species=[0x5CF17], Level=[0x5CF2F]} // Hitmonlee +StaticPokemon{}={Species=[0x5CF5F], Level=[0x5CF77]} // Hitmonchan +StaticPokemon{}={Species=[0x51DAD], Level=[0x51DAC]} // Lapras +StaticPokemon{}={Species=[0x61068], Level=[0x75DB5]} // Omanyte +StaticPokemon{}={Species=[0x6106C], Level=[0x75DB5]} // Kabuto +StaticPokemon{}={Species=[0x61064], Level=[0x75DB5]} // Aerodactyl +StaticPokemon{}={Species=[0x59630, 0x61BEB], Level=[0x59635]} // Snorlax 1 +StaticPokemon{}={Species=[0x59970], Level=[0x59975]} // Snorlax 2 +StaticPokemon{}={Species=[0x1E3D5], Level=[0x1E3D6]} // Voltorb 1 +StaticPokemon{}={Species=[0x1E3DD], Level=[0x1E3DE]} // Voltorb 2 +StaticPokemon{}={Species=[0x1E3E5], Level=[0x1E3E6]} // Voltorb 3 +StaticPokemon{}={Species=[0x1E3F5], Level=[0x1E3F6]} // Voltorb 4 +StaticPokemon{}={Species=[0x1E3FD], Level=[0x1E3FE]} // Voltorb 5 +StaticPokemon{}={Species=[0x1E40D], Level=[0x1E40E]} // Voltorb 6 +StaticPokemon{}={Species=[0x1E3ED], Level=[0x1E3EE]} // Electrode 1 +StaticPokemon{}={Species=[0x1E405], Level=[0x1E406]} // Electrode 2 +StaticPokemon{}={Species=[0x468A8, 0x468E8, 0x5DB9E], Level=[0x468E9]} // Articuno +StaticPokemon{}={Species=[0x1E3B5, 0x1E415], Level=[0x1E416]} // Zapdos +StaticPokemon{}={Species=[0x518C0, 0x51963], Level=[0x51964]} // Moltres +StaticPokemon{}={Species=[0x45F2C, 0x45F44], Level=[0x45F45]} // Mewtwo +StaticPokemon{}={Species=[0x52859, 0x5298A], Level=[0x5298B]} // Abra +StaticPokemon{}={Species=[0x5285A, 0x5298C], Level=[0x5298D]} // Clefairy +StaticPokemon{}={Species=[0x5285B, 0x5298E], Level=[0x5298F]} // Nidorina +StaticPokemon{}={Species=[0x52864, 0x52990], Level=[0x52991]} // Dratini +StaticPokemon{}={Species=[0x52865, 0x52992], Level=[0x52993]} // Scyther +StaticPokemon{}={Species=[0x52866, 0x52994], Level=[0x52995]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD6F4, 0x3EF9A, 0x58DE4, 0x60B33, 0x60C0A, 0x708E1], Level=[0x60B38]} +TMText[]=[6,0x0A0100,\pTM06 contains\n%m!\e] +TMText[]=[11,0x98A7C,TM11 teaches\n%m!\e] +TMText[]=[13,0x9CC22,contains\n%m!\e] +TMText[]=[18,0x9C86F,TM18 is\n%m!\e] +TMText[]=[21,0x9D521,\pTM21 contains\n%m.\e] +TMText[]=[24,0x9C0F6,\pTM24 contains\n%m!\e] +TMText[]=[27,0x96096,\pTM27 is\n%m!\e] +TMText[]=[28,0x9875D,Those miserable\nROCKETs!\pLook what they\ndid here!\pThey stole a TM\nfor teaching\l[POKé]MON how to\l%m!\e] +TMText[]=[28,0x987E3,I figure what's\nlost is lost!\pI decided to get\n%m\lwithout a TM!\e] +TMText[]=[29,0xA253F,TM29 is\n%m!\e] +TMText[]=[31,0xA168A,\pTM31 contains my\nfavorite,\l%m!\e] +TMText[]=[34,0x980C1,\pA TM contains a\ntechnique that\lcan be taught to\l[POKé]MON!\pA TM is good only\nonce! So when you\luse one to teach\la new technique,\lpick the [POKé]MON\lcarefully!\pTM34 contains\n%m!\e] +TMText[]=[36,0x824CA,TM36 is\n%m!\e] +TMText[]=[38,0xA09BD,\pTM38 contains\n%m!\e] +TMText[]=[39,0x8C8DA,TM39 is the move\n%m.\e] +TMText[]=[41,0xA5B6F,TM41 teaches\n%m!\pMany [POKé]MON\ncan use it!\e] +TMText[]=[42,0xA46AE,TM42 contains\n%m...\e] +TMText[]=[46,0xA1DE1,\pTM46 is\n%m!\e] +TMText[]=[48,0x9CCAD,contains\n%m!\e] +TMText[]=[49,0x9CD31,\pTM49 is\n%m!\e] +CRC32=9F7FDD53 + +[Blue (U)] +Game=POKEMON BLUE +Version=0 +NonJapanese=1 +Type=RB +CopyTMText=1 +CopyFrom=Red (U) +BWXPTweak=bwexp/rb_en_bwxp +XAccNerfTweak=rb_en_xaccnerf +CritRateTweak=rb_en_critrate +HiddenItemRoutine=0x76689 +StarterOffsets2=[0x1D104, 0x19591, 0x1CC88, 0x1CDC8, 0x1D11F, 0x50FAF, 0x510D9, 0x51CAF, 0x6060E, 0x61450, 0x75F9F] +StarterOffsets3=[0x1D115, 0x19599, 0x1CDD0, 0x1D130, 0x39CF2, 0x50FB1, 0x510DB, 0x51CB7, 0x60616, 0x61458, 0x75FA7] +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x49320], Level=[0x4931F]} // Magikarp +StaticPokemon{}={Species=[0x1DD49], Level=[0x1DD48]} // Eevee +StaticPokemon{}={Species=[0x5CF17], Level=[0x5CF2F]} // Hitmonlee +StaticPokemon{}={Species=[0x5CF5F], Level=[0x5CF77]} // Hitmonchan +StaticPokemon{}={Species=[0x51DAD], Level=[0x51DAC]} // Lapras +StaticPokemon{}={Species=[0x61068], Level=[0x75DB6]} // Omanyte +StaticPokemon{}={Species=[0x6106C], Level=[0x75DB6]} // Kabuto +StaticPokemon{}={Species=[0x61064], Level=[0x75DB6]} // Aerodactyl +StaticPokemon{}={Species=[0x59630, 0x61BEB], Level=[0x59635]} // Snorlax 1 +StaticPokemon{}={Species=[0x59970], Level=[0x59975]} // Snorlax 2 +StaticPokemon{}={Species=[0x1E3D5], Level=[0x1E3D6]} // Voltorb 1 +StaticPokemon{}={Species=[0x1E3DD], Level=[0x1E3DE]} // Voltorb 2 +StaticPokemon{}={Species=[0x1E3E5], Level=[0x1E3E6]} // Voltorb 3 +StaticPokemon{}={Species=[0x1E3F5], Level=[0x1E3F6]} // Voltorb 4 +StaticPokemon{}={Species=[0x1E3FD], Level=[0x1E3FE]} // Voltorb 5 +StaticPokemon{}={Species=[0x1E40D], Level=[0x1E40E]} // Voltorb 6 +StaticPokemon{}={Species=[0x1E3ED], Level=[0x1E3EE]} // Electrode 1 +StaticPokemon{}={Species=[0x1E405], Level=[0x1E406]} // Electrode 2 +StaticPokemon{}={Species=[0x468A8, 0x468E8, 0x5DB9E], Level=[0x468E9]} // Articuno +StaticPokemon{}={Species=[0x1E3B5, 0x1E415], Level=[0x1E416]} // Zapdos +StaticPokemon{}={Species=[0x518C0, 0x51963], Level=[0x51964]} // Moltres +StaticPokemon{}={Species=[0x45F2C, 0x45F44], Level=[0x45F45]} // Mewtwo +StaticPokemon{}={Species=[0x52859, 0x5298A], Level=[0x5298B]} // Abra +StaticPokemon{}={Species=[0x5285A, 0x5298C], Level=[0x5298D]} // Clefairy +StaticPokemon{}={Species=[0x5285B, 0x5298E], Level=[0x5298F]} // Nidorino +StaticPokemon{}={Species=[0x52864, 0x52990], Level=[0x52991]} // Pinsir +StaticPokemon{}={Species=[0x52865, 0x52992], Level=[0x52993]} // Dratini +StaticPokemon{}={Species=[0x52866, 0x52994], Level=[0x52995]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD6F4, 0x3EF9A, 0x58DE4, 0x60B33, 0x60C0A, 0x708E1], Level=[0x60B38]} +CRC32=D6DA8A1A + +[Yellow (U)] +Game=POKEMON YELLOW +Version=0 +NonJapanese=1 +Type=Yellow +CopyFrom=Red (U) +BWXPTweak=bwexp/yellow_en_bwxp +XAccNerfTweak=yellow_en_xaccnerf +CritRateTweak=yellow_en_critrate +PokedexOrder=0x410B1 +PokemonNamesOffset=0xE8000 +MewStatsOffset=0 +MoveNamesOffset=0xBC000 +ItemNamesOffset=0x45B7 +WildPokemonTableOffset=0xCB95 +OldRodOffset=0xE0FF +GoodRodOffset=0xE12C +SuperRodTableOffset=0xF5EDA +MapNameTableOffset=0x7139C +TypeEffectivenessOffset=0x3E5FA +PokemonMovesetsTableOffset=0x3B1E5 +PokemonMovesetsDataSize=0xC9F +PokemonMovesetsExtraSpaceOffset=0 +StarterOffsets1=[0x18F19, 0x1CB41, 0x1CB66] +StarterOffsets2=[0x3A28A] +TrainerDataTableOffset=0x39DD1 +TrainerDataClassCounts=[0, 14, 15, 19, 8, 10, 25, 7, 12, 14, 15, 9, 3, 0, 11, 15, 9, 7, 15, 4, 2, 8, 6, 17, 9, 3, 3, 0, 13, 3, 49, 10, 8, 1, 1, 1, 1, 1, 1, 1, 1, 5, 10, 3, 1, 24, 1, 1] +ExtraTrainerMovesTableOffset=0x39C6B +GymLeaderMovesTableOffset=0 +TMMovesOffset=0x1232D +TrainerClassNamesOffsets=[0x27E77, 0x3997E] +IntroPokemonOffset=0x5EDB +IntroCryOffset=0x1A4C +MapBanks=0xFC3E4 +MapAddresses=0xFC1F2 +SpecialMapPointerTable=0xF268D +HiddenItemRoutine=0x75F74 +TradeTableOffset=0x71C1D +TradeTableSize=10 +TradeNameLength=11 +TradesUnused=[2,4,6] +TextDelayFunctionOffset=0x38C8 +PCPotionOffset=0x5EA2 +PikachuEvoJumpOffset=0xD809 +CatchingTutorialMonOffset=0x190EA +MonPaletteIndicesOffset=0x72921 +SGBPalettesOffset=0x729B9 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0xF21C0], Level=[0xF21BF]} // Magikarp +StaticPokemon{}={Species=[0x1CF7B, 0x1CF8C, 0x1CFEB], Level=[0x1CF8B]} // Bulbasaur +StaticPokemon{}={Species=[0x5159E, 0x515AF], Level=[0x515AE]} // Charmander +StaticPokemon{}={Species=[0xF1A34, 0xF1A45], Level=[0xF1A44]} // Squirtle +StaticPokemon{}={Species=[0x1D652], Level=[0x1D651]} // Eevee +StaticPokemon{}={Species=[0x5CE0D], Level=[0x5CE25]} // Hitmonlee +StaticPokemon{}={Species=[0x5CE55], Level=[0x5CE6D]} // Hitmonchan +StaticPokemon{}={Species=[0x51DD6], Level=[0x51DD5]} // Lapras +StaticPokemon{}={Species=[0x61054], Level=[0x75630]} // Omanyte +StaticPokemon{}={Species=[0x61058], Level=[0x75630]} // Kabuto +StaticPokemon{}={Species=[0x61050], Level=[0x75630]} // Aerodactyl +StaticPokemon{}={Species=[0x594CC, 0x61C0E], Level=[0x594D1]} // Snorlax 1 +StaticPokemon{}={Species=[0x5980C], Level=[0x59811]} // Snorlax 2 +StaticPokemon{}={Species=[0x1DCDF], Level=[0x1DCE0]} // Voltorb 1 +StaticPokemon{}={Species=[0x1DCE7], Level=[0x1DCE8]} // Voltorb 2 +StaticPokemon{}={Species=[0x1DCEF], Level=[0x1DCF0]} // Voltorb 3 +StaticPokemon{}={Species=[0x1DCFF], Level=[0x1DD00]} // Voltorb 4 +StaticPokemon{}={Species=[0x1DD07], Level=[0x1DD08]} // Voltorb 5 +StaticPokemon{}={Species=[0x1DD17], Level=[0x1DD18]} // Voltorb 6 +StaticPokemon{}={Species=[0x1DCF7], Level=[0x1DCF8]} // Electrode 1 +StaticPokemon{}={Species=[0x1DD0F], Level=[0x1DD10]} // Electrode 2 +StaticPokemon{}={Species=[0x46B1A, 0x46B5A, 0x5DBD3], Level=[0x46B5B]} // Articuno +StaticPokemon{}={Species=[0x1DCBF, 0x1DD1F], Level=[0x1DD20]} // Zapdos +StaticPokemon{}={Species=[0x51902, 0x519A5], Level=[0x519A6]} // Moltres +StaticPokemon{}={Species=[0x4618D, 0x461A5], Level=[0x461A6]} // Mewtwo +StaticPokemon{}={Species=[0x527BA, 0x528EA], Level=[0x528EB]} // Abra +StaticPokemon{}={Species=[0x527BB, 0x528EC], Level=[0x528ED]} // Vulpix +StaticPokemon{}={Species=[0x527BC, 0x528EE], Level=[0x528EF]} // Wigglytuff +StaticPokemon{}={Species=[0x527C5, 0x528F0], Level=[0x528F1]} // Scyther +StaticPokemon{}={Species=[0x527C6, 0x528F2], Level=[0x528F3]} // Pinsir +StaticPokemon{}={Species=[0x527C7, 0x528F4], Level=[0x528F5]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD43D, 0x60B22, 0x60BF9, 0x70945, 0xF4070, 0xF6095], Level=[0x60B27]} +PikachuHappinessCheckOffset=0x1CF64 +TMText[]=[6,0x0B0EE4,\pTM06 contains\n%m!\e] +TMText[]=[11,0xAB2D6,TM11 teaches\n%m!\e] +TMText[]=[13,0xAE5CA,contains\n%m!\e] +TMText[]=[18,0xAE3F3,TM18 is\n%m!\e] +TMText[]=[21,0xAF141,\pTM21 contains\n%m.\e] +TMText[]=[24,0xAD9FE,\pTM24 contains\n%m!\e] +TMText[]=[27,0xA9BEC,\pTM27 is\n%m!\e] +TMText[]=[28,0xAAEC4,Those miserable\nROCKETs!\pLook what they\ndid here!\pThey stole a TM\nfor teaching\l[POKé]MON how to\l%m!\e] +TMText[]=[28,0xAAF4A,I figure what's\nlost is lost!\pI decided to get\n%m\lwithout a TM!\e] +TMText[]=[29,0xB343D,TM29 is\n%m!\e] +TMText[]=[31,0xB2593,\pTM31 contains my\nfavorite,\l%m!\e] +TMText[]=[34,0xAA729,\pA TM contains a\ntechnique that\lcan be taught to\l[POKé]MON!\pA TM is good only\nonce! So when you\luse one to teach\la new technique,\lpick the [POKé]MON\lcarefully!\pTM34 contains\n%m!\e] +TMText[]=[36,0x9A5B0,TM36 is\n%m!\e] +TMText[]=[38,0xB17A1,\pTM38 contains\n%m!\e] +TMText[]=[39,0xA1BDE,TM39 is the move\n%m.\e] +TMText[]=[41,0xB5E1F,TM41 teaches\n%m!\pMany [POKé]MON\ncan use it!\e] +TMText[]=[42,0xB48A0,TM42 contains\n%m...\e] +TMText[]=[46,0xB2CEA,\pTM46 is\n%m!\e] +TMText[]=[48,0xAE655,contains\n%m!\e] +TMText[]=[49,0xAE6B7,\pTM49 is\n%m!\e] +CRC32=7D527D62 + +[Red (J)] +Game=POKEMON RED +Version=0 +NonJapanese=0 +Type=RB +InternalPokemonCount=190 +PokedexOrder=0x4279A +PokemonNamesOffset=0x39068 +PokemonNamesLength=5 +PokemonStatsOffset=0x38000 +MewStatsOffset=0x4200 +WildPokemonTableOffset=0xCF61 +OldRodOffset=0xE3A1 +GoodRodOffset=0xE3CE +SuperRodTableOffset=0xEC24 +MapNameTableOffset=0x718AF +MoveCount=165 +MoveNamesOffset=0x10000 +MoveDataOffset=0x39658 +ItemNamesOffset=0x433F +TypeEffectivenessOffset=0x3E756 +PokemonMovesetsTableOffset=0x3B427 +PokemonMovesetsDataSize=0x814 +PokemonMovesetsExtraSpaceOffset=0 +StarterOffsets1=[0x1CBBF, 0x1C6C2, 0x1CBA7, 0x3A069, 0x514A1, 0x515CB] +StarterOffsets2=[0x1CB9D, 0x19C66, 0x1C6C6, 0x1C806, 0x1CBB8, 0x5149D, 0x515C7, 0x52A1D, 0x606AD, 0x61F2D, 0x77003] +StarterOffsets3=[0x1CBAE, 0x19C6E, 0x1C80E, 0x1CBC9, 0x3A063, 0x5149F, 0x515C9, 0x52A25, 0x606B5, 0x61F35, 0x7700B] +PatchPokedex=0 +CanChangeStarterText=0 +CanChangeTrainerText=0 +TrainerDataTableOffset=0x3A0AC +TrainerDataClassCounts=[0, 13, 14, 18, 8, 9, 24, 7, 12, 14, 15, 9, 3, 0, 11, 15, 9, 7, 15, 4, 2, 8, 6, 17, 9, 9, 3, 0, 13, 3, 41, 10, 8, 1, 1, 1, 1, 1, 1, 1, 1, 5, 12, 3, 1, 24, 1, 1] +ExtraTrainerMovesTableOffset=0x3A0A3 +GymLeaderMovesTableOffset=0x3A094 +TMMovesOffset=0x12276 +TrainerClassNamesOffsets=[0x27F2A, 0x39D1C] +IntroPokemonOffset=0x5FB1 +IntroCryOffset=0x716 +MapBanks=0xC883 +MapAddresses=0x1BCB +SpecialMapList=0x47965 +SpecialMapPointerTable=0x479BB +HiddenItemRoutine=0x77D78 +TradeTableOffset=0x72043 +TradeTableSize=10 +TradeNameLength=5 +TradesUnused=[2] +TextDelayFunctionOffset=0x391D +PCPotionOffset=0x5F78 +CatchingTutorialMonOffset=0x19181 +MonPaletteIndicesOffset=0x72A1E +SGBPalettesOffset=0x72AB6 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x4A557], Level=[0x4A556]} // Magikarp +StaticPokemon{}={Species=[0x1E771], Level=[0x1E770]} // Eevee +StaticPokemon{}={Species=[0x5E330], Level=[0x5E348]} // Hitmonlee +StaticPokemon{}={Species=[0x5E390], Level=[0x5E3A8]} // Hitmonchan +StaticPokemon{}={Species=[0x52B1B], Level=[0x52B1A]} // Lapras +StaticPokemon{}={Species=[0x617DB], Level=[0x76C43]} // Omanyte +StaticPokemon{}={Species=[0x617DF], Level=[0x76C43]} // Kabuto +StaticPokemon{}={Species=[0x617D7], Level=[0x76C43]} // Aerodactyl +StaticPokemon{}={Species=[0x59E34, 0x62EE0], Level=[0x59E39]} // Snorlax 1 +StaticPokemon{}={Species=[0x5A684], Level=[0x5A689]} // Snorlax 2 +StaticPokemon{}={Species=[0x1F0A7], Level=[0x1F0A8]} // Voltorb 1 +StaticPokemon{}={Species=[0x1F0AF], Level=[0x1F0B0]} // Voltorb 2 +StaticPokemon{}={Species=[0x1F0B7], Level=[0x1F0B8]} // Voltorb 3 +StaticPokemon{}={Species=[0x1F0C7], Level=[0x1F0C8]} // Voltorb 4 +StaticPokemon{}={Species=[0x1F0CF], Level=[0x1F0D0]} // Voltorb 5 +StaticPokemon{}={Species=[0x1F0DF], Level=[0x1F0E0]} // Voltorb 6 +StaticPokemon{}={Species=[0x1F0BF], Level=[0x1F0C0]} // Electrode 1 +StaticPokemon{}={Species=[0x1F0D7], Level=[0x1F0D8]} // Electrode 2 +StaticPokemon{}={Species=[0x477AE, 0x4780D, 0x5F91B], Level=[0x4780E]} // Articuno +StaticPokemon{}={Species=[0x1F087, 0x1F0E7], Level=[0x1F0E8]} // Zapdos +StaticPokemon{}={Species=[0x5267C, 0x526D4], Level=[0x526D5]} // Moltres +StaticPokemon{}={Species=[0x46C3F, 0x46C57], Level=[0x46C58]} // Mewtwo +StaticPokemon{}={Species=[0x53C10, 0x53D6A], Level=[0x53D6B]} // Abra +StaticPokemon{}={Species=[0x53C11, 0x53D6C], Level=[0x53D6D]} // Clefairy +StaticPokemon{}={Species=[0x53C12, 0x53D6E], Level=[0x53D6F]} // Nidorina/Nidorino +StaticPokemon{}={Species=[0x53C1B, 0x53D70], Level=[0x53D71]} // Dratini/Pinsir +StaticPokemon{}={Species=[0x53C1C, 0x53D72], Level=[0x53D73]} // Scyther/Dratini +StaticPokemon{}={Species=[0x53C1D, 0x53D74], Level=[0x53D75]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD764, 0x3F28D, 0x5BDD2, 0x60F66, 0x61129, 0x70E4D], Level=[0x60F6B]} +CRC32=13652705 + +[Green (J)] +Game=POKEMON GREEN +Version=0 +NonJapanese=0 +Type=RB +CopyStaticPokemon=1 +CopyFrom=Red (J) +IntroPokemonOffset=0x5FB2 +PCPotionOffset=0x5F79 +CRC32=BAEACD2B + +[Green (J)(T-Eng)] +Game=POKEMON GREEN +Version=0 +NonJapanese=0 +CRCInHeader=0xF57E +Type=RB +CopyStaticPokemon=1 +CopyFrom=Green (J) +ExtraTableFile=green_translation +CanChangeTrainerText=1 +TrainerClassNamesOffsets=[0xB21BF] +ItemNamesOffset=0xB1DB1 +MoveNamesOffset=0xB1657 + +[Blue (J)] +Game=POKEMON BLUE +Version=0 +NonJapanese=0 +Type=RB +CopyFrom=Red (J) +PokedexOrder=0x42784 +PokemonNamesOffset=0x39446 +PokemonStatsOffset=0x383DE +MewStatsOffset=0x425B +OldRodOffset=0xE3C6 +GoodRodOffset=0xE3F3 +SuperRodTableOffset=0xEC49 +MapNameTableOffset=0x7189E +MoveDataOffset=0x38000 +TypeEffectivenessOffset=0x3E75B +ItemNamesOffset=0x4733 +StarterOffsets1=[0x1CBBF, 0x1C6C2, 0x1CBA7, 0x3A069, 0x514A1, 0x515CB] +StarterOffsets2=[0x1CB9D, 0x19C5E, 0x1C6C6, 0x1C806, 0x1CBB8, 0x5149D, 0x515C7, 0x52A1F, 0x606AD, 0x61F2D, 0x77006] +StarterOffsets3=[0x1CBAE, 0x19C66, 0x1C80E, 0x1CBC9, 0x3A063, 0x5149F, 0x515C9, 0x52A27, 0x606B5, 0x61F35, 0x7700E] +TMMovesOffset=0x13C93 +TrainerClassNamesOffsets=[0x27EA1, 0x39DB5] +IntroPokemonOffset=0x60C4 +IntroCryOffset=0x1C77 +MapBanks=0xC275 +MapAddresses=0x0167 +SpecialMapList=0x47965 +SpecialMapPointerTable=0x479BB +HiddenItemRoutine=0x77D7B +TradeTableOffset=0x72033 +TradeTableSize=10 +TradeNameLength=4 +TradesUnused=[2] +TextDelayFunctionOffset=0x3931 +PCPotionOffset=0x608B +CatchingTutorialMonOffset=0x1917D +MonPaletteIndicesOffset=0x72A0D +SGBPalettesOffset=0x72AA5 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x4A553], Level=[0x4A552]} // Magikarp +StaticPokemon{}={Species=[0x1E771], Level=[0x1E770]} // Eevee +StaticPokemon{}={Species=[0x5E32F], Level=[0x5E347]} // Hitmonlee +StaticPokemon{}={Species=[0x5E38F], Level=[0x5E3A7]} // Hitmonchan +StaticPokemon{}={Species=[0x52B1D], Level=[0x52B1C]} // Lapras +StaticPokemon{}={Species=[0x617DB], Level=[0x76C46]} // Omanyte +StaticPokemon{}={Species=[0x617DF], Level=[0x76C46]} // Kabuto +StaticPokemon{}={Species=[0x617D7], Level=[0x76C46]} // Aerodactyl +StaticPokemon{}={Species=[0x5A079, 0x62DBF], Level=[0x5A07E]} // Snorlax 1 +StaticPokemon{}={Species=[0x5A8C9], Level=[0x5A8CE]} // Snorlax 2 +StaticPokemon{}={Species=[0x1F0A7], Level=[0x1F0A8]} // Voltorb 1 +StaticPokemon{}={Species=[0x1F0AF], Level=[0x1F0B0]} // Voltorb 2 +StaticPokemon{}={Species=[0x1F0B7], Level=[0x1F0B8]} // Voltorb 3 +StaticPokemon{}={Species=[0x1F0C7], Level=[0x1F0C8]} // Voltorb 4 +StaticPokemon{}={Species=[0x1F0CF], Level=[0x1F0D0]} // Voltorb 5 +StaticPokemon{}={Species=[0x1F0DF], Level=[0x1F0E0]} // Voltorb 6 +StaticPokemon{}={Species=[0x1F0BF], Level=[0x1F0C0]} // Electrode 1 +StaticPokemon{}={Species=[0x1F0D7], Level=[0x1F0D8]} // Electrode 2 +StaticPokemon{}={Species=[0x477AE, 0x4780D, 0x5F91B], Level=[0x4780E]} // Articuno +StaticPokemon{}={Species=[0x1F087, 0x1F0E7], Level=[0x1F0E8]} // Zapdos +StaticPokemon{}={Species=[0x5252E, 0x526D6], Level=[0x526D7]} // Moltres +StaticPokemon{}={Species=[0x46C3F, 0x46C57], Level=[0x46C58]} // Mewtwo +StaticPokemon{}={Species=[0x53C12, 0x53D6C], Level=[0x53D6D]} // Abra +StaticPokemon{}={Species=[0x53C13, 0x53D6E], Level=[0x53D6F]} // Pikachu +StaticPokemon{}={Species=[0x53C14, 0x53D70], Level=[0x53D71]} // Horsea +StaticPokemon{}={Species=[0x53C1D, 0x53D72], Level=[0x53D73]} // Clefable +StaticPokemon{}={Species=[0x53C1E, 0x53D74], Level=[0x53D75]} // Dragonair +StaticPokemon{}={Species=[0x53C1F, 0x53D76], Level=[0x53D77]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD77A, 0x3F28E, 0x58DE4, 0x60F66, 0x6103D, 0x70E3E], Level=[0x60F6B]} +CRC32=E4468D14 + +[Yellow (J)] +Game=POKEMON YELLOW +Version=0 +NonJapanese=0 +Type=Yellow +CopyFrom=Red (J) +PokedexOrder=0x4282D +PokemonNamesOffset=0x39462 +PokemonNamesLength=5 +PokemonStatsOffset=0x383DE +MewStatsOffset=0 +WildPokemonTableOffset=0xCB91 +OldRodOffset=0xE214 +GoodRodOffset=0xE241 +SuperRodTableOffset=0xA4C7D +MapNameTableOffset=0x713CA +MoveDataOffset=0x38000 +TypeEffectivenessOffset=0x3E8EA +PokemonMovesetsTableOffset=0x3B59C +PokemonMovesetsDataSize=0x84A +PokemonMovesetsExtraSpaceOffset=0 +StarterOffsets1=[0x18F19, 0x1D0A7, 0x1D0CC] +StarterOffsets2=[0x3A5FB] +ItemNamesOffset=0x45C4 +CanChangeTrainerText=0 +TrainerDataTableOffset=0x3A142 +TrainerDataClassCounts=[0, 14, 15, 19, 8, 10, 25, 7, 12, 14, 15, 9, 3, 0, 11, 15, 9, 7, 15, 4, 2, 8, 6, 17, 9, 3, 3, 0, 13, 3, 49, 10, 8, 1, 1, 1, 1, 1, 1, 1, 1, 5, 10, 3, 1, 24, 1, 1] +ExtraTrainerMovesTableOffset=0x39FDC +GymLeaderMovesTableOffset=0 +TMMovesOffset=0x1286C +TrainerClassNamesOffsets=[0x27E56, 0x39D34] +IntroPokemonOffset=0x5E3A +IntroCryOffset=0x1ABF +MapBanks=0xFC3E4 +MapAddresses=0xFC1F2 +SpecialMapPointerTable=0xF2B7F +HiddenItemRoutine=0x77C4D +TradeTableOffset=0x71B77 +TradeTableSize=10 +TradeNameLength=5 +TradesUnused=[2,4,6] +TextDelayFunctionOffset=0x38E9 +PCPotionOffset=0x5E01 +PikachuEvoJumpOffset=0xD8BF +CatchingTutorialMonOffset=0x1920B +MonPaletteIndicesOffset=0x7264F +SGBPalettesOffset=0x726E7 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0xF20D5], Level=[0xF20D4]} // Magikarp +StaticPokemon{}={Species=[0x1DB2F, 0x1DB40, 0x1DC33], Level=[0x1DB3F]} // Bulbasaur +StaticPokemon{}={Species=[0x51DED, 0x51DFE], Level=[0x51DFD]} // Charmander +StaticPokemon{}={Species=[0xF0C0E, 0xF0C1F], Level=[0xF0C1E]} // Squirtle +StaticPokemon{}={Species=[0x1E81D], Level=[0x1E81C]} // Eevee +StaticPokemon{}={Species=[0x5E0E6], Level=[0x5E0FE]} // Hitmonlee +StaticPokemon{}={Species=[0x5E146], Level=[0x5E15E]} // Hitmonchan +StaticPokemon{}={Species=[0x529E7], Level=[0x529E6]} // Lapras +StaticPokemon{}={Species=[0x616A9], Level=[0x76AA7]} // Omanyte +StaticPokemon{}={Species=[0x616AD], Level=[0x76AA7]} // Kabuto +StaticPokemon{}={Species=[0x616A5], Level=[0x76AA7]} // Aerodactyl +StaticPokemon{}={Species=[0x59E7A, 0x62C44], Level=[0x59E7F]} // Snorlax 1 +StaticPokemon{}={Species=[0x5A6CA], Level=[0x5A6CF]} // Snorlax 2 +StaticPokemon{}={Species=[0x1F155], Level=[0x1F156]} // Voltorb 1 +StaticPokemon{}={Species=[0x1F15D], Level=[0x1F15E]} // Voltorb 2 +StaticPokemon{}={Species=[0x1F165], Level=[0x1F166]} // Voltorb 3 +StaticPokemon{}={Species=[0x1F175], Level=[0x1F176]} // Voltorb 4 +StaticPokemon{}={Species=[0x1F17D], Level=[0x1F17E]} // Voltorb 5 +StaticPokemon{}={Species=[0x1F18D], Level=[0x1F18E]} // Voltorb 6 +StaticPokemon{}={Species=[0x1F16D], Level=[0x1F16E]} // Electrode 1 +StaticPokemon{}={Species=[0x1F185], Level=[0x1F186]} // Electrode 2 +StaticPokemon{}={Species=[0x47A8C, 0x47AEB, 0x5F701], Level=[0x47AEC]} // Articuno +StaticPokemon{}={Species=[0x1F135, 0x1F195], Level=[0x1F196]} // Zapdos +StaticPokemon{}={Species=[0x52411, 0x525B9], Level=[0x525BA]} // Moltres +StaticPokemon{}={Species=[0x46F0C, 0x46F24], Level=[0x46F25]} // Mewtwo +StaticPokemon{}={Species=[0x53A1B, 0x53B74], Level=[0x53B75]} // Abra +StaticPokemon{}={Species=[0x53A1C, 0x53B76], Level=[0x53B77]} // Vulpix +StaticPokemon{}={Species=[0x53A1D, 0x53B78], Level=[0x53B79]} // Wigglytuff +StaticPokemon{}={Species=[0x53A26, 0x53B7A], Level=[0x53B7B]} // Scyther +StaticPokemon{}={Species=[0x53A27, 0x53B7C], Level=[0x53B7D]} // Pinsir +StaticPokemon{}={Species=[0x53A28, 0x53B7E], Level=[0x53B7F]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD439, 0x60F55, 0x6102C, 0x70957, 0xF4070, 0xF539C], Level=[0x60F5A]} +PikachuHappinessCheckOffset=0x1DB18 +CRC32=4EC85504 + +[Red (F)] +Game=POKEMON RED +Version=0 +NonJapanese=1 +Type=RB +CRCInHeader=0x7AFC +CopyFrom=Red (U) +ExtraTableFile=rby_freger +OldRodOffset=0xE242 +GoodRodOffset=0xE26F +SuperRodTableOffset=0xE909 +MapNameTableOffset=0x71317 +PokedexOrder=0x40FAA +TypeEffectivenessOffset=0x3E489 +PokemonMovesetsTableOffset=0x3B05F +PokemonMovesetsDataSize=0x814 +PokemonMovesetsExtraSpaceOffset=0x3BBE9 +ItemNamesOffset=0x472D +StarterOffsets1=[0x1D126, 0x1CC84, 0x1D10E, 0x39CFB, 0x50FB3, 0x510DD] +StarterOffsets2=[0x1D104, 0x19596, 0x1CC88, 0x1CDC8, 0x1D11F, 0x50FAF, 0x510D9, 0x51CB2, 0x6060E, 0x61450, 0x75FE0] +StarterOffsets3=[0x1D115, 0x1959E, 0x1CDD0, 0x1D130, 0x39CF5, 0x50FB1, 0x510DB, 0x51CBA, 0x60616, 0x61458, 0x75FE8] +CanChangeStarterText=0 +PokedexRamOffset=0xD2FC +TrainerDataTableOffset=0x39D3E +ExtraTrainerMovesTableOffset=0x39D35 +GymLeaderMovesTableOffset=0x39D26 +TMMovesOffset=0x13782 +TrainerClassNamesOffsets=[0x27EBF, 0x399FF] +IntroPokemonOffset=0x6208 +IntroCryOffset=0x1C6F +HiddenItemRoutine=0x766CA +TradeTableOffset=0x71B4C +TextDelayFunctionOffset=0x38F0 +PCPotionOffset=0x61CF +MonPaletteIndicesOffset=0x72599 +SGBPalettesOffset=0x72631 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x4931F], Level=[0x4931E]} // Magikarp +StaticPokemon{}={Species=[0x1DD4C], Level=[0x1DD4B]} // Eevee +StaticPokemon{}={Species=[0x5CF0F], Level=[0x5CF27]} // Hitmonlee +StaticPokemon{}={Species=[0x5CF57], Level=[0x5CF6F]} // Hitmonchan +StaticPokemon{}={Species=[0x51DB0], Level=[0x51DAF]} // Lapras +StaticPokemon{}={Species=[0x61068], Level=[0x75DF7]} // Omanyte +StaticPokemon{}={Species=[0x6106C], Level=[0x75DF7]} // Kabuto +StaticPokemon{}={Species=[0x61064], Level=[0x75DF7]} // Aerodactyl +StaticPokemon{}={Species=[0x59630, 0x61BEB], Level=[0x59635]} // Snorlax 1 +StaticPokemon{}={Species=[0x59970], Level=[0x59975]} // Snorlax 2 +StaticPokemon{}={Species=[0x1E3D8], Level=[0x1E3D9]} // Voltorb 1 +StaticPokemon{}={Species=[0x1E3E0], Level=[0x1E3E1]} // Voltorb 2 +StaticPokemon{}={Species=[0x1E3E8], Level=[0x1E3E9]} // Voltorb 3 +StaticPokemon{}={Species=[0x1E3F8], Level=[0x1E3F9]} // Voltorb 4 +StaticPokemon{}={Species=[0x1E400], Level=[0x1E401]} // Voltorb 5 +StaticPokemon{}={Species=[0x1E410], Level=[0x1E411]} // Voltorb 6 +StaticPokemon{}={Species=[0x1E3F0], Level=[0x1E3F1]} // Electrode 1 +StaticPokemon{}={Species=[0x1E408], Level=[0x1E409]} // Electrode 2 +StaticPokemon{}={Species=[0x468A8, 0x468E8, 0x5DB92], Level=[0x468E9]} // Articuno +StaticPokemon{}={Species=[0x1E3B8, 0x1E418], Level=[0x1E419]} // Zapdos +StaticPokemon{}={Species=[0x518C3, 0x51966], Level=[0x51967]} // Moltres +StaticPokemon{}={Species=[0x45F2C, 0x45F44], Level=[0x45F45]} // Mewtwo +StaticPokemon{}={Species=[0x5285C, 0x5298F], Level=[0x52990]} // Abra +StaticPokemon{}={Species=[0x5285D, 0x52991], Level=[0x52992]} // Clefairy +StaticPokemon{}={Species=[0x5285E, 0x52993], Level=[0x52994]} // Nidorina/Nidorino +StaticPokemon{}={Species=[0x52867, 0x52995], Level=[0x52996]} // Dratini/Pinsir +StaticPokemon{}={Species=[0x52868, 0x52997], Level=[0x52998]} // Scyther/Dratini +StaticPokemon{}={Species=[0x52869, 0x52999], Level=[0x5299A]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD6F4, 0x3EFAF, 0x58DE4, 0x60B33, 0x60C0A, 0x708E2], Level=[0x60B38]} +CRC32=337FCE11 + +[Blue (F)] +Game=POKEMON BLUE +Version=0 +NonJapanese=1 +Type=RB +CopyStaticPokemon=1 +CRCInHeader=0x56A4 +CopyFrom=Red (F) +CRC32=50E2FC1D + +[Red (S)] +Game=POKEMON RED +Version=0 +NonJapanese=1 +Type=RB +CRCInHeader=0x384A +CopyFrom=Red (U) +ExtraTableFile=rby_espita +OldRodOffset=0xE254 +GoodRodOffset=0xE281 +SuperRodTableOffset=0xE91B +MapNameTableOffset=0x71316 +PokedexOrder=0x40FB4 +TypeEffectivenessOffset=0x3E483 +PokemonMovesetsTableOffset=0x3B069 +PokemonMovesetsDataSize=0x814 +PokemonMovesetsExtraSpaceOffset=0x3BBF3 +ItemNamesOffset=0x472B +StarterOffsets1=[0x1D126, 0x1CC84, 0x1D10E, 0x39D05, 0x50FB3, 0x510DD] +StarterOffsets2=[0x1D104, 0x19596, 0x1CC88, 0x1CDC8, 0x1D11F, 0x50FAF, 0x510D9, 0x51CAB, 0x6060E, 0x61450, 0x76026] +StarterOffsets3=[0x1D115, 0x1959E, 0x1CDD0, 0x1D130, 0x39CFF, 0x50FB1, 0x510DB, 0x51CB3, 0x60616, 0x61458, 0x7602E] +CanChangeStarterText=0 +PokedexRamOffset=0xD2FC +TrainerDataTableOffset=0x39D48 +ExtraTrainerMovesTableOffset=0x39D3F +GymLeaderMovesTableOffset=0x39D30 +TMMovesOffset=0x13798 +TrainerClassNamesOffsets=[0x27ECB, 0x399FF] +IntroPokemonOffset=0x61CC +IntroCryOffset=0x1C72 +HiddenItemRoutine=0x76710 +TradeTableOffset=0x71B6B +TextDelayFunctionOffset=0x38F2 +PCPotionOffset=0x6193 +MonPaletteIndicesOffset=0x725B8 +SGBPalettesOffset=0x72650 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x49323], Level=[0x49322]} // Magikarp +StaticPokemon{}={Species=[0x1DD4A], Level=[0x1DD49]} // Eevee +StaticPokemon{}={Species=[0x5CF1B], Level=[0x5CF33]} // Hitmonlee +StaticPokemon{}={Species=[0x5CF63], Level=[0x5CF7B]} // Hitmonchan +StaticPokemon{}={Species=[0x51DA9], Level=[0x51DA8]} // Lapras +StaticPokemon{}={Species=[0x61068], Level=[0x75E3D]} // Omanyte +StaticPokemon{}={Species=[0x6106C], Level=[0x75E3D]} // Kabuto +StaticPokemon{}={Species=[0x61064], Level=[0x75E3D]} // Aerodactyl +StaticPokemon{}={Species=[0x59630, 0x61BEB], Level=[0x59635]} // Snorlax 1 +StaticPokemon{}={Species=[0x59970], Level=[0x59975]} // Snorlax 2 +StaticPokemon{}={Species=[0x1E3D6], Level=[0x1E3D7]} // Voltorb 1 +StaticPokemon{}={Species=[0x1E3DE], Level=[0x1E3DF]} // Voltorb 2 +StaticPokemon{}={Species=[0x1E3E6], Level=[0x1E3E7]} // Voltorb 3 +StaticPokemon{}={Species=[0x1E3F6], Level=[0x1E3F7]} // Voltorb 4 +StaticPokemon{}={Species=[0x1E3FE], Level=[0x1E3FF]} // Voltorb 5 +StaticPokemon{}={Species=[0x1E40E], Level=[0x1E40F]} // Voltorb 6 +StaticPokemon{}={Species=[0x1E3EE], Level=[0x1E3EF]} // Electrode 1 +StaticPokemon{}={Species=[0x1E406], Level=[0x1E407]} // Electrode 2 +StaticPokemon{}={Species=[0x468A8, 0x468E8, 0x5DBA4], Level=[0x468E9]} // Articuno +StaticPokemon{}={Species=[0x1E3B6, 0x1E416], Level=[0x1E417]} // Zapdos +StaticPokemon{}={Species=[0x518BC, 0x5195F], Level=[0x51960]} // Moltres +StaticPokemon{}={Species=[0x45F2C, 0x45F44], Level=[0x45F45]} // Mewtwo +StaticPokemon{}={Species=[0x52856, 0x52989], Level=[0x5298A]} // Abra +StaticPokemon{}={Species=[0x52857, 0x5298B], Level=[0x5298C]} // Clefairy +StaticPokemon{}={Species=[0x52858, 0x5298D], Level=[0x5298E]} // Nidorina/Nidorino +StaticPokemon{}={Species=[0x52861, 0x5298F], Level=[0x52990]} // Dratini/Pinsir +StaticPokemon{}={Species=[0x52862, 0x52991], Level=[0x52992]} // Scyther/Dratini +StaticPokemon{}={Species=[0x52863, 0x52993], Level=[0x52994]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD6F4, 0x3EFA9, 0x58DE4, 0x60B33, 0x60C0A, 0x708E2], Level=[0x60B38]} +CRC32=D8507D8A + +[Blue (S)] +Game=POKEMON BLUE +Version=0 +NonJapanese=1 +Type=RB +CopyStaticPokemon=1 +CRCInHeader=0x14D7 +CopyFrom=Red (S) +CRC32=D95416F9 + +[Red (G)] +Game=POKEMON RED +Version=0 +NonJapanese=1 +Type=RB +CRCInHeader=0x5CDC +CopyFrom=Red (U) +ExtraTableFile=rby_freger +OldRodOffset=0xE24B +GoodRodOffset=0xE278 +SuperRodTableOffset=0xE912 +MapNameTableOffset=0x71310 +PokedexOrder=0x40F96 +TypeEffectivenessOffset=0x3E482 +PokemonMovesetsTableOffset=0x3B064 +PokemonMovesetsDataSize=0x814 +PokemonMovesetsExtraSpaceOffset=0x3BBEE +ItemNamesOffset=0x472D +StarterOffsets1=[0x1D126, 0x1CC84, 0x1D10E, 0x39D00, 0x50FB3, 0x510DD] +StarterOffsets2=[0x1D104, 0x19596, 0x1CC88, 0x1CDC8, 0x1D11F, 0x50FAF, 0x510D9, 0x51CA8, 0x6060E, 0x61450, 0x75FF1] +StarterOffsets3=[0x1D115, 0x1959E, 0x1CDD0, 0x1D130, 0x39CFA, 0x50FB1, 0x510DB, 0x51CB0, 0x60616, 0x61458, 0x75FF9] +CanChangeStarterText=0 +PokedexRamOffset=0xD2FC +TrainerDataTableOffset=0x39D43 +TrainerDataClassCounts=[0, 13, 14, 18, 8, 9, 24, 7, 12, 14, 15, 9, 3, 0, 11, 15, 9, 7, 15, 4, 2, 8, 6, 17, 9, 9, 3, 0, 13, 3, 41, 10, 8, 1, 1, 1, 1, 1, 1, 1, 1, 5, 12, 3, 1, 24, 1, 1] +ExtraTrainerMovesTableOffset=0x39D3A +GymLeaderMovesTableOffset=0x39D2B +TMMovesOffset=0x13774 +TrainerClassNamesOffsets=[0x27EC3, 0x399FF] +IntroPokemonOffset=0x6194 +IntroCryOffset=0x1C73 +HiddenItemRoutine=0x766D8 +TradeTableOffset=0x71B55 +TextDelayFunctionOffset=0x38ED +PCPotionOffset=0x615B +MonPaletteIndicesOffset=0x725A2 +SGBPalettesOffset=0x7263A +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x49323], Level=[0x49322]} // Magikarp +StaticPokemon{}={Species=[0x1DD49], Level=[0x1DD48]} // Eevee +StaticPokemon{}={Species=[0x5CF15], Level=[0x5CF2D]} // Hitmonlee +StaticPokemon{}={Species=[0x5CF5D], Level=[0x5CF75]} // Hitmonchan +StaticPokemon{}={Species=[0x51DA6], Level=[0x51DA5]} // Lapras +StaticPokemon{}={Species=[0x61068], Level=[0x75E08]} // Omanyte +StaticPokemon{}={Species=[0x6106C], Level=[0x75E08]} // Kabuto +StaticPokemon{}={Species=[0x61064], Level=[0x75E08]} // Aerodactyl +StaticPokemon{}={Species=[0x59630, 0x61BEB], Level=[0x59635]} // Snorlax 1 +StaticPokemon{}={Species=[0x59970], Level=[0x59975]} // Snorlax 2 +StaticPokemon{}={Species=[0x1E3D5], Level=[0x1E3D6]} // Voltorb 1 +StaticPokemon{}={Species=[0x1E3DD], Level=[0x1E3DE]} // Voltorb 2 +StaticPokemon{}={Species=[0x1E3E5], Level=[0x1E3E6]} // Voltorb 3 +StaticPokemon{}={Species=[0x1E3F5], Level=[0x1E3F6]} // Voltorb 4 +StaticPokemon{}={Species=[0x1E3FD], Level=[0x1E3FE]} // Voltorb 5 +StaticPokemon{}={Species=[0x1E40D], Level=[0x1E40E]} // Voltorb 6 +StaticPokemon{}={Species=[0x1E3ED], Level=[0x1E3EE]} // Electrode 1 +StaticPokemon{}={Species=[0x1E405], Level=[0x1E406]} // Electrode 2 +StaticPokemon{}={Species=[0x468A8, 0x468E8, 0x5DB9E], Level=[0x468E9]} // Articuno +StaticPokemon{}={Species=[0x1E3B5, 0x1E415], Level=[0x1E416]} // Zapdos +StaticPokemon{}={Species=[0x518B9, 0x5195C], Level=[0x5195D]} // Moltres +StaticPokemon{}={Species=[0x45F2C, 0x45F44], Level=[0x45F45]} // Mewtwo +StaticPokemon{}={Species=[0x52851, 0x52984], Level=[0x52985]} // Abra +StaticPokemon{}={Species=[0x52852, 0x52986], Level=[0x52987]} // Clefairy +StaticPokemon{}={Species=[0x52853, 0x52988], Level=[0x52989]} // Nidorina +StaticPokemon{}={Species=[0x5285C, 0x5298A], Level=[0x5298B]} // Dratini +StaticPokemon{}={Species=[0x5285D, 0x5298C], Level=[0x5298D]} // Scyther +StaticPokemon{}={Species=[0x5285E, 0x5298E], Level=[0x5298F]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD6F4, 0x3EFA8, 0x58DE4, 0x60B33, 0x60C0A, 0x708DD], Level=[0x60B38]} +CRC32=89197825 + +[Blue (G)] +Game=POKEMON BLUE +Version=0 +NonJapanese=1 +Type=RB +CRCInHeader=0x2EBC +CopyFrom=Red (G) +HiddenItemRoutine=0x766D9 +StarterOffsets2=[0x1D104, 0x19596, 0x1CC88, 0x1CDC8, 0x1D11F, 0x50FAF, 0x510D9, 0x51CA8, 0x6060E, 0x61450, 0x75FF2] +StarterOffsets3=[0x1D115, 0x1959E, 0x1CDD0, 0x1D130, 0x39CFA, 0x50FB1, 0x510DB, 0x51CB0, 0x60616, 0x61458, 0x75FFA] +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x49323], Level=[0x49322]} // Magikarp +StaticPokemon{}={Species=[0x1DD49], Level=[0x1DD48]} // Eevee +StaticPokemon{}={Species=[0x5CF15], Level=[0x5CF2D]} // Hitmonlee +StaticPokemon{}={Species=[0x5CF5D], Level=[0x5CF75]} // Hitmonchan +StaticPokemon{}={Species=[0x51DA6], Level=[0x51DA5]} // Lapras +StaticPokemon{}={Species=[0x61068], Level=[0x75E09]} // Omanyte +StaticPokemon{}={Species=[0x6106C], Level=[0x75E09]} // Kabuto +StaticPokemon{}={Species=[0x61064], Level=[0x75E09]} // Aerodactyl +StaticPokemon{}={Species=[0x59630, 0x61BEB], Level=[0x59635]} // Snorlax 1 +StaticPokemon{}={Species=[0x59970], Level=[0x59975]} // Snorlax 2 +StaticPokemon{}={Species=[0x1E3D5], Level=[0x1E3D6]} // Voltorb 1 +StaticPokemon{}={Species=[0x1E3DD], Level=[0x1E3DE]} // Voltorb 2 +StaticPokemon{}={Species=[0x1E3E5], Level=[0x1E3E6]} // Voltorb 3 +StaticPokemon{}={Species=[0x1E3F5], Level=[0x1E3F6]} // Voltorb 4 +StaticPokemon{}={Species=[0x1E3FD], Level=[0x1E3FE]} // Voltorb 5 +StaticPokemon{}={Species=[0x1E40D], Level=[0x1E40E]} // Voltorb 6 +StaticPokemon{}={Species=[0x1E3ED], Level=[0x1E3EE]} // Electrode 1 +StaticPokemon{}={Species=[0x1E405], Level=[0x1E406]} // Electrode 2 +StaticPokemon{}={Species=[0x468A8, 0x468E8, 0x5DB9E], Level=[0x468E9]} // Articuno +StaticPokemon{}={Species=[0x1E3B5, 0x1E415], Level=[0x1E416]} // Zapdos +StaticPokemon{}={Species=[0x518B9, 0x5195C], Level=[0x5195D]} // Moltres +StaticPokemon{}={Species=[0x45F2C, 0x45F44], Level=[0x45F45]} // Mewtwo +StaticPokemon{}={Species=[0x52851, 0x52984], Level=[0x52985]} // Abra +StaticPokemon{}={Species=[0x52852, 0x52986], Level=[0x52987]} // Clefairy +StaticPokemon{}={Species=[0x52853, 0x52988], Level=[0x52989]} // Nidorino +StaticPokemon{}={Species=[0x5285C, 0x5298A], Level=[0x5298B]} // Pinsir +StaticPokemon{}={Species=[0x5285D, 0x5298C], Level=[0x5298D]} // Dratini +StaticPokemon{}={Species=[0x5285E, 0x5298E], Level=[0x5298F]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD6F4, 0x3EFA8, 0x58DE4, 0x60B33, 0x60C0A, 0x708DD], Level=[0x60B38]} +CRC32=9C336307 + +[Red (I)] +Game=POKEMON RED +Version=0 +NonJapanese=1 +Type=RB +CRCInHeader=0x89D2 +CopyFrom=Red (U) +ExtraTableFile=rby_espita +OldRodOffset=0xE24C +GoodRodOffset=0xE279 +SuperRodTableOffset=0xE913 +MapNameTableOffset=0x71313 +PokedexOrder=0x40FB6 +TypeEffectivenessOffset=0x3E477 +PokemonMovesetsTableOffset=0x3B096 +PokemonMovesetsDataSize=0x814 +PokemonMovesetsExtraSpaceOffset=0x3BC20 +ItemNamesOffset=0x472D +StarterOffsets1=[0x1D126, 0x1CC84, 0x1D10E, 0x39D20, 0x50FB3, 0x510DD] +StarterOffsets2=[0x1D104, 0x19596, 0x1CC88, 0x1CDC8, 0x1D11F, 0x50FAF, 0x510D9, 0x51CAE, 0x6060E, 0x61450, 0x76015] +StarterOffsets3=[0x1D115, 0x1959E, 0x1CDD0, 0x1D130, 0x39D1A, 0x50FB1, 0x510DB, 0x51CB6, 0x60616, 0x61458, 0x7601D] +CanChangeStarterText=0 +PokedexRamOffset=0xD2FC +TrainerDataTableOffset=0x39D63 +ExtraTrainerMovesTableOffset=0x39D5A +GymLeaderMovesTableOffset=0x39D4B +TMMovesOffset=0x13798 +TrainerClassNamesOffsets=[0x27ECD, 0x399FF] +IntroPokemonOffset=0x61CC +IntroCryOffset=0x1C73 +HiddenItemRoutine=0x766FF +TradeTableOffset=0x71BBB +TextDelayFunctionOffset=0x38EB +PCPotionOffset=0x6193 +MonPaletteIndicesOffset=0x72608 +SGBPalettesOffset=0x726A0 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x49322], Level=[0x49321]} // Magikarp +StaticPokemon{}={Species=[0x1DD4A], Level=[0x1DD49]} // Eevee +StaticPokemon{}={Species=[0x5CF12], Level=[0x5CF2A]} // Hitmonlee +StaticPokemon{}={Species=[0x5CF5A], Level=[0x5CF72]} // Hitmonchan +StaticPokemon{}={Species=[0x51DAC], Level=[0x51DAB]} // Lapras +StaticPokemon{}={Species=[0x61068], Level=[0x75E2C]} // Omanyte +StaticPokemon{}={Species=[0x6106C], Level=[0x75E2C]} // Kabuto +StaticPokemon{}={Species=[0x61064], Level=[0x75E2C]} // Aerodactyl +StaticPokemon{}={Species=[0x59630, 0x61BEB], Level=[0x59635]} // Snorlax 1 +StaticPokemon{}={Species=[0x59970], Level=[0x59975]} // Snorlax 2 +StaticPokemon{}={Species=[0x1E3D6], Level=[0x1E3D7]} // Voltorb 1 +StaticPokemon{}={Species=[0x1E3DE], Level=[0x1E3DF]} // Voltorb 2 +StaticPokemon{}={Species=[0x1E3E6], Level=[0x1E3E7]} // Voltorb 3 +StaticPokemon{}={Species=[0x1E3F6], Level=[0x1E3F7]} // Voltorb 4 +StaticPokemon{}={Species=[0x1E3FE], Level=[0x1E3FF]} // Voltorb 5 +StaticPokemon{}={Species=[0x1E40E], Level=[0x1E40F]} // Voltorb 6 +StaticPokemon{}={Species=[0x1E3EE], Level=[0x1E3EF]} // Electrode 1 +StaticPokemon{}={Species=[0x1E406], Level=[0x1E407]} // Electrode 2 +StaticPokemon{}={Species=[0x468A8, 0x468E8, 0x5DB9A], Level=[0x468E9]} // Articuno +StaticPokemon{}={Species=[0x1E3B6, 0x1E416], Level=[0x1E417]} // Zapdos +StaticPokemon{}={Species=[0x518BF, 0x51962], Level=[0x51963]} // Moltres +StaticPokemon{}={Species=[0x45F2C, 0x45F44], Level=[0x45F45]} // Mewtwo +StaticPokemon{}={Species=[0x52858, 0x5298C], Level=[0x5298D]} // Abra +StaticPokemon{}={Species=[0x52859, 0x5298E], Level=[0x5298F]} // Clefairy +StaticPokemon{}={Species=[0x5285A, 0x52990], Level=[0x52991]} // Nidorina +StaticPokemon{}={Species=[0x52863, 0x52992], Level=[0x52993]} // Dratini +StaticPokemon{}={Species=[0x52864, 0x52994], Level=[0x52995]} // Scyther +StaticPokemon{}={Species=[0x52865, 0x52996], Level=[0x52997]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD6F4, 0x3EF9D, 0x58DE4, 0x60B33, 0x60C0A, 0x708DF], Level=[0x60B38]} +CRC32=2945ACEB + +[Blue (I)] +Game=POKEMON BLUE +Version=0 +NonJapanese=1 +Type=RB +CRCInHeader=0x5E9C +CopyFrom=Red (I) +HiddenItemRoutine=0x766FD +PCPotionOffset=0x6192 +StarterOffsets2=[0x1D104, 0x19596, 0x1CC88, 0x1CDC8, 0x1D11F, 0x50FAF, 0x510D9, 0x51CAE, 0x6060E, 0x61450, 0x76013] +StarterOffsets3=[0x1D115, 0x1959E, 0x1CDD0, 0x1D130, 0x39D1A, 0x50FB1, 0x510DB, 0x51CB6, 0x60616, 0x61458, 0x7601B] +IntroPokemonOffset=0x61CB +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x49322], Level=[0x49321]} // Magikarp +StaticPokemon{}={Species=[0x1DD4A], Level=[0x1DD49]} // Eevee +StaticPokemon{}={Species=[0x5CF12], Level=[0x5CF2A]} // Hitmonlee +StaticPokemon{}={Species=[0x5CF5A], Level=[0x5CF72]} // Hitmonchan +StaticPokemon{}={Species=[0x51DAC], Level=[0x51DAB]} // Lapras +StaticPokemon{}={Species=[0x61068], Level=[0x75E2A]} // Omanyte +StaticPokemon{}={Species=[0x6106C], Level=[0x75E2A]} // Kabuto +StaticPokemon{}={Species=[0x61064], Level=[0x75E2A]} // Aerodactyl +StaticPokemon{}={Species=[0x59630, 0x61BEB], Level=[0x59635]} // Snorlax 1 +StaticPokemon{}={Species=[0x59970], Level=[0x59975]} // Snorlax 2 +StaticPokemon{}={Species=[0x1E3D6], Level=[0x1E3D7]} // Voltorb 1 +StaticPokemon{}={Species=[0x1E3DE], Level=[0x1E3DF]} // Voltorb 2 +StaticPokemon{}={Species=[0x1E3E6], Level=[0x1E3E7]} // Voltorb 3 +StaticPokemon{}={Species=[0x1E3F6], Level=[0x1E3F7]} // Voltorb 4 +StaticPokemon{}={Species=[0x1E3FE], Level=[0x1E3FF]} // Voltorb 5 +StaticPokemon{}={Species=[0x1E40E], Level=[0x1E40F]} // Voltorb 6 +StaticPokemon{}={Species=[0x1E3EE], Level=[0x1E3EF]} // Electrode 1 +StaticPokemon{}={Species=[0x1E406], Level=[0x1E407]} // Electrode 2 +StaticPokemon{}={Species=[0x468A8, 0x468E8, 0x5DB9A], Level=[0x468E9]} // Articuno +StaticPokemon{}={Species=[0x1E3B6, 0x1E416], Level=[0x1E417]} // Zapdos +StaticPokemon{}={Species=[0x518BF, 0x51962], Level=[0x51963]} // Moltres +StaticPokemon{}={Species=[0x45F2C, 0x45F44], Level=[0x45F45]} // Mewtwo +StaticPokemon{}={Species=[0x52858, 0x5298C], Level=[0x5298D]} // Abra +StaticPokemon{}={Species=[0x52859, 0x5298E], Level=[0x5298F]} // Clefairy +StaticPokemon{}={Species=[0x5285A, 0x52990], Level=[0x52991]} // Nidorino +StaticPokemon{}={Species=[0x52863, 0x52992], Level=[0x52993]} // Pinsir +StaticPokemon{}={Species=[0x52864, 0x52994], Level=[0x52995]} // Dratini +StaticPokemon{}={Species=[0x52865, 0x52996], Level=[0x52997]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD6F4, 0x3EF9D, 0x58DE4, 0x60B33, 0x60C0A, 0x708DF], Level=[0x60B38]} +CRC32=4D0984A9 + +[Yellow (F)] +Game=POKEMON YELAPSF +Version=0 +NonJapanese=1 +Type=Yellow +CopyFrom=Yellow (U) +ExtraTableFile=rby_freger +OldRodOffset=0xE0EF +GoodRodOffset=0xE11C +SuperRodTableOffset=0xF5ED4 +MapNameTableOffset=0x713A0 +PokedexOrder=0x41036 +TypeEffectivenessOffset=0x3E610 +PokemonMovesetsTableOffset=0x3B1E8 +PokemonMovesetsDataSize=0xC9C +PokemonMovesetsExtraSpaceOffset=0 +ItemNamesOffset=0x45B8 +StarterOffsets2=[0x3A28D] +TrainerDataTableOffset=0x39DD4 +ExtraTrainerMovesTableOffset=0x39C6E +TMMovesOffset=0x1233C +TrainerClassNamesOffsets=[0x27E74, 0x3997E] +IntroPokemonOffset=0x5F64 +IntroCryOffset=0x1A48 +SpecialMapPointerTable=0xF25CF +HiddenItemRoutine=0x75F69 +TradeTableOffset=0x71BEE +TextDelayFunctionOffset=0x38CB +PCPotionOffset=0x5F2B +MonPaletteIndicesOffset=0x728F2 +SGBPalettesOffset=0x7298A +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0xF2102], Level=[0xF2101]} // Magikarp +StaticPokemon{}={Species=[0x1CF7B, 0x1CF8C, 0x1CFEB], Level=[0x1CF8B]} // Bulbasaur +StaticPokemon{}={Species=[0x515A1, 0x515B2], Level=[0x515B1]} // Charmander +StaticPokemon{}={Species=[0xF1976, 0xF1987], Level=[0xF1986]} // Squirtle +StaticPokemon{}={Species=[0x1D655], Level=[0x1D654]} // Eevee +StaticPokemon{}={Species=[0x5CE05], Level=[0x5CE1D]} // Hitmonlee +StaticPokemon{}={Species=[0x5CE4D], Level=[0x5CE65]} // Hitmonchan +StaticPokemon{}={Species=[0x51DD9], Level=[0x51DD8]} // Lapras +StaticPokemon{}={Species=[0x61054], Level=[0x75625]} // Omanyte +StaticPokemon{}={Species=[0x61058], Level=[0x75625]} // Kabuto +StaticPokemon{}={Species=[0x61050], Level=[0x75625]} // Aerodactyl +StaticPokemon{}={Species=[0x594CC, 0x61C0E], Level=[0x594D1]} // Snorlax 1 +StaticPokemon{}={Species=[0x5980C], Level=[0x59811]} // Snorlax 2 +StaticPokemon{}={Species=[0x1DCE2], Level=[0x1DCE3]} // Voltorb 1 +StaticPokemon{}={Species=[0x1DCEA], Level=[0x1DCEB]} // Voltorb 2 +StaticPokemon{}={Species=[0x1DCF2], Level=[0x1DCF3]} // Voltorb 3 +StaticPokemon{}={Species=[0x1DD02], Level=[0x1DD03]} // Voltorb 4 +StaticPokemon{}={Species=[0x1DD0A], Level=[0x1DD0B]} // Voltorb 5 +StaticPokemon{}={Species=[0x1DD1A], Level=[0x1DD1B]} // Voltorb 6 +StaticPokemon{}={Species=[0x1DCFA], Level=[0x1DCFB]} // Electrode 1 +StaticPokemon{}={Species=[0x1DD12], Level=[0x1DD13]} // Electrode 2 +StaticPokemon{}={Species=[0x46B1A, 0x46B5A, 0x5DBC7], Level=[0x46B5B]} // Articuno +StaticPokemon{}={Species=[0x1DCC2, 0x1DD22], Level=[0x1DD23]} // Zapdos +StaticPokemon{}={Species=[0x51905, 0x519A8], Level=[0x519A9]} // Moltres +StaticPokemon{}={Species=[0x4618D, 0x461A5], Level=[0x461A6]} // Mewtwo +StaticPokemon{}={Species=[0x527BD, 0x528EF], Level=[0x528F0]} // Abra +StaticPokemon{}={Species=[0x527BE, 0x528F1], Level=[0x528F2]} // Vulpix +StaticPokemon{}={Species=[0x527BF, 0x528F3], Level=[0x528F4]} // Wigglytuff +StaticPokemon{}={Species=[0x527C8, 0x528F5], Level=[0x528F6]} // Scyther +StaticPokemon{}={Species=[0x527C9, 0x528F7], Level=[0x528F8]} // Pinsir +StaticPokemon{}={Species=[0x527CA, 0x528F9], Level=[0x528FA]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD43D, 0x60B22, 0x60BF9, 0x70946, 0xF4070, 0xF608F], Level=[0x60B27]} +PikachuHappinessCheckOffset=0x1CF64 +CRC32=D03426E9 + +[Yellow (G)] +Game=POKEMON YELAPSD +Version=0 +NonJapanese=1 +Type=Yellow +CopyFrom=Yellow (U) +ExtraTableFile=rby_freger +OldRodOffset=0xE0F4 +GoodRodOffset=0xE121 +SuperRodTableOffset=0xF5ED3 +MapNameTableOffset=0x71399 +PokedexOrder=0x41023 +TypeEffectivenessOffset=0x3E609 +PokemonMovesetsTableOffset=0x3B1F2 +PokemonMovesetsDataSize=0xC92 +PokemonMovesetsExtraSpaceOffset=0 +ItemNamesOffset=0x45B8 +StarterOffsets2=[0x3A297] +TrainerDataTableOffset=0x39DDE +ExtraTrainerMovesTableOffset=0x39C78 +TMMovesOffset=0x1232E +TrainerClassNamesOffsets=[0x27E78, 0x3997E] +IntroPokemonOffset=0x5EF0 +IntroCryOffset=0x1A51 +SpecialMapPointerTable=0xF25EE +HiddenItemRoutine=0x75F6E +TradeTableOffset=0x71BFC +TextDelayFunctionOffset=0x38CD +PCPotionOffset=0x5EB7 +MonPaletteIndicesOffset=0x72900 +SGBPalettesOffset=0x72998 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0xF2121], Level=[0xF2120]} // Magikarp +StaticPokemon{}={Species=[0x1CF7B, 0x1CF8C, 0x1CFEB], Level=[0x1CF8B]} // Bulbasaur +StaticPokemon{}={Species=[0x51597, 0x515A8], Level=[0x515A7]} // Charmander +StaticPokemon{}={Species=[0xF1995, 0xF19A6], Level=[0xF19A5]} // Squirtle +StaticPokemon{}={Species=[0x1D652], Level=[0x1D651]} // Eevee +StaticPokemon{}={Species=[0x5CE0B], Level=[0x5CE23]} // Hitmonlee +StaticPokemon{}={Species=[0x5CE53], Level=[0x5CE6B]} // Hitmonchan +StaticPokemon{}={Species=[0x51DCF], Level=[0x51DCE]} // Lapras +StaticPokemon{}={Species=[0x61054], Level=[0x7562D]} // Omanyte +StaticPokemon{}={Species=[0x61058], Level=[0x7562D]} // Kabuto +StaticPokemon{}={Species=[0x61050], Level=[0x7562D]} // Aerodactyl +StaticPokemon{}={Species=[0x594CC, 0x61C0E], Level=[0x594D1]} // Snorlax 1 +StaticPokemon{}={Species=[0x5980C], Level=[0x59811]} // Snorlax 2 +StaticPokemon{}={Species=[0x1DCDF], Level=[0x1DCE0]} // Voltorb 1 +StaticPokemon{}={Species=[0x1DCE7], Level=[0x1DCE8]} // Voltorb 2 +StaticPokemon{}={Species=[0x1DCEF], Level=[0x1DCF0]} // Voltorb 3 +StaticPokemon{}={Species=[0x1DCFF], Level=[0x1DD00]} // Voltorb 4 +StaticPokemon{}={Species=[0x1DD07], Level=[0x1DD08]} // Voltorb 5 +StaticPokemon{}={Species=[0x1DD17], Level=[0x1DD18]} // Voltorb 6 +StaticPokemon{}={Species=[0x1DCF7], Level=[0x1DCF8]} // Electrode 1 +StaticPokemon{}={Species=[0x1DD0F], Level=[0x1DD10]} // Electrode 2 +StaticPokemon{}={Species=[0x46B1A, 0x46B5A, 0x5DBD3], Level=[0x46B5B]} // Articuno +StaticPokemon{}={Species=[0x1DCBF, 0x1DD1F], Level=[0x1DD20]} // Zapdos +StaticPokemon{}={Species=[0x518FB, 0x5199E], Level=[0x5199F]} // Moltres +StaticPokemon{}={Species=[0x4618D, 0x461A5], Level=[0x461A6]} // Mewtwo +StaticPokemon{}={Species=[0x527B2, 0x528E4], Level=[0x528E5]} // Abra +StaticPokemon{}={Species=[0x527B3, 0x528E6], Level=[0x528E7]} // Vulpix +StaticPokemon{}={Species=[0x527B4, 0x528E8], Level=[0x528E9]} // Wigglytuff +StaticPokemon{}={Species=[0x527BD, 0x528EA], Level=[0x528EB]} // Scyther +StaticPokemon{}={Species=[0x527BE, 0x528EC], Level=[0x528ED]} // Pinsir +StaticPokemon{}={Species=[0x527BF, 0x528EE], Level=[0x528EF]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD43D, 0x60B22, 0x60BF9, 0x70941, 0xF4070, 0xF608E], Level=[0x60B27]} +PikachuHappinessCheckOffset=0x1CF64 +CRC32=7A01E45A + +[Yellow (I)] +Game=POKEMON YELAPSI +Version=0 +NonJapanese=1 +Type=Yellow +CopyFrom=Yellow (U) +ExtraTableFile=rby_espita +OldRodOffset=0xE103 +GoodRodOffset=0xE130 +SuperRodTableOffset=0xF5ECE +MapNameTableOffset=0x7139C +PokedexOrder=0x41043 +TypeEffectivenessOffset=0x3E60C +PokemonMovesetsTableOffset=0x3B20D +PokemonMovesetsDataSize=0xC77 +PokemonMovesetsExtraSpaceOffset=0 +ItemNamesOffset=0x45B8 +StarterOffsets2=[0x3A2B2] +TrainerDataTableOffset=0x39DF9 +ExtraTrainerMovesTableOffset=0x39C93 +TMMovesOffset=0x12352 +TrainerClassNamesOffsets=[0x27E82, 0x3997E] +IntroPokemonOffset=0x5F2A +IntroCryOffset=0x1A4C +SpecialMapPointerTable=0xF2609 +HiddenItemRoutine=0x75F70 +TradeTableOffset=0x71C5D +TextDelayFunctionOffset=0x38C6 +PCPotionOffset=0x5EF1 +MonPaletteIndicesOffset=0x72961 +SGBPalettesOffset=0x729F9 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0xF213C], Level=[0xF213B]} // Magikarp +StaticPokemon{}={Species=[0x1CF7B, 0x1CF8C, 0x1CFEB], Level=[0x1CF8B]} // Bulbasaur +StaticPokemon{}={Species=[0x5159D, 0x515AE], Level=[0x515AD]} // Charmander +StaticPokemon{}={Species=[0xF19B0, 0xF19C1], Level=[0xF19C0]} // Squirtle +StaticPokemon{}={Species=[0x1D653], Level=[0x1D652]} // Eevee +StaticPokemon{}={Species=[0x5CE08], Level=[0x5CE20]} // Hitmonlee +StaticPokemon{}={Species=[0x5CE50], Level=[0x5CE68]} // Hitmonchan +StaticPokemon{}={Species=[0x51DD5], Level=[0x51DD4]} // Lapras +StaticPokemon{}={Species=[0x61054], Level=[0x7562C]} // Omanyte +StaticPokemon{}={Species=[0x61058], Level=[0x7562C]} // Kabuto +StaticPokemon{}={Species=[0x61050], Level=[0x7562C]} // Aerodactyl +StaticPokemon{}={Species=[0x594CC, 0x61C0E], Level=[0x594D1]} // Snorlax 1 +StaticPokemon{}={Species=[0x5980C], Level=[0x59811]} // Snorlax 2 +StaticPokemon{}={Species=[0x1DCE0], Level=[0x1DCE1]} // Voltorb 1 +StaticPokemon{}={Species=[0x1DCE8], Level=[0x1DCE9]} // Voltorb 2 +StaticPokemon{}={Species=[0x1DCF0], Level=[0x1DCF1]} // Voltorb 3 +StaticPokemon{}={Species=[0x1DD00], Level=[0x1DD01]} // Voltorb 4 +StaticPokemon{}={Species=[0x1DD08], Level=[0x1DD09]} // Voltorb 5 +StaticPokemon{}={Species=[0x1DD18], Level=[0x1DD19]} // Voltorb 6 +StaticPokemon{}={Species=[0x1DCF8], Level=[0x1DCF9]} // Electrode 1 +StaticPokemon{}={Species=[0x1DD10], Level=[0x1DD11]} // Electrode 2 +StaticPokemon{}={Species=[0x46B1A, 0x46B5A, 0x5DBCF], Level=[0x46B5B]} // Articuno +StaticPokemon{}={Species=[0x1DCC0, 0x1DD20], Level=[0x1DD21]} // Zapdos +StaticPokemon{}={Species=[0x51901, 0x519A4], Level=[0x519A5]} // Moltres +StaticPokemon{}={Species=[0x4618D, 0x461A5], Level=[0x461A6]} // Mewtwo +StaticPokemon{}={Species=[0x527B9, 0x528EC], Level=[0x528ED]} // Abra +StaticPokemon{}={Species=[0x527BA, 0x528EE], Level=[0x528EF]} // Vulpix +StaticPokemon{}={Species=[0x527BB, 0x528F0], Level=[0x528F1]} // Wigglytuff +StaticPokemon{}={Species=[0x527C4, 0x528F2], Level=[0x528F3]} // Scyther +StaticPokemon{}={Species=[0x527C5, 0x528F4], Level=[0x528F5]} // Pinsir +StaticPokemon{}={Species=[0x527C6, 0x528F6], Level=[0x528F7]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD43D, 0x60B22, 0x60BF9, 0x70943, 0xF4070, 0xF6089], Level=[0x60B27]} +PikachuHappinessCheckOffset=0x1CF64 +CRC32=8B56FE33 + +[Yellow (S)] +Game=POKEMON YELAPSS +Version=0 +NonJapanese=1 +Type=Yellow +CopyFrom=Yellow (U) +ExtraTableFile=rby_espita +OldRodOffset=0xE102 +GoodRodOffset=0xE12F +SuperRodTableOffset=0xF5ED2 +MapNameTableOffset=0x7139F +PokedexOrder=0x41041 +TypeEffectivenessOffset=0x3E60A +PokemonMovesetsTableOffset=0x3B1F2 +PokemonMovesetsDataSize=0xC92 +PokemonMovesetsExtraSpaceOffset=0 +ItemNamesOffset=0x45B8 +StarterOffsets2=[0x3A297] +TrainerDataTableOffset=0x39DDE +ExtraTrainerMovesTableOffset=0x39C78 +TMMovesOffset=0x12352 +TrainerClassNamesOffsets=[0x27E80, 0x3997E] +IntroPokemonOffset=0x5F22 +IntroCryOffset=0x1A4B +SpecialMapPointerTable=0xF2609 +HiddenItemRoutine=0x75F6F +TradeTableOffset=0x71C0D +TextDelayFunctionOffset=0x38CD +PCPotionOffset=0x5EE9 +MonPaletteIndicesOffset=0x72911 +SGBPalettesOffset=0x729A9 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0xF213C], Level=[0xF213B]} // Magikarp +StaticPokemon{}={Species=[0x1CF7B, 0x1CF8C, 0x1CFEB], Level=[0x1CF8B]} // Bulbasaur +StaticPokemon{}={Species=[0x5159A, 0x515AB], Level=[0x515AA]} // Charmander +StaticPokemon{}={Species=[0xF19B0, 0xF19C1], Level=[0xF19C0]} // Squirtle +StaticPokemon{}={Species=[0x1D653], Level=[0x1D652]} // Eevee +StaticPokemon{}={Species=[0x5CE11], Level=[0x5CE29]} // Hitmonlee +StaticPokemon{}={Species=[0x5CE59], Level=[0x5CE71]} // Hitmonchan +StaticPokemon{}={Species=[0x51DD2], Level=[0x51DD1]} // Lapras +StaticPokemon{}={Species=[0x61054], Level=[0x7562B]} // Omanyte +StaticPokemon{}={Species=[0x61058], Level=[0x7562B]} // Kabuto +StaticPokemon{}={Species=[0x61050], Level=[0x7562B]} // Aerodactyl +StaticPokemon{}={Species=[0x594CC, 0x61C0E], Level=[0x594D1]} // Snorlax 1 +StaticPokemon{}={Species=[0x5980C], Level=[0x59811]} // Snorlax 2 +StaticPokemon{}={Species=[0x1DCE0], Level=[0x1DCE1]} // Voltorb 1 +StaticPokemon{}={Species=[0x1DCE8], Level=[0x1DCE9]} // Voltorb 2 +StaticPokemon{}={Species=[0x1DCF0], Level=[0x1DCF1]} // Voltorb 3 +StaticPokemon{}={Species=[0x1DD00], Level=[0x1DD01]} // Voltorb 4 +StaticPokemon{}={Species=[0x1DD08], Level=[0x1DD09]} // Voltorb 5 +StaticPokemon{}={Species=[0x1DD18], Level=[0x1DD19]} // Voltorb 6 +StaticPokemon{}={Species=[0x1DCF8], Level=[0x1DCF9]} // Electrode 1 +StaticPokemon{}={Species=[0x1DD10], Level=[0x1DD11]} // Electrode 2 +StaticPokemon{}={Species=[0x46B1A, 0x46B5A, 0x5DBD9], Level=[0x46B5B]} // Articuno +StaticPokemon{}={Species=[0x1DCC0, 0x1DD20], Level=[0x1DD21]} // Zapdos +StaticPokemon{}={Species=[0x518FE, 0x519A1], Level=[0x519A2]} // Moltres +StaticPokemon{}={Species=[0x4618D, 0x461A5], Level=[0x461A6]} // Mewtwo +StaticPokemon{}={Species=[0x527B7, 0x528E9], Level=[0x528EA]} // Abra +StaticPokemon{}={Species=[0x527B8, 0x528EB], Level=[0x528EC]} // Vulpix +StaticPokemon{}={Species=[0x527B9, 0x528ED], Level=[0x528EE]} // Wigglytuff +StaticPokemon{}={Species=[0x527C2, 0x528EF], Level=[0x528F0]} // Scyther +StaticPokemon{}={Species=[0x527C3, 0x528F1], Level=[0x528F2]} // Pinsir +StaticPokemon{}={Species=[0x527C4, 0x528F3], Level=[0x528F4]} // Porygon +StaticPokemonGhostMarowak{}={Species=[0xD43D, 0x60B22, 0x60BF9, 0x70946, 0xF4070, 0xF608D], Level=[0x60B27]} +PikachuHappinessCheckOffset=0x1CF64 +CRC32=964B7A10 diff --git a/src/com/pkrandom/config/gen2_offsets.ini b/src/com/pkrandom/config/gen2_offsets.ini new file mode 100755 index 0000000..7033fe5 --- /dev/null +++ b/src/com/pkrandom/config/gen2_offsets.ini @@ -0,0 +1,1026 @@ +[Gold (U)] +Game=AAUE +Version=0 +NonJapanese=1 +Type=GS +ExtraTableFile=gsc_english +BWXPTweak=bwexp/gs_en_bwxp +PokemonNamesOffset=0x1B0B74 +PokemonNamesLength=10 +PokemonStatsOffset=0x51B0B +WildPokemonOffset=0x2AB35 +FishingWildsOffset=0x92A52 +HeadbuttWildsOffset=0xBA47C +HeadbuttTableSize=7 +BCCWildsOffset=0x97BB8 +FleeingDataOffset=0x3C551 +MoveDataOffset=0x41AFE +MoveNamesOffset=0x1B1574 +ItemNamesOffset=0x1B0000 +PokemonMovesetsTableOffset=0x427BD +EggMovesTableOffset=0x239FE +SupportsFourStartingMoves=0 +StarterOffsets1=[0x1800D2, 0x1800D4, 0x1800EB, 0x1800F6] +StarterOffsets2=[0x180114, 0x180116, 0x18012D, 0x180138] +StarterOffsets3=[0x180150, 0x180152, 0x180169, 0x180174] +StarterHeldItems=[0x1800F8, 0x18013A, 0x180176] +CanChangeStarterText=1 +CanChangeTrainerText=1 +StarterTextOffsets=[0x1805F4, 0x180620, 0x18064D] +TrainerClassAmount=0x42 +TrainerDataTableOffset=0x3993E +TrainerDataClassCounts=[1, 1, 1, 1, 1, 1, 1, 1, 15, 0, 1, 3, 1, 1, 1, 1, 1, 1, 1, 5, 1, 12, 18, 19, 15, 1, 19, 20, 16, 13, 31, 5, 2, 3, 1, 14, 22, 21, 19, 12, 12, 6, 2, 20, 9, 1, 3, 8, 5, 9, 4, 12, 21, 19, 2, 9, 7, 3, 12, 6, 8, 5, 1, 1, 2, 5] +TMMovesOffset=0x11A66 +TrainerClassNamesOffset=0x1B0955 +MaxSumOfTrainerNameLengths=4895 +DoublesTrainerClasses=[60] // only twins +IntroSpriteOffset=0x5FDE +IntroCryOffset=0x6061 +MapHeaders=0x940ED +LandmarkTableOffset=0x92382 +LandmarkCount=95 +TradeTableOffset=0xFCC24 +TradeTableSize=6 +TradeNameLength=11 +TradeOTLength=11 +TradesUnused=[] +TextDelayFunctionOffset=0x31E2 +CatchingTutorialOffsets=[0x128DBB, 0x128DF1, 0x128E39] +PicPointers=0x48000 +PokemonPalettes=0xAD3D +TypeEffectivenessOffset=0x34D01 +GuaranteedCatchPrefix=D147FA19D1FE03 +StaticPokemonSupport=1 +GameCornerPokemonNameLength=11 +StaticPokemon{}={Species=[0x111772, 0x111775], Level=[0x111776]} // Lapras +StaticPokemon{}={Species=[0x114DBA, 0x114DBD], Level=[0x114DBE]} // Electrode1 +StaticPokemon{}={Species=[0x114DE5, 0x114DE8], Level=[0x114DE9]} // Electrode2 +StaticPokemon{}={Species=[0x114E10, 0x114E13], Level=[0x114E14]} // Electrode3 +StaticPokemon{}={Species=[0x11C1A6, 0x11C1B6], Level=[0x11C1B7]} // Lugia +StaticPokemon{}={Species=[0x124F76, 0x124F7A], Level=[0x124F7B]} // RedGyarados +StaticPokemon{}={Species=[0x12E1D6], Level=[0x12E1D7]} // Sudowoodo +StaticPokemon{}={Species=[0x13D2A4, 0x13D2AB], Level=[0x13D2AC]} // Snorlax +StaticPokemon{}={Species=[0x16E919, 0x16E929], Level=[0x16E92A]} // Ho-Oh +StaticPokemon{}={Species=[0x1146F3, 0x1146FE], Level=[0x1146FF]} // Voltorb +StaticPokemon{}={Species=[0x114706, 0x114711], Level=[0x114712]} // Geodude +StaticPokemon{}={Species=[0x114719, 0x114724], Level=[0x114725]} // Koffing +StaticPokemon{}={Species=[0x73E6], Level=[0x73EB]} // Shuckle +StaticPokemon{}={Species=[0x119F20], Level=[0x119F21]} // Tyrogue +StaticPokemon{}={Species=[0x15924F]} // Togepi (egg) +StaticPokemon{}={Species=[0x1599FC], Level=[0x1599FD]} // Kenya +StaticPokemon{}={Species=[0x15CC10], Level=[0x15CC11]} // Eevee +StaticPokemon{}={Species=[0x2A7D8, 0x3C568, 0x1093D5], Level=[0x2A7E7]} // Raikou +StaticPokemon{}={Species=[0x2A7DD, 0x3C569, 0x1093E3], Level=[0x2A7E7]} // Entei +StaticPokemon{}={Species=[0x2A7E2, 0x3C56A, 0x1093F1], Level=[0x2A7E7]} // Suicune +StaticPokemonGameCorner{}={Species=[0x15E8B7, 0x15E8C8, 0x15E8CD, 0x15E93D], Level=[0x15E8CE]} // Abra +StaticPokemonGameCorner{}={Species=[0x15E8E5, 0x15E8F6, 0x15E8FB, 0x15E94D], Level=[0x15E8FC]} // Ekans +StaticPokemonGameCorner{}={Species=[0x15E913, 0x15E924, 0x15E929, 0x15E95D], Level=[0x15E92A]} // Dratini +StaticPokemonGameCorner{}={Species=[0x179B9C, 0x179BAD, 0x179BB2, 0x179C22], Level=[0x179BB3]} // Mr.Mime +StaticPokemonGameCorner{}={Species=[0x179BCA, 0x179BDB, 0x179BE0, 0x179C32], Level=[0x179BE1]} // Eevee +StaticPokemonGameCorner{}={Species=[0x179BF8, 0x179C09, 0x179C0E, 0x179C42], Level=[0x179C0F]} // Porygon +StaticEggPokemonOffsets=[14] +TMText[]=[1,0x1755D0,That is\n%m.\e] +TMText[]=[3,0x17933A,TM03 is\n%m.\pIt's a terrifying\nmove!\e] +TMText[]=[5,0x12CC1A,WROOOAR!\nIT'S %m!\e] +TMText[]=[6,0x17031D,JANINE: You're so\ntough! I have a \lspecial gift!\pIt's %m!\e] +TMText[]=[7,0x151346,MANAGER: TM07 is\nmy %m.\pIt's a powerful\ntechnique!\e] +TMText[]=[8,0x12E465,That happens to be\n%m.\pIf any rocks are\nin your way, find\lROCK SMASH!\e] +TMText[]=[10,0x14D5B5,Do you see it? It\n is %m!\e] +TMText[]=[11,0x10DEC0,It's %m.\nUse it wisely.\e] +TMText[]=[12,0x15F058,It's %m.\pUse it on\nenemy [POKé]MON.\e] +TMText[]=[13,0x14519F,That there's\n%m.\pIt's a rare move.\e] +TMText[]=[16,0x1456C0,That TM contains\n%m.\pIt demonstrates\nthe harshness of\lwinter.\e] +TMText[]=[19,0x17A052,ERIKA: That was a\ndelightful match.\pI felt inspired.\nPlease, I wish you\lto have this TM.\pIt's %m.\pIt is a wonderful\nmove!\pPlease use it if\nit pleases you…\e] +TMText[]=[23,0x144387,…That teaches\n%m.\e] +TMText[]=[24,0x11C6D7,That contains\n%m.\pIf you don't want\nit, you don't have\lto take it.\e] +TMText[]=[24,0x14C4A8,That contains\n%m.\pIf you don't want\nit, you don't have\lto take it.\e] +TMText[]=[29,0x184B57,TM29 is\n%m.\pIt may be\nuseful.\e] +TMText[]=[30,0x1493D7,It's %m.\pUse it if it\nappeals to you.\e] +TMText[]=[31,0x1583B6,By using a TM, a\n[POKé]MON will\pinstantly learn a\nnew move.\pThink before you\nact--a TM can be\lused only once.\pTM31 contains\n%m.\e] +TMText[]=[37,0x18244E,TM37 happens to be\n%m.\pIt's for advanced\ntrainers only.\pUse it if you\ndare. Good luck!\e] +TMText[]=[42,0x138344,TM42 contains\n%m…\p…Zzz…\e] +TMText[]=[45,0x15C308,It's %m!\pIsn't it just per-\nfect for a cutie\llike me?\e] +TMText[]=[49,0x155061,TM49 contains\n%m.\pIsn't it great?\nI discovered it!\e] +TMText[]=[50,0x129DBC,TM50 is\n%m.\pOoooh…\nIt's scary…\pI don't want to\nhave bad dreams.\e] +CRC32=6BDE3C3E + +[Silver (U)] +Game=AAXE +Version=0 +NonJapanese=1 +Type=GS +CopyTMText=1 +CopyFrom=Gold (U) +BWXPTweak=bwexp/gs_en_bwxp +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x111772, 0x111775], Level=[0x111776]} // Lapras +StaticPokemon{}={Species=[0x114DBA, 0x114DBD], Level=[0x114DBE]} // Electrode1 +StaticPokemon{}={Species=[0x114DE5, 0x114DE8], Level=[0x114DE9]} // Electrode2 +StaticPokemon{}={Species=[0x114E10, 0x114E13], Level=[0x114E14]} // Electrode3 +StaticPokemon{}={Species=[0x11C1A6, 0x11C1C1], Level=[0x11C1C2]} // Lugia +StaticPokemon{}={Species=[0x124F76, 0x124F7A], Level=[0x124F7B]} // RedGyarados +StaticPokemon{}={Species=[0x12E1D6], Level=[0x12E1D7]} // Sudowoodo +StaticPokemon{}={Species=[0x13D2A4, 0x13D2AB], Level=[0x13D2AC]} // Snorlax +StaticPokemon{}={Species=[0x16E919, 0x16E934], Level=[0x16E935]} // Ho-Oh +StaticPokemon{}={Species=[0x1146F3, 0x1146FE], Level=[0x1146FF]} // Voltorb +StaticPokemon{}={Species=[0x114706, 0x114711], Level=[0x114712]} // Geodude +StaticPokemon{}={Species=[0x114719, 0x114724], Level=[0x114725]} // Koffing +StaticPokemon{}={Species=[0x73AC], Level=[0x73B1]} // Shuckle +StaticPokemon{}={Species=[0x119F20], Level=[0x119F21]} // Tyrogue +StaticPokemon{}={Species=[0x15924F]} // Togepi (egg) +StaticPokemon{}={Species=[0x1599FC], Level=[0x1599FD]} // Kenya +StaticPokemon{}={Species=[0x15CC10], Level=[0x15CC11]} // Eevee +StaticPokemon{}={Species=[0x2A7D8, 0x3C568, 0x1093D5], Level=[0x2A7E7]} // Raikou +StaticPokemon{}={Species=[0x2A7DD, 0x3C569, 0x1093E3], Level=[0x2A7E7]} // Entei +StaticPokemon{}={Species=[0x2A7E2, 0x3C56A, 0x1093F1], Level=[0x2A7E7]} // Suicune +StaticEggPokemonOffsets=[14] +StaticPokemonGameCorner{}={Species=[0x15E99C, 0x15E9AD, 0x15E9B2, 0x15EA22], Level=[0x15E9B3]} // Abra +StaticPokemonGameCorner{}={Species=[0x15E9CA, 0x15E9DB, 0x15E9E0, 0x15EA32], Level=[0x15E9E1]} // Sandshrew +StaticPokemonGameCorner{}={Species=[0x15E9F8, 0x15EA09, 0x15EA0E, 0x15EA42], Level=[0x15EA0F]} // Dratini +StaticPokemonGameCorner{}={Species=[0x179B9C, 0x179BAD, 0x179BB2, 0x179C22], Level=[0x179BB3]} // Mr.Mime +StaticPokemonGameCorner{}={Species=[0x179BCA, 0x179BDB, 0x179BE0, 0x179C32], Level=[0x179BE1]} // Eevee +StaticPokemonGameCorner{}={Species=[0x179BF8, 0x179C09, 0x179C0E, 0x179C42], Level=[0x179C0F]} // Porygon +CRC32=8AD48636 + +[Crystal (U)] +Game=BYTE +Version=0 +NonJapanese=1 +Type=Crystal +ExtraTableFile=gsc_english +BWXPTweak=bwexp/crystal_en_bwxp +PokemonNamesOffset=0x53384 +PokemonNamesLength=10 +PokemonStatsOffset=0x51424 +WildPokemonOffset=0x2A5E9 +FishingWildsOffset=0x924E3 +HeadbuttWildsOffset=0xB82FA +HeadbuttTableSize=13 +BCCWildsOffset=0x97D87 +FleeingDataOffset=0x3C59A +MoveDataOffset=0x41AFB +MoveNamesOffset=0x1C9F29 +ItemNamesOffset=0x1C8000 +PokemonMovesetsTableOffset=0x425B1 +EggMovesTableOffset=0x23B11 +SupportsFourStartingMoves=1 +StarterOffsets1=[0x78C7F, 0x78C81, 0x78C98, 0x78CA3] +StarterOffsets2=[0x78CC1, 0x78CC3, 0x78CDA, 0x78CE5] +StarterOffsets3=[0x78CFD, 0x78CFF, 0x78D16, 0x78D21] +StarterHeldItems=[0x78CA5, 0x78CE7, 0x78D23] +CanChangeStarterText=1 +StarterTextOffsets=[0x793D9, 0x79405, 0x79432] +CanChangeTrainerText=1 +TrainerClassAmount=0x43 +TrainerDataTableOffset=0x39999 +TrainerDataClassCounts=[1, 1, 1, 1, 1, 1, 1, 1, 15, 0, 1, 3, 1, 1, 1, 1, 1, 1, 1, 5, 1, 14, 24, 19, 17, 1, 20, 21, 17, 15, 31, 5, 2, 3, 1, 19, 25, 21, 19, 13, 14, 6, 2, 22, 9, 1, 3, 8, 6, 9, 4, 12, 26, 22, 2, 12, 7, 3, 14, 6, 10, 6, 1, 1, 2, 5, 1] +TMMovesOffset=0x1167A +TrainerClassNamesOffset=0x2C1EF +MaxSumOfTrainerNameLengths=4124 +DoublesTrainerClasses=[60] // only twins +IntroSpriteOffset=0x5FD2 +IntroCryOffset=0x6050 +MapHeaders=0x94000 +LandmarkTableOffset=0x1CA8C3 +LandmarkCount=96 +TradeTableOffset=0xFCE58 +TradeTableSize=7 +TradeNameLength=11 +TradeOTLength=11 +TradesUnused=[] +TextDelayFunctionOffset=0x313D +CatchingTutorialOffsets=[0x1A0F90, 0x1A0FC6, 0x1A100E] +PicPointers=0x120000 +PokemonPalettes=0xA8CE +MoveTutorMoves=[0x492B4, 0x492B7, 0x492B1] +MoveTutorMenuOffset=0x19896C +MoveTutorMenuNewSpace=0x19BB00 +TypeEffectivenessOffset=0x34BB1 +GuaranteedCatchPrefix=47FA30D2FE03 +StaticPokemonSupport=1 +GameCornerPokemonNameLength=11 +StaticPokemon{}={Species=[0x5A321, 0x5A324], Level=[0x5A325]} // Lapras +StaticPokemon{}={Species=[0x6D102, 0x6D105], Level=[0x6D106]} // Electrode1 +StaticPokemon{}={Species=[0x6D12D, 0x6D130], Level=[0x6D131]} // Electrode2 +StaticPokemon{}={Species=[0x6D158, 0x6D15B], Level=[0x6D15C]} // Electrode3 +StaticPokemon{}={Species=[0x18C51E, 0x18C52A], Level=[0x18C52B]} // Lugia +StaticPokemon{}={Species=[0x7006A, 0x7006E], Level=[0x7006F]} // RedGyarados +StaticPokemon{}={Species=[0x194068], Level=[0x194069]} // Sudowoodo +StaticPokemon{}={Species=[0x1AA9B1, 0x1AA9B8], Level=[0x1AA9B9]} // Snorlax +StaticPokemon{}={Species=[0x7724A, 0x77256], Level=[0x77257]} // Ho-Oh +StaticPokemon{}={Species=[0x4A6FD, 0x71E35, 0x1850E5, 0x1850EA, 0x186198, 0x1861D2], Level=[0x1850EB]} // Suicune +StaticPokemon{}={Species=[0x6CA38, 0x6CA43], Level=[0x6CA44]} // Voltorb +StaticPokemon{}={Species=[0x6CA4B, 0x6CA56], Level=[0x6CA57]} // Geodude +StaticPokemon{}={Species=[0x6CA5E, 0x6CA69], Level=[0x6CA6A]} // Koffing +StaticPokemon{}={Species=[0x730A], Level=[0x730F]} // Shuckle +StaticPokemon{}={Species=[0x7E22A], Level=[0x7E22B]} // Tyrogue +StaticPokemon{}={Species=[0x694E2]} // Togepi (egg) +StaticPokemon{}={Species=[0x69D65], Level=[0x69D66]} // Kenya +StaticPokemon{}={Species=[0x54C06], Level=[0x54C07]} // Eevee +StaticPokemon{}={Species=[0x18D1D7], Level=[0x18D1D8]} // Dratini +StaticPokemon{}={Species=[0x2A2A1, 0x3C5B1, 0x4A6E9, 0x1850A5, 0x18617C], Level=[0x2A2AB]} // Raikou +StaticPokemon{}={Species=[0x2A2A6, 0x3C5B2, 0x4A6F3, 0x1850C6, 0x18618A], Level=[0x2A2AB]} // Entei +StaticPokemonOddEggOffset=0x1FB56E +StaticPokemonOddEggDataSize=0x3B +StaticPokemonGameCorner{}={Species=[0x56D34, 0x56D45, 0x56D4A, 0x56DBA], Level=[0x56D4B]} // Abra +StaticPokemonGameCorner{}={Species=[0x56D62, 0x56D73, 0x56D78, 0x56DCA], Level=[0x56D79]} // Cubone +StaticPokemonGameCorner{}={Species=[0x56D90, 0x56DA1, 0x56DA6, 0x56DDA], Level=[0x56DA7]} // Wobbuffet +StaticPokemonGameCorner{}={Species=[0x727FB, 0x7280C, 0x72811, 0x72881], Level=[0x72812]} // Pikachu +StaticPokemonGameCorner{}={Species=[0x72829, 0x7283A, 0x7283F, 0x72891], Level=[0x72840]} // Porygon +StaticPokemonGameCorner{}={Species=[0x72857, 0x72868, 0x7286D, 0x728A1], Level=[0x7286E]} // Larvitar +StaticEggPokemonOffsets=[15] +TMText[]=[1,0x9D8DB,That is\n%m.\e] +TMText[]=[3,0x71DB4,TM03 is\n%m.\pIt's a terrifying\nmove!\e] +TMText[]=[5,0x19118D,WROOOAR!\nIT'S %m!\e] +TMText[]=[6,0x196003,JANINE: You're so\ntough! I have a \lspecial gift!\pIt's %m!\e] +TMText[]=[7,0x1893F5,MANAGER: TM07 is\nmy %m.\pIt's a powerful\ntechnique!\e] +TMText[]=[8,0x19452D,That happens to be\n%m.\pIf any rocks are\nin your way, find\lROCK SMASH!\e] +TMText[]=[10,0x19A5DF,Do you see it? It\n is %m!\e] +TMText[]=[11,0x5E822,It's %m.\nUse it wisely.\e] +TMText[]=[12,0x62DF7,It's %m.\pUse it on\nenemy [POKé]MON.\e] +TMText[]=[13,0x9D1C8,That there's\n%m.\pIt's a rare move.\e] +TMText[]=[16,0x199DF0,That TM contains\n%m.\pIt demonstrates\nthe harshness of\lwinter.\e] +TMText[]=[19,0x72CB1,ERIKA: That was a\ndelightful match.\pI felt inspired.\nPlease, I wish you\lto have this TM.\pIt's %m.\pIt is a wonderful\nmove!\pPlease use it if\nit pleases you…\e] +TMText[]=[23,0x9C3A6,…That teaches\n%m.\e] +TMText[]=[24,0x18CA0E,That contains\n%m.\pIf you don't want\nit, you don't have\lto take it.\e] +TMText[]=[24,0x1951D2,That contains\n%m.\pIf you don't want\nit, you don't have\lto take it.\e] +TMText[]=[29,0x18A7BC,TM29 is\n%m.\pIt may be\nuseful.\e] +TMText[]=[30,0x9A0ED,It's %m.\pUse it if it\nappeals to you.\e] +TMText[]=[31,0x68649,By using a TM, a\n[POKé]MON will\pinstantly learn a\nnew move.\pThink before you\nact--a TM can be\lused only once.\pTM31 contains\n%m.\e] +TMText[]=[37,0x7B490,TM37 happens to be\n%m.\pIt's for advanced\ntrainers only.\pUse it if you\ndare. Good luck!\e] +TMText[]=[42,0x1A9D87,TM42 contains\n%m…\p…Zzz…\e] +TMText[]=[45,0x54303,It's %m!\pIsn't it just per-\nfect for a cutie\llike me?\e] +TMText[]=[49,0x18EEFB,TM49 contains\n%m.\pIsn't it great?\nI discovered it!\e] +TMText[]=[50,0x1A5897,TM50 is\n%m.\pOoooh…\nIt's scary…\pI don't want to\nhave bad dreams.\e] +CRC32=EE6F5188 + +[Crystal (U 1.1)] +Game=BYTE +Version=1 +NonJapanese=1 +Type=Crystal +CopyTMText=1 +CopyStaticPokemon=1 +CopyFrom=Crystal (U) +BWXPTweak=bwexp/crystal_en_bwxp +CRC32=3358E30A + +[Crystal SpeedChoice v3] +Game=KAPB +Version=2 +NonJapanese=1 +Type=Crystal +ExtraTableFile=gsc_english +PokemonNamesOffset=0x53390 +PokemonNamesLength=10 +PokemonStatsOffset=0x51430 +WildPokemonOffset=0x2A614 +FishingWildsOffset=0x924E3 +HeadbuttWildsOffset=0xB82FA +HeadbuttTableSize=13 +BCCWildsOffset=0x97DAA +FleeingDataOffset=0x3C59A +MoveDataOffset=0x2F8E0 +MoveNamesOffset=0x7442 +ItemNamesOffset=0x1C8000 +PokemonMovesetsTableOffset=0x41ED4 +SupportsFourStartingMoves=1 +StarterOffsets1=[0x78C7F, 0x78C81, 0x78C98, 0x78CA3] +StarterOffsets2=[0x78CC1, 0x78CC3, 0x78CDA, 0x78CE5] +StarterOffsets3=[0x78CFD, 0x78CFF, 0x78D16, 0x78D21] +StarterHeldItems=[0x78CA5, 0x78CE7, 0x78D23] +StarterTextOffsets=[0x793D9, 0x79405, 0x79432] +CanChangeStarterText=1 +CanChangeTrainerText=1 +TrainerClassAmount=0x43 +TrainerDataTableOffset=0x39999 +TrainerDataClassCounts=[1, 1, 1, 1, 1, 1, 1, 1, 15, 0, 1, 3, 1, 1, 1, 1, 1, 1, 1, 5, 1, 14, 24, 19, 17, 1, 20, 21, 17, 15, 31, 5, 2, 3, 1, 19, 25, 21, 19, 13, 14, 6, 2, 22, 9, 1, 3, 8, 6, 9, 4, 12, 26, 22, 2, 12, 7, 3, 14, 6, 10, 6, 1, 1, 2, 5, 1] +TMMovesOffset=0x1167A +TrainerClassNamesOffset=0x2C1EF +MaxSumOfTrainerNameLengths=4124 +DoublesTrainerClasses=[60] // only twins +IntroSpriteOffset=0x5F93 +IntroCryOffset=0x6011 +MapHeaders=0x94000 +LandmarkTableOffset=0x1CA8C3 +LandmarkCount=96 +TradeTableOffset=0xFCE58 +TradeTableSize=7 +TradeNameLength=11 +TradeOTLength=11 +TradesUnused=[] +TextDelayFunctionOffset=0 +CatchingTutorialOffsets=[0x1A0F90, 0x1A0FC6, 0x1A100E] +PicPointers=0x120000 +PokemonPalettes=0xA8CE +MoveTutorMoves=[0x492B4, 0x492B5, 0x492B6] +MoveTutorMenuOffset=0x19896C +MoveTutorMenuNewSpace=0x19899A +CheckValueOffset=0x3E95 +StaticPokemonSupport=0 +TMText[]=[1,0x9D8DB,That is\n%m.\e] +TMText[]=[3,0x71FD9,TM03 is\n%m.\pIt's a terrifying\nmove!\e] +TMText[]=[5,0x19118D,WROOOAR!\nIT'S %m!\e] +TMText[]=[6,0x196003,JANINE: You're so\ntough! I have a \lspecial gift!\pIt's %m!\e] +TMText[]=[7,0x1893F5,MANAGER: TM07 is\nmy %m.\pIt's a powerful\ntechnique!\e] +TMText[]=[8,0x19452D,That happens to be\n%m.\pIf any rocks are\nin your way, find\lROCK SMASH!\e] +TMText[]=[10,0x19A6DF,Do you see it? It\n is %m!\e] +TMText[]=[11,0x5E822,It's %m.\nUse it wisely.\e] +TMText[]=[12,0x62DF7,It's %m.\pUse it on\nenemy [POKé]MON.\e] +TMText[]=[13,0x9D1C8,That there's\n%m.\pIt's a rare move.\e] +TMText[]=[16,0x199EF0,That TM contains\n%m.\pIt demonstrates\nthe harshness of\lwinter.\e] +TMText[]=[19,0x72ED6,That was a\ndelightful match.\pI felt inspired.\nPlease, I wish you\lto have this TM.\pIt's %m.\pIt is a wonderful\nmove!\pPlease use it if\nit pleases you…\e] +TMText[]=[23,0x9C3A6,…That teaches\n%m.\e] +TMText[]=[24,0x18CA0E,That contains\n%m.\pIf you don't want\nit, you don't have\lto take it.\e] +TMText[]=[24,0x1951D2,That contains\n%m.\pIf you don't want\nit, you don't have\lto take it.\e] +TMText[]=[29,0x18A7BC,TM29 is\n%m.\pIt may be\nuseful.\e] +TMText[]=[30,0x9A0ED,It's %m.\pUse it if it\nappeals to you.\e] +TMText[]=[31,0x68654,By using a TM, a\n[POKé]MON will\pinstantly learn a\nnew move.\pThink before you\nact--a TM can be\lused only once.\pTM31 contains\n%m.\e] +TMText[]=[37,0x7B490,TM37 happens to be\n%m.\pIt's for advanced\ntrainers only.\pUse it if you\ndare. Good luck!\e] +TMText[]=[42,0x1A9D87,TM42 contains\n%m…\p…Zzz…\e] +TMText[]=[45,0x54303,It's %m!\pIsn't it just per-\nfect for a cutie\llike me?\e] +TMText[]=[49,0x18EF06,TM49 contains\n%m.\pIsn't it great?\nI discovered it!\e] +TMText[]=[50,0x1A5897,TM50 is\n%m.\pOoooh…\nIt's scary…\pI don't want to\nhave bad dreams.\e] + +[Gold (J 1.0)] +Game=AAUJ +Version=0 +NonJapanese=0 +Type=GS +PokemonNamesOffset=0x53A09 +PokemonNamesLength=5 +PokemonStatsOffset=0x51AA9 +WildPokemonOffset=0x2AC1B +FishingWildsOffset=0x929A1 +HeadbuttWildsOffset=0xBA47C +HeadbuttTableSize=7 +BCCWildsOffset=0x97BDC +FleeingDataOffset=0x3C551 +MoveDataOffset=0x41C6C +MoveNamesOffset=0x4163E +ItemNamesOffset=0x7293 +PokemonMovesetsTableOffset=0x4295F +EggMovesTableOffset=0x23B07 +SupportsFourStartingMoves=0 +StarterOffsets1=[0x4E598, 0x4E59A, 0x4E5B1, 0x4E5BC] +StarterOffsets2=[0x4E5DA, 0x4E5DC, 0x4E5F3, 0x4E5FE] +StarterOffsets3=[0x4E616, 0x4E618, 0x4E62F, 0x4E63A] +StarterHeldItems=[0x4E5BE, 0x4E600, 0x4E63C] +CanChangeStarterText=0 +CanChangeTrainerText=0 +TrainerClassAmount=0x42 +TrainerDataTableOffset=0x3995C +TrainerDataClassCounts=[1, 1, 1, 1, 1, 1, 1, 1, 15, 0, 1, 3, 1, 1, 1, 1, 1, 1, 1, 5, 1, 12, 18, 19, 15, 1, 19, 20, 16, 13, 31, 5, 2, 3, 1, 14, 22, 21, 19, 12, 12, 6, 2, 20, 9, 1, 3, 8, 5, 9, 4, 12, 21, 19, 2, 9, 7, 3, 12, 6, 8, 5, 1, 1, 2, 5] +TMMovesOffset=0x11A00 +TrainerClassNamesOffset=0x2D2D6 +DoublesTrainerClasses=[60] // only twins +IntroSpriteOffset=0x5FEC +IntroCryOffset=0x60ED +MapHeaders=0x940ED +LandmarkTableOffset=0x924B6 +LandmarkCount=95 +TradeTableOffset=0xFCC23 +TradeTableSize=6 +TradeNameLength=4 +TradeOTLength=3 +TradesUnused=[] +TextDelayFunctionOffset=0x319E +CatchingTutorialOffsets=[0xD1295, 0xD12CB, 0xD1313] +PicPointers=0x48000 +PokemonPalettes=0xACC3 +TypeEffectivenessOffset=0x34CFD +GuaranteedCatchPrefix=47FA0BD1FE03 +StaticPokemonSupport=0 +CRC32=524478D4 + +[Gold (J 1.1)] +Game=AAUJ +Version=1 +NonJapanese=0 +Type=GS +CopyFrom=Gold (J 1.0) +CRC32=4EF7F2A5 + +[Silver (J 1.0)] +Game=AAXJ +Version=0 +NonJapanese=0 +Type=GS +CopyFrom=Gold (J 1.0) +CRC32=BE1B928A + +[Silver (J 1.1)] +Game=AAXJ +Version=1 +NonJapanese=0 +Type=GS +CopyFrom=Silver (J 1.0) +CRC32=0AEA5383 + +[Crystal (J)] +Game=BXTJ +Version=0 +NonJapanese=0 +Type=Crystal +PokemonNamesOffset=0x5341A +PokemonNamesLength=5 +PokemonStatsOffset=0x514BA +WildPokemonOffset=0x2A60C +FishingWildsOffset=0x92A56 +HeadbuttWildsOffset=0xB82DA +HeadbuttTableSize=13 +BCCWildsOffset=0x97D49 +FleeingDataOffset=0x3C599 +MoveDataOffset=0x41C69 +MoveNamesOffset=0x4163B +ItemNamesOffset=0x70FA +PokemonMovesetsTableOffset=0x42753 +EggMovesTableOffset=0x23B8C +SupportsFourStartingMoves=0 +StarterOffsets1=[0x6E294, 0x6E296, 0x6E2AD, 0x6E2B8] +StarterOffsets2=[0x6E2D6, 0x6E2D8, 0x6E2EF, 0x6E2FA] +StarterOffsets3=[0x6E312, 0x6E314, 0x6E32B, 0x6E336] +StarterHeldItems=[0x6E2BA, 0x6E2FC, 0x6E338] +CanChangeStarterText=0 +CanChangeTrainerText=0 +TrainerClassAmount=0x43 +TrainerDataTableOffset=0x399BA +TrainerDataClassCounts=[1, 1, 1, 1, 1, 1, 1, 1, 15, 0, 1, 3, 1, 1, 1, 1, 1, 1, 1, 5, 1, 14, 24, 19, 17, 1, 20, 21, 17, 15, 31, 5, 2, 3, 1, 19, 25, 21, 19, 13, 14, 6, 2, 22, 9, 1, 3, 8, 6, 9, 4, 12, 26, 22, 2, 12, 7, 3, 14, 6, 10, 6, 1, 1, 2, 5, 1] +TMMovesOffset=0x11614 +TrainerClassNamesOffset=0x2D319 +DoublesTrainerClasses=[60] // only twins +MapHeaders=0x94000 +LandmarkTableOffset=0x92557 +LandmarkCount=96 +TradeTableOffset=0xFCE57 +TradeTableSize=7 +TradeNameLength=4 +TradeOTLength=3 +TradesUnused=[] +TextDelayFunctionOffset=0x3109 +PicPointers=0x120000 +PokemonPalettes=0xA88B +TypeEffectivenessOffset=0x34BB1 +GuaranteedCatchPrefix=47FA61D2FE03 +StaticPokemonSupport=0 +IntroSpriteOffset=0x5FC2 +IntroCryOffset=0x60BE +MoveTutorMoves=[0x49206, 0x49209, 0x49203] +CatchingTutorialOffsets=[0x993C1, 0x993F7, 0x9943F] +CRC32=270C4ECC + +[Gold (F)] +Game=AAUF +Version=0 +NonJapanese=1 +Type=GS +CopyFrom=Gold (U) +ExtraTableFile=gsc_freger +PokemonNamesOffset=0x1B0BC5 +PokemonStatsOffset=0x51B10 +WildPokemonOffset=0x2ABB2 +FishingWildsOffset=0x92454 +BCCWildsOffset=0x97BD7 +MoveDataOffset=0x41B09 +MoveNamesOffset=0x1B15C5 +PokemonMovesetsTableOffset=0x427C8 +EggMovesTableOffset=0x23A00 +TMMovesOffset=0x11A65 +TrainerClassNamesOffset=0x1B0995 +IntroSpriteOffset=0x6013 +IntroCryOffset=0x6096 +LandmarkTableOffset=0x9C02D +LandmarkCount=95 +TextDelayFunctionOffset=0x31F4 +CatchingTutorialOffsets=[0x128D9C, 0x128DD2, 0x128E1A] +StaticPokemonSupport=1 +CanChangeStarterText=0 +GameCornerPokemonNameLength=11 +StaticPokemon{}={Species=[0x1117E7, 0x1117EA], Level=[0x1117EB]} // Lapras +StaticPokemon{}={Species=[0x114DA4, 0x114DA7], Level=[0x114DA8]} // Electrode1 +StaticPokemon{}={Species=[0x114DCF, 0x114DD2], Level=[0x114DD3]} // Electrode2 +StaticPokemon{}={Species=[0x114DFA, 0x114DFD], Level=[0x114DFE]} // Electrode3 +StaticPokemon{}={Species=[0x11C1A6, 0x11C1B6], Level=[0x11C1B7]} // Lugia +StaticPokemon{}={Species=[0x125051, 0x125055], Level=[0x125056]} // RedGyarados +StaticPokemon{}={Species=[0x12E13B], Level=[0x12E13C]} // Sudowoodo +StaticPokemon{}={Species=[0x13D1A8, 0x13D1AF], Level=[0x13D1B0]} // Snorlax +StaticPokemon{}={Species=[0x16E919, 0x16E929], Level=[0x16E92A]} // Ho-Oh +StaticPokemon{}={Species=[0x1146FF, 0x11470A], Level=[0x11470B]} // Voltorb +StaticPokemon{}={Species=[0x114712, 0x11471D], Level=[0x11471E]} // Geodude +StaticPokemon{}={Species=[0x114725, 0x114730], Level=[0x114731]} // Koffing +StaticPokemon{}={Species=[0x741B], Level=[0x7420]} // Shuckle +StaticPokemon{}={Species=[0x119EC2], Level=[0x119EC3]} // Tyrogue +StaticPokemon{}={Species=[0x159572]} // Togepi (egg) +StaticPokemon{}={Species=[0x16035E], Level=[0x16035F]} // Kenya +StaticPokemon{}={Species=[0x15CEB9], Level=[0x15CEBA]} // Eevee +StaticPokemon{}={Species=[0x2A855, 0x3C568, 0x1094CB], Level=[0x2A864]} // Raikou +StaticPokemon{}={Species=[0x2A85A, 0x3C569, 0x1094D9], Level=[0x2A864]} // Entei +StaticPokemon{}={Species=[0x2A85F, 0x3C56A, 0x1094E7], Level=[0x2A864]} // Suicune +StaticEggPokemonOffsets=[14] +CRC32=37A70702 + +[Silver (F)] +Game=AAXF +Version=0 +NonJapanese=1 +Type=GS +CopyFrom=Gold (F) +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x1117E7, 0x1117EA], Level=[0x1117EB]} // Lapras +StaticPokemon{}={Species=[0x114DA4, 0x114DA7], Level=[0x114DA8]} // Electrode1 +StaticPokemon{}={Species=[0x114DCF, 0x114DD2], Level=[0x114DD3]} // Electrode2 +StaticPokemon{}={Species=[0x114DFA, 0x114DFD], Level=[0x114DFE]} // Electrode3 +StaticPokemon{}={Species=[0x11C1A6, 0x11C1C1], Level=[0x11C1C2]} // Lugia +StaticPokemon{}={Species=[0x125051, 0x125055], Level=[0x125056]} // RedGyarados +StaticPokemon{}={Species=[0x12E13B], Level=[0x12E13C]} // Sudowoodo +StaticPokemon{}={Species=[0x13D1A8, 0x13D1AF], Level=[0x13D1B0]} // Snorlax +StaticPokemon{}={Species=[0x16E919, 0x16E934], Level=[0x16E935]} // Ho-Oh +StaticPokemon{}={Species=[0x1146FF, 0x11470A], Level=[0x11470B]} // Voltorb +StaticPokemon{}={Species=[0x114712, 0x11471D], Level=[0x11471E]} // Geodude +StaticPokemon{}={Species=[0x114725, 0x114730], Level=[0x114731]} // Koffing +StaticPokemon{}={Species=[0x73E1], Level=[0x73E6]} // Shuckle +StaticPokemon{}={Species=[0x119EC2], Level=[0x119EC3]} // Tyrogue +StaticPokemon{}={Species=[0x159572]} // Togepi (egg) +StaticPokemon{}={Species=[0x16035E], Level=[0x16035F]} // Kenya +StaticPokemon{}={Species=[0x15CEB9], Level=[0x15CEBA]} // Eevee +StaticPokemon{}={Species=[0x2A855, 0x3C568, 0x1094CB], Level=[0x2A864]} // Raikou +StaticPokemon{}={Species=[0x2A85A, 0x3C569, 0x1094D9], Level=[0x2A864]} // Entei +StaticPokemon{}={Species=[0x2A85F, 0x3C56A, 0x1094E7], Level=[0x2A864]} // Suicune +StaticEggPokemonOffsets=[14] +CRC32=E0C216EA + +[Gold (G)] +Game=AAUD +Version=0 +NonJapanese=1 +Type=GS +CopyFrom=Gold (U) +ExtraTableFile=gsc_freger +PokemonNamesOffset=0x1B0B6B +PokemonStatsOffset=0x51B00 +WildPokemonOffset=0x2ABB5 +FishingWildsOffset=0x9245F +BCCWildsOffset=0x97BD7 +MoveDataOffset=0x41AF1 +MoveNamesOffset=0x1B156B +PokemonMovesetsTableOffset=0x427B0 +EggMovesTableOffset=0x23A00 +TMMovesOffset=0x11A5D +TrainerClassNamesOffset=0x1B0946 +IntroSpriteOffset=0x6016 +IntroCryOffset=0x6099 +LandmarkTableOffset=0x9C02D +LandmarkCount=95 +TextDelayFunctionOffset=0x320F +CatchingTutorialOffsets=[0x128EEE, 0x128F24, 0x128F6C] +StaticPokemonSupport=1 +CanChangeStarterText=0 +GameCornerPokemonNameLength=11 +StaticPokemon{}={Species=[0x111A29, 0x111A2C], Level=[0x111A2D]} // Lapras +StaticPokemon{}={Species=[0x114E5F, 0x114E62], Level=[0x114E63]} // Electrode1 +StaticPokemon{}={Species=[0x114E8A, 0x114E8D], Level=[0x114E8E]} // Electrode2 +StaticPokemon{}={Species=[0x114EB5, 0x114EB8], Level=[0x114EB9]} // Electrode3 +StaticPokemon{}={Species=[0x11C1A6, 0x11C1B6], Level=[0x11C1B7]} // Lugia +StaticPokemon{}={Species=[0x125100, 0x125104], Level=[0x125105]} // RedGyarados +StaticPokemon{}={Species=[0x12E5FE], Level=[0x12E5FF]} // Sudowoodo +StaticPokemon{}={Species=[0x13D4E7, 0x13D4EE], Level=[0x13D4EF]} // Snorlax +StaticPokemon{}={Species=[0x16EC22, 0x16EC32], Level=[0x16EC33]} // Ho-Oh +StaticPokemon{}={Species=[0x11470E, 0x114719], Level=[0x11471A]} // Voltorb +StaticPokemon{}={Species=[0x114721, 0x11472C], Level=[0x11472D]} // Geodude +StaticPokemon{}={Species=[0x114734, 0x11473F], Level=[0x114740]} // Koffing +StaticPokemon{}={Species=[0x741D], Level=[0x7422]} // Shuckle +StaticPokemon{}={Species=[0x11A249], Level=[0x11A24A]} // Tyrogue +StaticPokemon{}={Species=[0x159680]} // Togepi (egg) +StaticPokemon{}={Species=[0x1603CA], Level=[0x1603CB]} // Kenya +StaticPokemon{}={Species=[0x15CE51], Level=[0x15CE52]} // Eevee +StaticPokemon{}={Species=[0x2A858, 0x3C568, 0x1095C5], Level=[0x2A867]} // Raikou +StaticPokemon{}={Species=[0x2A85D, 0x3C569, 0x1095D3], Level=[0x2A867]} // Entei +StaticPokemon{}={Species=[0x2A862, 0x3C56A, 0x1095E1], Level=[0x2A867]} // Suicune +StaticEggPokemonOffsets=[14] +CRC32=4889DFAA + +[Silver (G)] +Game=AAXD +Version=0 +NonJapanese=1 +Type=GS +CopyFrom=Gold (G) +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x111A29, 0x111A2C], Level=[0x111A2D]} // Lapras +StaticPokemon{}={Species=[0x114E5F, 0x114E62], Level=[0x114E63]} // Electrode1 +StaticPokemon{}={Species=[0x114E8A, 0x114E8D], Level=[0x114E8E]} // Electrode2 +StaticPokemon{}={Species=[0x114EB5, 0x114EB8], Level=[0x114EB9]} // Electrode3 +StaticPokemon{}={Species=[0x11C1A6, 0x11C1C1], Level=[0x11C1C2]} // Lugia +StaticPokemon{}={Species=[0x125100, 0x125104], Level=[0x125105]} // RedGyarados +StaticPokemon{}={Species=[0x12E5FE], Level=[0x12E5FF]} // Sudowoodo +StaticPokemon{}={Species=[0x13D4E7, 0x13D4EE], Level=[0x13D4EF]} // Snorlax +StaticPokemon{}={Species=[0x16EC22, 0x16EC3D], Level=[0x16EC3E]} // Ho-Oh +StaticPokemon{}={Species=[0x11470E, 0x114719], Level=[0x11471A]} // Voltorb +StaticPokemon{}={Species=[0x114721, 0x11472C], Level=[0x11472D]} // Geodude +StaticPokemon{}={Species=[0x114734, 0x11473F], Level=[0x114740]} // Koffing +StaticPokemon{}={Species=[0x73E4], Level=[0x73E9]} // Shuckle +StaticPokemon{}={Species=[0x11A249], Level=[0x11A24A]} // Tyrogue +StaticPokemon{}={Species=[0x159680]} // Togepi (egg) +StaticPokemon{}={Species=[0x1603CA], Level=[0x1603CB]} // Kenya +StaticPokemon{}={Species=[0x15CE51], Level=[0x15CE52]} // Eevee +StaticPokemon{}={Species=[0x2A858, 0x3C568, 0x1095C5], Level=[0x2A867]} // Raikou +StaticPokemon{}={Species=[0x2A85D, 0x3C569, 0x1095D3], Level=[0x2A867]} // Entei +StaticPokemon{}={Species=[0x2A862, 0x3C56A, 0x1095E1], Level=[0x2A867]} // Suicune +StaticEggPokemonOffsets=[14] +CRC32=96C9DB95 + +[Gold (S)] +Game=AAUS +Version=0 +NonJapanese=1 +Type=GS +CopyFrom=Gold (U) +ExtraTableFile=gsc_espita +PokemonNamesOffset=0x1B0BB8 +PokemonStatsOffset=0x51B19 +WildPokemonOffset=0x2ABA0 +FishingWildsOffset=0x92464 +BCCWildsOffset=0x97BCF +MoveDataOffset=0x41B0F +MoveNamesOffset=0x1B15B8 +PokemonMovesetsTableOffset=0x427CE +EggMovesTableOffset=0x239FF +TMMovesOffset=0x11A79 +TrainerClassNamesOffset=0x1B098B +IntroSpriteOffset=0x6022 +IntroCryOffset=0x60A5 +LandmarkTableOffset=0x9C02D +LandmarkCount=95 +TextDelayFunctionOffset=0x3206 +CatchingTutorialOffsets=[0x128DC3, 0x128DF9, 0x128E41] +StaticPokemonSupport=1 +CanChangeStarterText=0 +GameCornerPokemonNameLength=11 +StaticPokemon{}={Species=[0x111714, 0x111717], Level=[0x111718]} // Lapras +StaticPokemon{}={Species=[0x114DD6, 0x114DD9], Level=[0x114DDA]} // Electrode1 +StaticPokemon{}={Species=[0x114E01, 0x114E04], Level=[0x114E05]} // Electrode2 +StaticPokemon{}={Species=[0x114E2C, 0x114E2F], Level=[0x114E30]} // Electrode3 +StaticPokemon{}={Species=[0x11C1A6, 0x11C1B6], Level=[0x11C1B7]} // Lugia +StaticPokemon{}={Species=[0x124F4F, 0x124F53], Level=[0x124F54]} // RedGyarados +StaticPokemon{}={Species=[0x12E1D3], Level=[0x12E1D4]} // Sudowoodo +StaticPokemon{}={Species=[0x13D2B7, 0x13D2BE], Level=[0x13D2BF]} // Snorlax +StaticPokemon{}={Species=[0x16E97D, 0x16E98D], Level=[0x16E98E]} // Ho-Oh +StaticPokemon{}={Species=[0x114702, 0x11470D], Level=[0x11470E]} // Voltorb +StaticPokemon{}={Species=[0x114715, 0x114720], Level=[0x114721]} // Geodude +StaticPokemon{}={Species=[0x114728, 0x114733], Level=[0x114734]} // Koffing +StaticPokemon{}={Species=[0x742E], Level=[0x7433]} // Shuckle +StaticPokemon{}={Species=[0x119F0B], Level=[0x119F0C]} // Tyrogue +StaticPokemon{}={Species=[0x15935C]} // Togepi (egg) +StaticPokemon{}={Species=[0x160329], Level=[0x16032A]} // Kenya +StaticPokemon{}={Species=[0x15CC68], Level=[0x15CC69]} // Eevee +StaticPokemon{}={Species=[0x2A843, 0x3C568, 0x109391], Level=[0x2A852]} // Raikou +StaticPokemon{}={Species=[0x2A848, 0x3C569, 0x10939F], Level=[0x2A852]} // Entei +StaticPokemon{}={Species=[0x2A84D, 0x3C56A, 0x1093AD], Level=[0x2A852]} // Suicune +StaticEggPokemonOffsets=[14] +CRC32=3434A92B + +[Silver (S)] +Game=AAXS +Version=0 +NonJapanese=1 +Type=GS +CopyFrom=Gold (S) +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x111714, 0x111717], Level=[0x111718]} // Lapras +StaticPokemon{}={Species=[0x114DD6, 0x114DD9], Level=[0x114DDA]} // Electrode1 +StaticPokemon{}={Species=[0x114E01, 0x114E04], Level=[0x114E05]} // Electrode2 +StaticPokemon{}={Species=[0x114E2C, 0x114E2F], Level=[0x114E30]} // Electrode3 +StaticPokemon{}={Species=[0x11C1A6, 0x11C1C1], Level=[0x11C1C2]} // Lugia +StaticPokemon{}={Species=[0x124F4F, 0x124F53], Level=[0x124F54]} // RedGyarados +StaticPokemon{}={Species=[0x12E1D3], Level=[0x12E1D4]} // Sudowoodo +StaticPokemon{}={Species=[0x13D2B7, 0x13D2BE], Level=[0x13D2BF]} // Snorlax +StaticPokemon{}={Species=[0x16E97D, 0x16E998], Level=[0x16E999]} // Ho-Oh +StaticPokemon{}={Species=[0x114702, 0x11470D], Level=[0x11470E]} // Voltorb +StaticPokemon{}={Species=[0x114715, 0x114720], Level=[0x114721]} // Geodude +StaticPokemon{}={Species=[0x114728, 0x114733], Level=[0x114734]} // Koffing +StaticPokemon{}={Species=[0x73F6], Level=[0x73FB]} // Shuckle +StaticPokemon{}={Species=[0x119F0B], Level=[0x119F0C]} // Tyrogue +StaticPokemon{}={Species=[0x15935C]} // Togepi (egg) +StaticPokemon{}={Species=[0x160329], Level=[0x16032A]} // Kenya +StaticPokemon{}={Species=[0x15CC68], Level=[0x15CC69]} // Eevee +StaticPokemon{}={Species=[0x2A843, 0x3C568, 0x109391], Level=[0x2A852]} // Raikou +StaticPokemon{}={Species=[0x2A848, 0x3C569, 0x10939F], Level=[0x2A852]} // Entei +StaticPokemon{}={Species=[0x2A84D, 0x3C56A, 0x1093AD], Level=[0x2A852]} // Suicune +StaticEggPokemonOffsets=[14] +CRC32=1D9FAAC5 + +[Gold (I)] +Game=AAUI +Version=0 +NonJapanese=1 +Type=GS +CopyFrom=Gold (U) +ExtraTableFile=gsc_espita +PokemonNamesOffset=0x1B0BD2 +PokemonStatsOffset=0x51B19 +WildPokemonOffset=0x2AB99 +FishingWildsOffset=0x92447 +BCCWildsOffset=0x97BD5 +MoveDataOffset=0x41AFD +MoveNamesOffset=0x1B15D2 +PokemonMovesetsTableOffset=0x427BC +EggMovesTableOffset=0x239FF +TMMovesOffset=0x11A65 +TrainerClassNamesOffset=0x1B099A +IntroSpriteOffset=0x6011 +IntroCryOffset=0x6094 +LandmarkTableOffset=0x9C02D +LandmarkCount=95 +TextDelayFunctionOffset=0x3207 +CatchingTutorialOffsets=[0x128DFE, 0x128E34, 0x128E7C] +StaticPokemonSupport=1 +CanChangeStarterText=0 +GameCornerPokemonNameLength=11 +StaticPokemon{}={Species=[0x1117CC, 0x1117CF], Level=[0x1117D0]} // Lapras +StaticPokemon{}={Species=[0x114DF5, 0x114DF8], Level=[0x114DF9]} // Electrode1 +StaticPokemon{}={Species=[0x114E20, 0x114E23], Level=[0x114E24]} // Electrode2 +StaticPokemon{}={Species=[0x114E4B, 0x114E4E], Level=[0x114E4F]} // Electrode3 +StaticPokemon{}={Species=[0x11C1A6, 0x11C1B6], Level=[0x11C1B7]} // Lugia +StaticPokemon{}={Species=[0x124F0B, 0x124F0F], Level=[0x124F10]} // RedGyarados +StaticPokemon{}={Species=[0x12E26A], Level=[0x12E26B]} // Sudowoodo +StaticPokemon{}={Species=[0x13D2D2, 0x13D2D9], Level=[0x13D2DA]} // Snorlax +StaticPokemon{}={Species=[0x16E948, 0x16E958], Level=[0x16E959]} // Ho-Oh +StaticPokemon{}={Species=[0x1146CF, 0x1146DA], Level=[0x1146DB]} // Voltorb +StaticPokemon{}={Species=[0x1146E2, 0x1146ED], Level=[0x1146EE]} // Geodude +StaticPokemon{}={Species=[0x1146F5, 0x114700], Level=[0x114701]} // Koffing +StaticPokemon{}={Species=[0x7418], Level=[0x741D]} // Shuckle +StaticPokemon{}={Species=[0x119EF3], Level=[0x119EF4]} // Tyrogue +StaticPokemon{}={Species=[0x1593B0]} // Togepi (egg) +StaticPokemon{}={Species=[0x160332], Level=[0x160333]} // Kenya +StaticPokemon{}={Species=[0x15CC91], Level=[0x15CC92]} // Eevee +StaticPokemon{}={Species=[0x2A83C, 0x3C568, 0x109418], Level=[0x2A84B]} // Raikou +StaticPokemon{}={Species=[0x2A841, 0x3C569, 0x109426], Level=[0x2A84B]} // Entei +StaticPokemon{}={Species=[0x2A846, 0x3C56A, 0x109434], Level=[0x2A84B]} // Suicune +StaticEggPokemonOffsets=[14] +CRC32=4C184CE3 + +[Silver (I)] +Game=AAXI +Version=0 +NonJapanese=1 +Type=GS +CopyFrom=Gold (I) +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x1117CC, 0x1117CF], Level=[0x1117D0]} // Lapras +StaticPokemon{}={Species=[0x114DF5, 0x114DF8], Level=[0x114DF9]} // Electrode1 +StaticPokemon{}={Species=[0x114E20, 0x114E23], Level=[0x114E24]} // Electrode2 +StaticPokemon{}={Species=[0x114E4B, 0x114E4E], Level=[0x114E4F]} // Electrode3 +StaticPokemon{}={Species=[0x11C1A6, 0x11C1C1], Level=[0x11C1C2]} // Lugia +StaticPokemon{}={Species=[0x124F0B, 0x124F0F], Level=[0x124F10]} // RedGyarados +StaticPokemon{}={Species=[0x12E26A], Level=[0x12E26B]} // Sudowoodo +StaticPokemon{}={Species=[0x13D2D2, 0x13D2D9], Level=[0x13D2DA]} // Snorlax +StaticPokemon{}={Species=[0x16E948, 0x16E963], Level=[0x16E964]} // Ho-Oh +StaticPokemon{}={Species=[0x1146CF, 0x1146DA], Level=[0x1146DB]} // Voltorb +StaticPokemon{}={Species=[0x1146E2, 0x1146ED], Level=[0x1146EE]} // Geodude +StaticPokemon{}={Species=[0x1146F5, 0x114700], Level=[0x114701]} // Koffing +StaticPokemon{}={Species=[0x73DE], Level=[0x73E3]} // Shuckle +StaticPokemon{}={Species=[0x119EF3], Level=[0x119EF4]} // Tyrogue +StaticPokemon{}={Species=[0x1593B0]} // Togepi (egg) +StaticPokemon{}={Species=[0x160332], Level=[0x160333]} // Kenya +StaticPokemon{}={Species=[0x15CC91], Level=[0x15CC92]} // Eevee +StaticPokemon{}={Species=[0x2A83C, 0x3C568, 0x109418], Level=[0x2A84B]} // Raikou +StaticPokemon{}={Species=[0x2A841, 0x3C569, 0x109426], Level=[0x2A84B]} // Entei +StaticPokemon{}={Species=[0x2A846, 0x3C56A, 0x109434], Level=[0x2A84B]} // Suicune +StaticEggPokemonOffsets=[14] +CRC32=CBA6D2D4 + +[Crystal (F)] +Game=BYTF +Version=0 +NonJapanese=1 +Type=Crystal +CopyFrom=Crystal (U) +ExtraTableFile=gsc_freger +PokemonNamesOffset=0x53377 +PokemonStatsOffset=0x51417 +WildPokemonOffset=0x2A5F2 +FishingWildsOffset=0x924FA +HeadbuttWildsOffset=0xB830C +BCCWildsOffset=0x97D88 +MoveDataOffset=0x41B06 +MoveNamesOffset=0x1C9F96 +PokemonMovesetsTableOffset=0x425BC +SupportsFourStartingMoves=1 +StarterOffsets1=[0x78C67, 0x78C69, 0x78C80, 0x78C8B] +StarterOffsets2=[0x78CA9, 0x78CAB, 0x78CC2, 0x78CCD] +StarterOffsets3=[0x78CE5, 0x78CE7, 0x78CFE, 0x78D09] +StarterHeldItems=[0x78C8D, 0x78CCF, 0x78D0B] +TMMovesOffset=0x11679 +TrainerClassNamesOffset=0x2C1EF +IntroSpriteOffset=0x5FEC +IntroCryOffset=0x606A +LandmarkTableOffset=0x1CA97F +LandmarkCount=96 +TextDelayFunctionOffset=0x312A +CatchingTutorialOffsets=[0x1A0F54, 0x1A0F8A, 0x1A0FD2] +StaticPokemonSupport=1 +CanChangeStarterText=0 +GameCornerPokemonNameLength=11 +StaticPokemon{}={Species=[0x5A400, 0x5A403], Level=[0x5A404]} // Lapras +StaticPokemon{}={Species=[0x6D10E, 0x6D111], Level=[0x6D112]} // Electrode1 +StaticPokemon{}={Species=[0x6D139, 0x6D13C], Level=[0x6D13D]} // Electrode2 +StaticPokemon{}={Species=[0x6D164, 0x6D167], Level=[0x6D168]} // Electrode3 +StaticPokemon{}={Species=[0x18C515, 0x18C521], Level=[0x18C522]} // Lugia +StaticPokemon{}={Species=[0x7006A, 0x7006E], Level=[0x7006F]} // RedGyarados +StaticPokemon{}={Species=[0x194068], Level=[0x194069]} // Sudowoodo +StaticPokemon{}={Species=[0x1AA8E6, 0x1AA8ED], Level=[0x1AA8EE]} // Snorlax +StaticPokemon{}={Species=[0x77273, 0x7727F], Level=[0x77280]} // Ho-Oh +StaticPokemon{}={Species=[0x4A671, 0x71F4F, 0x1851CA, 0x1851CF, 0x186206, 0x186240], Level=[0x1851D0]} // Suicune +StaticPokemon{}={Species=[0x6CA74, 0x6CA7F], Level=[0x6CA80]} // Voltorb +StaticPokemon{}={Species=[0x6CA87, 0x6CA92], Level=[0x6CA93]} // Geodude +StaticPokemon{}={Species=[0x6CA9A, 0x6CAA5], Level=[0x6CAA6]} // Koffing +StaticPokemon{}={Species=[0x7324], Level=[0x7329]} // Shuckle +StaticPokemon{}={Species=[0x7E1C2], Level=[0x7E1C3]} // Tyrogue +StaticPokemon{}={Species=[0x697B9]} // Togepi (egg) +StaticPokemon{}={Species=[0x69FF3], Level=[0x69FF4]} // Kenya +StaticPokemon{}={Species=[0x54E6C], Level=[0x54E6D]} // Eevee +StaticPokemon{}={Species=[0x18D1A6], Level=[0x18D1A7]} // Dratini +StaticPokemon{}={Species=[0x2A2AA, 0x3C5B1, 0x4A65D, 0x18518A, 0x1861EA], Level=[0x2A2B4]} // Raikou +StaticPokemon{}={Species=[0x2A2AF, 0x3C5B2, 0x4A667, 0x1851AB, 0x1861F8], Level=[0x2A2B4]} // Entei +StaticPokemonOddEggOffset=0x1FB56E +StaticPokemonOddEggDataSize=0x3B +StaticEggPokemonOffsets=[15] +MoveTutorMoves=[0x49212, 0x49215, 0x4920F] +CRC32=878B2AA7 + +[Crystal (G)] +Game=BYTD +Version=0 +NonJapanese=1 +Type=Crystal +CopyFrom=Crystal (U) +ExtraTableFile=gsc_freger +PokemonNamesOffset=0x5336E +PokemonStatsOffset=0x5140E +WildPokemonOffset=0x2A5FE +FishingWildsOffset=0x92502 +HeadbuttWildsOffset=0xB830C +BCCWildsOffset=0x97D88 +MoveDataOffset=0x41AEB +MoveNamesOffset=0x1C9E9D +PokemonMovesetsTableOffset=0x425A1 +SupportsFourStartingMoves=1 +StarterOffsets1=[0x78DFA, 0x78DFC, 0x78E13, 0x78E1E] +StarterOffsets2=[0x78E3C, 0x78E3E, 0x78E55, 0x78E60] +StarterOffsets3=[0x78E78, 0x78E7A, 0x78E91, 0x78E9C] +StarterHeldItems=[0x78E20, 0x78E62, 0x78E9E] +TMMovesOffset=0x11671 +TrainerClassNamesOffset=0x2C1EF +IntroSpriteOffset=0x5FF3 +IntroCryOffset=0x6071 +LandmarkTableOffset=0x1CA8E5 +LandmarkCount=96 +TextDelayFunctionOffset=0x3127 +CatchingTutorialOffsets=[0x1A11AA, 0x1A11E0, 0x1A1228] +StaticPokemonSupport=1 +CanChangeStarterText=0 +GameCornerPokemonNameLength=11 +StaticPokemon{}={Species=[0x5A6D8, 0x5A6DB], Level=[0x5A6DC]} // Lapras +StaticPokemon{}={Species=[0x6D1F9, 0x6D1FC], Level=[0x6D1FD]} // Electrode1 +StaticPokemon{}={Species=[0x6D224, 0x6D227], Level=[0x6D228]} // Electrode2 +StaticPokemon{}={Species=[0x6D24F, 0x6D252], Level=[0x6D253]} // Electrode3 +StaticPokemon{}={Species=[0x18C5B9, 0x18C5C5], Level=[0x18C5C6]} // Lugia +StaticPokemon{}={Species=[0x7006A, 0x7006E], Level=[0x7006F]} // RedGyarados +StaticPokemon{}={Species=[0x194068], Level=[0x194069]} // Sudowoodo +StaticPokemon{}={Species=[0x1AAEBB, 0x1AAEC2], Level=[0x1AAEC3]} // Snorlax +StaticPokemon{}={Species=[0x7761A, 0x77626], Level=[0x77627]} // Ho-Oh +StaticPokemon{}={Species=[0x4A678, 0x72287, 0x18530B, 0x185310, 0x18653E, 0x186578], Level=[0x185311]} // Suicune +StaticPokemon{}={Species=[0x6CAA5, 0x6CAB0], Level=[0x6CAB1]} // Voltorb +StaticPokemon{}={Species=[0x6CAB8, 0x6CAC3], Level=[0x6CAC4]} // Geodude +StaticPokemon{}={Species=[0x6CACB, 0x6CAD6], Level=[0x6CAD7]} // Koffing +StaticPokemon{}={Species=[0x732B], Level=[0x7330]} // Shuckle +StaticPokemon{}={Species=[0x7E585], Level=[0x7E586]} // Tyrogue +StaticPokemon{}={Species=[0x69927]} // Togepi (egg) +StaticPokemon{}={Species=[0x6A2C8], Level=[0x6A2C9]} // Kenya +StaticPokemon{}={Species=[0x54E5A], Level=[0x54E5B]} // Eevee +StaticPokemon{}={Species=[0x18D34F], Level=[0x18D350]} // Dratini +StaticPokemon{}={Species=[0x2A2B6, 0x3C5B1, 0x4A664, 0x1852CB, 0x186522], Level=[0x2A2C0]} // Raikou +StaticPokemon{}={Species=[0x2A2BB, 0x3C5B2, 0x4A66E, 0x1852EC, 0x186530], Level=[0x2A2C0]} // Entei +StaticPokemonOddEggOffset=0x1FB56E +StaticPokemonOddEggDataSize=0x3B +StaticEggPokemonOffsets=[15] +MoveTutorMoves=[0x49213, 0x49216, 0x49210] +CRC32=616D85DE + +[Crystal (S)] +Game=BYTS +Version=0 +NonJapanese=1 +Type=Crystal +CopyFrom=Crystal (U) +ExtraTableFile=gsc_espita +PokemonNamesOffset=0x5338D +PokemonStatsOffset=0x5142D +WildPokemonOffset=0x2A5F0 +FishingWildsOffset=0x9250C +HeadbuttWildsOffset=0xB830C +BCCWildsOffset=0x97D80 +MoveDataOffset=0x41B0E +MoveNamesOffset=0x1CA045 +PokemonMovesetsTableOffset=0x425C4 +SupportsFourStartingMoves=1 +StarterOffsets1=[0x78C70, 0x78C72, 0x78C89, 0x78C94] +StarterOffsets2=[0x78CB2, 0x78CB4, 0x78CCB, 0x78CD6] +StarterOffsets3=[0x78CEE, 0x78CF0, 0x78D07, 0x78D12] +StarterHeldItems=[0x78C96, 0x78CD8, 0x78D14] +TMMovesOffset=0x1168D +TrainerClassNamesOffset=0x2C1EF +IntroSpriteOffset=0x5FF1 +IntroCryOffset=0x606F +LandmarkTableOffset=0x1CAAB4 +LandmarkCount=96 +TextDelayFunctionOffset=0x3127 +CatchingTutorialOffsets=[0x1A0FAC, 0x1A0FE2, 0x1A102A] +StaticPokemonSupport=1 +CanChangeStarterText=0 +GameCornerPokemonNameLength=11 +StaticPokemon{}={Species=[0x5A3EE, 0x5A3F1], Level=[0x5A3F2]} // Lapras +StaticPokemon{}={Species=[0x6D155, 0x6D158], Level=[0x6D159]} // Electrode1 +StaticPokemon{}={Species=[0x6D180, 0x6D183], Level=[0x6D184]} // Electrode2 +StaticPokemon{}={Species=[0x6D1AB, 0x6D1AE], Level=[0x6D1AF]} // Electrode3 +StaticPokemon{}={Species=[0x18C541, 0x18C54D], Level=[0x18C54E]} // Lugia +StaticPokemon{}={Species=[0x7006A, 0x7006E], Level=[0x7006F]} // RedGyarados +StaticPokemon{}={Species=[0x194068], Level=[0x194069]} // Sudowoodo +StaticPokemon{}={Species=[0x1AAA68, 0x1AAA6F], Level=[0x1AAA70]} // Snorlax +StaticPokemon{}={Species=[0x7724B, 0x77257], Level=[0x77258]} // Ho-Oh +StaticPokemon{}={Species=[0x4A66D, 0x71E41, 0x185134, 0x185139, 0x1862E5, 0x18631F], Level=[0x18513A]} // Suicune +StaticPokemon{}={Species=[0x6CA6E, 0x6CA79], Level=[0x6CA7A]} // Voltorb +StaticPokemon{}={Species=[0x6CA81, 0x6CA8C], Level=[0x6CA8D]} // Geodude +StaticPokemon{}={Species=[0x6CA94, 0x6CA9F], Level=[0x6CAA0]} // Koffing +StaticPokemon{}={Species=[0x7329], Level=[0x732E]} // Shuckle +StaticPokemon{}={Species=[0x7E1F7], Level=[0x7E1F8]} // Tyrogue +StaticPokemon{}={Species=[0x695F2]} // Togepi (egg) +StaticPokemon{}={Species=[0x69E73], Level=[0x69E74]} // Kenya +StaticPokemon{}={Species=[0x54C5B], Level=[0x54C5C]} // Eevee +StaticPokemon{}={Species=[0x18D224], Level=[0x18D225]} // Dratini +StaticPokemon{}={Species=[0x2A2A8, 0x3C5B1, 0x4A659, 0x1850F4, 0x1862C9], Level=[0x2A2B2]} // Raikou +StaticPokemon{}={Species=[0x2A2AD, 0x3C5B2, 0x4A663, 0x185115, 0x1862D7], Level=[0x2A2B2]} // Entei +StaticPokemonOddEggOffset=0x1FB56D +StaticPokemonOddEggDataSize=0x3B +StaticEggPokemonOffsets=[15] +MoveTutorMoves=[0x49211, 0x49214, 0x4920E] +CRC32=FF0A6F8A + +[Crystal (I)] +Game=BYTI +Version=0 +NonJapanese=1 +Type=Crystal +CopyFrom=Crystal (U) +ExtraTableFile=gsc_espita +PokemonNamesOffset=0x53393 +PokemonStatsOffset=0x51433 +WildPokemonOffset=0x2A5E0 +FishingWildsOffset=0x924F6 +HeadbuttWildsOffset=0xB830C +BCCWildsOffset=0x97D86 +MoveDataOffset=0x41AFA +MoveNamesOffset=0x1C9E86 +PokemonMovesetsTableOffset=0x425B0 +SupportsFourStartingMoves=1 +StarterOffsets1=[0x78CD4, 0x78CD6, 0x78CED, 0x78CF8] +StarterOffsets2=[0x78D16, 0x78D18, 0x78D2F, 0x78D3A] +StarterOffsets3=[0x78D52, 0x78D54, 0x78D6B, 0x78D76] +StarterHeldItems=[0x78CFA, 0x78D3C, 0x78D78] +TMMovesOffset=0x11679 +TrainerClassNamesOffset=0x2C1EF +IntroSpriteOffset=0x5FEB +IntroCryOffset=0x6069 +LandmarkTableOffset=0x1CA8B4 +LandmarkCount=96 +TextDelayFunctionOffset=0x312B +CatchingTutorialOffsets=[0x1A0F97, 0x1A0FCD, 0x1A1015] +StaticPokemonSupport=1 +CanChangeStarterText=0 +GameCornerPokemonNameLength=11 +StaticPokemon{}={Species=[0x5A35E, 0x5A361], Level=[0x5A362]} // Lapras +StaticPokemon{}={Species=[0x6D16C, 0x6D16F], Level=[0x6D170]} // Electrode1 +StaticPokemon{}={Species=[0x6D197, 0x6D19A], Level=[0x6D19B]} // Electrode2 +StaticPokemon{}={Species=[0x6D1C2, 0x6D1C5], Level=[0x6D1C6]} // Electrode3 +StaticPokemon{}={Species=[0x18C528, 0x18C534], Level=[0x18C535]} // Lugia +StaticPokemon{}={Species=[0x7006A, 0x7006E], Level=[0x7006F]} // RedGyarados +StaticPokemon{}={Species=[0x194068], Level=[0x194069]} // Sudowoodo +StaticPokemon{}={Species=[0x1AAA70, 0x1AAA77], Level=[0x1AAA78]} // Snorlax +StaticPokemon{}={Species=[0x772AC, 0x772B8], Level=[0x772B9]} // Ho-Oh +StaticPokemon{}={Species=[0x4A678, 0x71EE1, 0x185182, 0x185187, 0x186245, 0x18627F], Level=[0x185188]} // Suicune +StaticPokemon{}={Species=[0x6CA3C, 0x6CA47], Level=[0x6CA48]} // Voltorb +StaticPokemon{}={Species=[0x6CA4F, 0x6CA5A], Level=[0x6CA5B]} // Geodude +StaticPokemon{}={Species=[0x6CA62, 0x6CA6D], Level=[0x6CA6E]} // Koffing +StaticPokemon{}={Species=[0x7323], Level=[0x7328]} // Shuckle +StaticPokemon{}={Species=[0x7E217], Level=[0x7E218]} // Tyrogue +StaticPokemon{}={Species=[0x69651]} // Togepi (egg) +StaticPokemon{}={Species=[0x69E5A], Level=[0x69E5B]} // Kenya +StaticPokemon{}={Species=[0x54CA2], Level=[0x54CA3]} // Eevee +StaticPokemon{}={Species=[0x18D1C3], Level=[0x18D1C4]} // Dratini +StaticPokemon{}={Species=[0x2A298, 0x3C5B1, 0x4A664, 0x185142, 0x186229], Level=[0x2A2A2]} // Raikou +StaticPokemon{}={Species=[0x2A29D, 0x3C5B2, 0x4A66E, 0x185163, 0x186237], Level=[0x2A2A2]} // Entei +StaticPokemonOddEggOffset=0x1FB56E +StaticPokemonOddEggDataSize=0x3B +StaticEggPokemonOffsets=[15] +MoveTutorMoves=[0x49215, 0x49218, 0x49212] +CRC32=D45AC039 diff --git a/src/com/pkrandom/config/gen3_offsets.ini b/src/com/pkrandom/config/gen3_offsets.ini new file mode 100755 index 0000000..6d61d76 --- /dev/null +++ b/src/com/pkrandom/config/gen3_offsets.ini @@ -0,0 +1,2366 @@ +[Ruby (U)] +Game=AXVE +Version=0 +Type=Ruby +TableFile=gba_english +FreeSpace=0x700000 +PokemonNameLength=11 +PokemonCount=411 +PokemonStats=0x1FEC18 +PokemonMovesets=0x207BC8 +EggMoves=0x2091DC +PokemonTMHMCompat=0x1FD0F0 +PokemonEvolutions=0x203B68 +StarterPokemon=0x3F76C4 +StarterItems=0x821AA +TrainerData=0x1F04FC +TrainerEntrySize=40 +TrainerCount=0x2B6 +TrainerClassNames=0x1F0208 +TrainerClassCount=58 +TrainerClassNameLength=13 +TrainerNameLength=12 +DoublesTrainerClasses=[27, 42, 55, 56, 57] +EliteFourIndices=[261, 262, 263, 264, 335] +ItemData=0x3C5564 +ItemCount=348 +ItemEntrySize=44 +MoveData=0x1FB12C +MoveCount=354 +MoveDescriptions=0x3C09D8 +MoveNameLength=13 +MoveNames=0x1F8320 +AbilityNameLength=13 +AbilityNames=0x1FA248 +TmMoves=0x376504 +IntroCryOffset=0xA506 +IntroSpriteOffset=0xB2B8 +IntroPaletteOffset=0xB2C4 +IntroOtherOffset=0xB286 +PokemonFrontSprites=0x1E8354 +PokemonNormalPalettes=0x1EA5B4 +ItemBallPic=59 +TradeTableOffset=0x215AC4 +TradeTableSize=3 +TradesUnused=[] +RunIndoorsTweakOffset=0xE5E00 +CatchingTutorialOpponentMonOffset=0x81B00 +CatchingTutorialPlayerMonOffset=0x10F62E +PCPotionOffset=0x4062F0 +PickupTableStartLocator=16001E00170028000200320044003C +PickupItemCount=10 +InstantTextTweak=instant_text/ruby_10_instant_text +TypeEffectivenessOffset=0x1F9720 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x157663, 0x157691], Level=[0x157693]} // Lileep +StaticPokemon{}={Species=[0x1576B6, 0x1576E4], Level=[0x1576E6]} // Anorith +StaticPokemon{}={Species=[0x1A04A3, 0x15DE26, 0x15DE2D], Level=[0x15DE28]} // Groudon +StaticPokemon{}={Species=[0x15CB89, 0x15CB92], Level=[0x15CB94]} // Regirock +StaticPokemon{}={Species=[0x15EFA1, 0x15EFAA], Level=[0x15EFAC]} // Regice +StaticPokemon{}={Species=[0x15F054, 0x15F05D], Level=[0x15F05F]} // Registeel +StaticPokemon{}={Species=[0x160BCE, 0x160BF4], Level=[0x160BF6]} // Latias (Southern Island) +StaticPokemon{}={Species=[0x15F319, 0x15F320], Level=[0x15F31B]} // Rayquaza +StaticPokemon{}={Species=[0x1A05E2, 0x1A05EB], Level=[0x1A05ED]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x1518E8, 0x1518F1], Level=[0x1518F3]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15E903, 0x15E90A], Level=[0x15E905]} // Voltorb 1 +StaticPokemon{}={Species=[0x15E921, 0x15E928], Level=[0x15E923]} // Voltorb 2 +StaticPokemon{}={Species=[0x15E93F, 0x15E946], Level=[0x15E941]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A0500, 0x1A0507], Level=[0x1A0502]} // Electrode 1 +StaticPokemon{}={Species=[0x1A051E, 0x1A0525], Level=[0x1A0520]} // Electrode 2 +StaticPokemon{}={Species=[0x14E79A]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AAAF, 0x15AABF], Level=[0x15AAB1]} // Beldum +StaticPokemon{}={Species=[0x163D99], Level=[0x163D9B]} // Castform +RoamingPokemon{}={Species=[0x110988, 0x1342F8], Level=[0x13425C, 0x13426C]} // Latios +StaticEggPokemonOffsets=[15] +StaticFirstBattleTweak=hardcoded_statics/rs_firstbattle +StaticFirstBattleSpeciesOffset=0xFE003C +StaticFirstBattleLevelOffset=0xFE0008 +StaticFirstBattleOffset=18 +FindMapsWithMonFunctionStartOffset=0x110908 +CreateInitialRoamerMonFunctionStartOffset=0x134240 +TMText[]=[3,15,0,1,0x70,The TM I handed you contains [move].] +TMText[]=[4,14,0,1,0x74,TATE: That TM04 contains... LIZA: [move]!\pTATE: It’s a move that’s perfect... LIZA: For any POKéMON!] +TMText[]=[5,0,29,12,0x0D,All my POKéMON does is [move]... No one dares to come near me...\pSigh... If you would, please take this TM away...] +TMText[]=[5,0,29,12,0x2F,TM05 contains [move].] +TMText[]=[8,3,3,1,0x7C,That TM08 contains [move].] +TMText[]=[9,0,19,32,0x0D,I like filling my mouth with seeds, then spitting them out fast!\pI like you, so you can have this!\pUse it on a POKéMON, and it will learn [move].\pWhat does that have to do with firing seeds? Well, nothing!] +TMText[]=[24,0,2,8,0x4C,WATTSON: Wahahahaha!\pI knew it, \v01\v05! I knew I’d made the right choice asking you!\pThis is my thanks - a TM containing [move]!\pGo on, you’ve earned it!] +TMText[]=[31,15,5,1,0x24,TM31 contains [move]! It’s a move so horrible that I can’t describe it.] +TMText[]=[34,10,0,1,0x8B,That TM34 there contains [move]. You can count on it!] +TMText[]=[39,11,3,1,0x7F,That TM39 contains [move].\pIf you use a TM, it instantly teaches the move to a POKéMON.\pRemember, a TM can be used only once, so think before you use it.] +TMText[]=[40,12,1,1,0x67,TM40 contains [move].] +TMText[]=[41,9,3,13,0x2F,That’s, like, TM41, you know? Hey, it’s [move], you hearing me?\pHey, now, you listen here, like, I’m not laying a torment on you!] +TMText[]=[42,8,1,1,0x48F,DAD: TM42 contains [move].\pIt might be able to turn a bad situation into an advantage.] +TMText[]=[47,24,10,1,0x19,STEVEN: Okay, thank you.\pYou went through all this trouble to deliver that. I need to thank you.\pLet me see... I’ll give you this TM.\pIt contains my favorite move, [move].] +TMText[]=[50,4,1,1,0x7F,That TM50 contains [move].] +MainGameLegendaries=[383] +ShopItemOffsets=[0x14BAD0, 0x14BEB4, 0x152F9C, 0x152FB8, 0x153640, 0x1538E4, 0x153980, 0x153ED4, 0x1552D0, 0x1552FC, 0x156428, 0x1573D8, 0x157C00, 0x157C28, 0x158080, 0x159F44, 0x159F78, 0x159FA8, 0x159FD0, 0x15A030, 0x15A054, 0x15A940, 0x15B234, 0x15BAC0] +SkipShops=[1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19, 20, 21, 22, 23] +MainGameShops=[0, 4, 17, 18] +CRC32=F0815EE7 + +[Ruby (E)] +Game=AXVE +Version=1 +Type=Ruby +CopyTMText=1 +CopyFrom=Ruby (U) +PokemonStats=0x1FEC30 +PokemonMovesets=0x207BE0 +EggMoves=0x2091F4 +PokemonTMHMCompat=0x1FD108 +PokemonEvolutions=0x203B80 +StarterPokemon=0x3F76E0 +StarterItems=0x821CA +TrainerData=0x1F0514 +TrainerClassNames=0x1F0220 +ItemData=0x3C5580 +MoveData=0x1FB144 +MoveDescriptions=0x3C09F4 +MoveNames=0x1F8338 +AbilityNames=0x1FA260 +TmMoves=0x37651C +PokemonFrontSprites=0x1E836C +PokemonNormalPalettes=0x1EA5CC +TradeTableOffset=0x215ADC +RunIndoorsTweakOffset=0xE5E20 +CatchingTutorialOpponentMonOffset=0x81B20 +CatchingTutorialPlayerMonOffset=0x10F64E +PCPotionOffset=0x40630C +InstantTextTweak=instant_text/ruby_11_instant_text +TypeEffectivenessOffset=0x1F9738 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x157683, 0x1576B1], Level=[0x1576B3]} // Lileep +StaticPokemon{}={Species=[0x1576D6, 0x157704], Level=[0x157706]} // Anorith +StaticPokemon{}={Species=[0x1A04C3, 0x15DE46, 0x15DE4D], Level=[0x15DE48]} // Groudon +StaticPokemon{}={Species=[0x15CBA9, 0x15CBB2], Level=[0x15CBB4]} // Regirock +StaticPokemon{}={Species=[0x15EFC1, 0x15EFCA], Level=[0x15EFCC]} // Regice +StaticPokemon{}={Species=[0x15F074, 0x15F07D], Level=[0x15F07F]} // Registeel +StaticPokemon{}={Species=[0x160BEE, 0x160C14], Level=[0x160C16]} // Latias (Southern Island) +StaticPokemon{}={Species=[0x15F339, 0x15F340], Level=[0x15F33B]} // Rayquaza +StaticPokemon{}={Species=[0x1A0602, 0x1A060B], Level=[0x1A060D]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x151908, 0x151911], Level=[0x151913]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15E923, 0x15E92A], Level=[0x15E925]} // Voltorb 1 +StaticPokemon{}={Species=[0x15E941, 0x15E948], Level=[0x15E943]} // Voltorb 2 +StaticPokemon{}={Species=[0x15E95F, 0x15E966], Level=[0x15E961]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A0520, 0x1A0527], Level=[0x1A0522]} // Electrode 1 +StaticPokemon{}={Species=[0x1A053E, 0x1A0545], Level=[0x1A0540]} // Electrode 2 +StaticPokemon{}={Species=[0x14E7BA]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AACF, 0x15AADF], Level=[0x15AAD1]} // Beldum +StaticPokemon{}={Species=[0x163DB9], Level=[0x163DBB]} // Castform +RoamingPokemon{}={Species=[0x1109A8, 0x134318], Level=[0x13427C, 0x13428C]} // Latios +StaticFirstBattleTweak=hardcoded_statics/rs_firstbattle +FindMapsWithMonFunctionStartOffset=0x110928 +CreateInitialRoamerMonFunctionStartOffset=0x134260 +ShopItemOffsets=[0x14BAF0, 0x14BED4, 0x152FBC, 0x152FD8, 0x153660, 0x153904, 0x1539A0, 0x153EF4, 0x1552F0, 0x15531C, 0x156448, 0x1573F8, 0x157C20, 0x157C48, 0x1580A0, 0x159F64, 0x159F98, 0x159FC8, 0x159FF0, 0x15A050, 0x15A074, 0x15A960, 0x15B254, 0x15BAE0] +CRC32=61641576 + +[Ruby (U/E) 1.2] +Game=AXVE +Version=2 +Type=Ruby +CopyTMText=1 +CopyStaticPokemon=1 +CopyFrom=Ruby (E) +InstantTextTweak=instant_text/ruby_12_instant_text +StaticFirstBattleTweak=hardcoded_statics/rs_firstbattle +CRC32=AEAC73E6 + +[Sapphire (U)] +Game=AXPE +Version=0 +Type=Sapp +CopyTMText=1 +CopyFrom=Ruby (U) +PokemonStats=0x1FEBA8 +PokemonMovesets=0x207B58 +EggMoves=0x20916C +PokemonTMHMCompat=0x1FD080 +PokemonEvolutions=0x203AF8 +StarterPokemon=0x3F771C +TrainerData=0x1F048C +TrainerClassNames=0x1F0198 +ItemData=0x3C55BC +MoveData=0x1FB0BC +MoveDescriptions=0x3C0A30 +MoveNames=0x1F82B0 +AbilityNames=0x1FA1D8 +TmMoves=0x376494 +PokemonFrontSprites=0x1E82E4 +PokemonNormalPalettes=0x1EA544 +TradeTableOffset=0x215A54 +RunIndoorsTweakOffset=0xE5E00 +PCPotionOffset=0x406348 +InstantTextTweak=instant_text/sapphire_10_instant_text +TypeEffectivenessOffset=0x1F96B0 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x1575F3, 0x157621], Level=[0x157623]} // Lileep +StaticPokemon{}={Species=[0x157646, 0x157674], Level=[0x157676]} // Anorith +StaticPokemon{}={Species=[0x1A0433, 0x15DDB6, 0x15DDBD], Level=[0x15DDB8]} // Kyogre +StaticPokemon{}={Species=[0x15CB19, 0x15CB22], Level=[0x15CB24]} // Regirock +StaticPokemon{}={Species=[0x15EF31, 0x15EF3A], Level=[0x15EF3C]} // Regice +StaticPokemon{}={Species=[0x15EFE4, 0x15EFED], Level=[0x15EFEF]} // Registeel +StaticPokemon{}={Species=[0x160B5E, 0x160B84], Level=[0x160B86]} // Latios (Southern Island) +StaticPokemon{}={Species=[0x15F2A9, 0x15F2B0], Level=[0x15F2AB]} // Rayquaza +StaticPokemon{}={Species=[0x1A0572, 0x1A057B], Level=[0x1A057D]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x15187C, 0x151885], Level=[0x151887]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15E893, 0x15E89A], Level=[0x15E895]} // Voltorb 1 +StaticPokemon{}={Species=[0x15E8B1, 0x15E8B8], Level=[0x15E8B3]} // Voltorb 2 +StaticPokemon{}={Species=[0x15E8CF, 0x15E8D6], Level=[0x15E8D1]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A0490, 0x1A0497], Level=[0x1A0492]} // Electrode 1 +StaticPokemon{}={Species=[0x1A04AE, 0x1A04B5], Level=[0x1A04B0]} // Electrode 2 +StaticPokemon{}={Species=[0x14E72E]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AA3F, 0x15AA4F], Level=[0x15AA41]} // Beldum +StaticPokemon{}={Species=[0x163D29], Level=[0x163D2B]} // Castform +RoamingPokemon{}={Species=[0x110984, 0x1342FC], Level=[0x13425A, 0x13426A]} // Latias +MainGameLegendaries=[382] +StaticFirstBattleTweak=hardcoded_statics/rs_firstbattle +ShopItemOffsets=[0x14BAD0, 0x14BEB4, 0x152F2C, 0x152F48, 0x1535D0, 0x153874, 0x153910, 0x153E64, 0x155260, 0x15528C, 0x1563B8, 0x157368, 0x157B90, 0x157BB8, 0x158010, 0x159ED4, 0x159F08, 0x159F38, 0x159F60, 0x159FC0, 0x159FE4, 0x15A8D0, 0x15B1C4, 0x15BA50] +CRC32=554DEDC4 + +[Sapphire (E)] +Game=AXPE +Version=1 +Type=Sapp +CopyTMText=1 +CopyFrom=Sapphire (U) +PokemonStats=0x1FEBC0 +PokemonMovesets=0x207B70 +EggMoves=0x209184 +PokemonTMHMCompat=0x1FD098 +PokemonEvolutions=0x203B10 +StarterPokemon=0x3F773C +StarterItems=0x821CA +TrainerData=0x1F04A4 +TrainerClassNames=0x1F01B0 +ItemData=0x3C55DC +MoveData=0x1FB0D4 +MoveDescriptions=0x3C0A50 +MoveNames=0x1F82C8 +AbilityNames=0x1FA1F0 +TmMoves=0x3764AC +PokemonFrontSprites=0x1E82FC +PokemonNormalPalettes=0x1EA55C +TradeTableOffset=0x215A6C +RunIndoorsTweakOffset=0xE5E20 +CatchingTutorialOpponentMonOffset=0x81B20 +CatchingTutorialPlayerMonOffset=0x10F64E +PCPotionOffset=0x406368 +InstantTextTweak=instant_text/sapphire_11_instant_text +TypeEffectivenessOffset=0x1F96C8 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x157613, 0x157641], Level=[0x157643]} // Lileep +StaticPokemon{}={Species=[0x157666, 0x157694], Level=[0x157696]} // Anorith +StaticPokemon{}={Species=[0x1A0453, 0x15DDD6, 0x15DDDD], Level=[0x15DDD8]} // Kyogre +StaticPokemon{}={Species=[0x15CB39, 0x15CB42], Level=[0x15CB44]} // Regirock +StaticPokemon{}={Species=[0x15EF51, 0x15EF5A], Level=[0x15EF5C]} // Regice +StaticPokemon{}={Species=[0x15F004, 0x15F00D], Level=[0x15F00F]} // Registeel +StaticPokemon{}={Species=[0x160B7E, 0x160BA4], Level=[0x160BA6]} // Latios (Southern Island) +StaticPokemon{}={Species=[0x15F2C9, 0x15F2D0], Level=[0x15F2CB]} // Rayquaza +StaticPokemon{}={Species=[0x1A0592, 0x1A059B], Level=[0x1A059D]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x15189C, 0x1518A5], Level=[0x1518A7]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15E8B3, 0x15E8BA], Level=[0x15E8B5]} // Voltorb 1 +StaticPokemon{}={Species=[0x15E8D1, 0x15E8D8], Level=[0x15E8D3]} // Voltorb 2 +StaticPokemon{}={Species=[0x15E8EF, 0x15E8F6], Level=[0x15E8F1]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A04B0, 0x1A04B7], Level=[0x1A04B2]} // Electrode 1 +StaticPokemon{}={Species=[0x1A04CE, 0x1A04D5], Level=[0x1A04D0]} // Electrode 2 +StaticPokemon{}={Species=[0x14E74E]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AA5F, 0x15AA6F], Level=[0x15AA61]} // Beldum +StaticPokemon{}={Species=[0x163D49], Level=[0x163D4B]} // Castform +RoamingPokemon{}={Species=[0x1109A4, 0x13431C], Level=[0x13427A, 0x13428A]} // Latias +StaticFirstBattleTweak=hardcoded_statics/rs_firstbattle +ShopItemOffsets=[0x14BAF0, 0x14BED4, 0x152F4C, 0x152F68, 0x1535F0, 0x153894, 0x153930, 0x153E84, 0x155280, 0x1552AC, 0x1563D8, 0x157388, 0x157BB0, 0x157BD8, 0x158030, 0x159EF4, 0x159F28, 0x159F58, 0x159F80, 0x159FE0, 0x15A004, 0x15A8F0, 0x15B1E4, 0x15BA70] +CRC32=BAFEDAE5 + +[Sapphire (U/E) 1.2] +Game=AXPE +Version=2 +Type=Sapp +CopyTMText=1 +CopyStaticPokemon=1 +CopyFrom=Sapphire (E) +InstantTextTweak=instant_text/sapphire_12_instant_text +StaticFirstBattleTweak=hardcoded_statics/rs_firstbattle +CRC32=9CC4410E + +[Emerald (U)] +Game=BPEE +Version=0 +Type=Em +TableFile=gba_english +FreeSpace=0xE40000 +PokemonCount=411 +PokemonNameLength=11 +PokemonMovesets=0x32937C +EggMoves=0x32ADD8 +PokemonTMHMCompat=0x31E898 +PokemonEvolutions=0x32531C +StarterPokemon=0x5B1DF8 +StarterItems=0xB117A +TrainerData=0x310030 +TrainerEntrySize=40 +TrainerCount=0x357 +TrainerClassNames=0x30FCD4 +TrainerClassCount=66 +TrainerClassNameLength=13 +TrainerNameLength=12 +DoublesTrainerClasses=[34, 46, 55, 56, 57] +EliteFourIndices=[261, 262, 263, 264, 335] +MossdeepStevenTeamOffset=0x5DD6D0 +ItemEntrySize=44 +ItemCount=376 +MoveCount=354 +MoveDescriptions=0x61C524 +MoveNameLength=13 +AbilityNameLength=13 +TmMoves=0x615B94 +TmMovesDuplicate=0x616040 +MoveTutorData=0x61500C +MoveTutorMoves=30 +ItemImages=0x614410 +TmPals=[0xDB5E94, 0xDB5DF4, 0xDB604C, 0xDB5EBC, 0xDB5FD4, 0xDB6024, 0xDB5F0C, 0xDB5FFC, 0xDB5F84, 0xDB5FFC, 0xDB5F34, 0xDB5E44, 0xDB5F0C, 0xDB5FAC, 0xDB5E6C, 0xDB5EE4, 0xDB5E1C, 0xDB5F5C] +IntroCryOffset=0x30B0C +IntroSpriteOffset=0x31924 +ItemBallPic=59 +TradeTableOffset=0x338ED0 +TradeTableSize=4 +TradesUnused=[] +RunIndoorsTweakOffset=0x11A1E8 +InstantTextTweak=instant_text/em_instant_text +CatchingTutorialOpponentMonOffset=0xB0870 +CatchingTutorialPlayerMonOffset=0x139472 +PCPotionOffset=0x5DFEFC +PickupTableStartLocator=0D000E0016000300560055 +PickupItemCount=29 +TypeEffectivenessOffset=0x31ACE8 +DeoxysStatPrefix=190A1E0A230A280A +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x211A1C, 0x211A41, 0x211A44, 0x211AC6, 0x211AD4], Level=[0x211A46]} // Lileep +StaticPokemon{}={Species=[0x211A2E, 0x211AE4, 0x211AE7, 0x211B69, 0x211B77], Level=[0x211AE9]} // Anorith +StaticPokemon{}={Species=[0x23B032, 0x23B040, 0x23B095, 0x1E5982, 0x1E5A02, 0x1E5ABE, 0x1E5B3E], Level=[0x23B042]} // Kyogre +StaticPokemon{}={Species=[0x23B103, 0x23B111, 0x23B166, 0x1E59C2, 0x1E5AFE], Level=[0x23B113]} // Groudon +StaticPokemon{}={Species=[0x22DA06, 0x22DA0F, 0x22DA55], Level=[0x22DA11]} // Regirock +StaticPokemon{}={Species=[0x238F5C, 0x238F65, 0x238FAB], Level=[0x238F67]} // Regice +StaticPokemon{}={Species=[0x23905E, 0x239067, 0x2390AD], Level=[0x239069]} // Registeel +StaticPokemon{}={Species=[0x239725, 0x23972E, 0x239774, 0x2397C3, 0x2397E1, 0x1E5C60, 0x1E5C7E, 0x1E5D14, 0x1E5D32], Level=[0x239730]} // Rayquaza +StaticPokemon{}={Species=[0x272384, 0x27238D], Level=[0x27238F]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x1F56D6, 0x1F56DF], Level=[0x1F56E1]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x2377B2, 0x2377B9], Level=[0x2377B4]} // Voltorb 1 +StaticPokemon{}={Species=[0x2377FF, 0x237806], Level=[0x237801]} // Voltorb 2 +StaticPokemon{}={Species=[0x23784C, 0x237853], Level=[0x23784E]} // Voltorb 3 +StaticPokemon{}={Species=[0x2339EE, 0x2339F5], Level=[0x2339F0]} // Electrode 1 +StaticPokemon{}={Species=[0x233A3B, 0x233A42], Level=[0x233A3D]} // Electrode 2 +StaticPokemon{}={Species=[0x242D1B, 0x242D29], Level=[0x242D2B]} // Sudowoodo in Battle Frontier +StaticPokemon{}={Species=[0x242A92, 0x242BA7], Level=[0x242BAC]} // Latios on Southern Island +StaticPokemon{}={Species=[0x242A9D, 0x242BBA], Level=[0x242BBF]} // Latias on Southern Island +StaticPokemon{}={Species=[0x267FE7, 0x267FF7, 0x268041, 0x26804C], Level=[0x267FFC]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x267E0D, 0x267E47, 0x267E9C, 0x267EA7], Level=[0x267E4C]} // Mew on Faraway Island +StaticPokemon{}={Species=[0x26919F, 0x2691CE, 0x26921D, 0x269228], Level=[0x2691D3]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x2692E7, 0x2692F2, 0x26933C, 0x269347], Level=[0x2692F7]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x1EA783]} // Wynaut Egg +StaticPokemon{}={Species=[0x222868, 0x22286B, 0x2228ED, 0x2228FE], Level=[0x22286D]} // Beldum +StaticPokemon{}={Species=[0x270058, 0x27005B, 0x2700E7], Level=[0x27005D]} // Castform +RoamingPokemon{}={Species=[0x161BB0], Level=[0x161BE2, 0x161BEE]} // Latios +RoamingPokemon{}={Species=[0x161BB8], Level=[0x161BE2, 0x161BEE]} // Latias +StaticEggPokemonOffsets=[22] +StaticFirstBattleTweak=hardcoded_statics/em_firstbattle +StaticFirstBattleSpeciesOffset=0xFE003C +StaticFirstBattleLevelOffset=0xFE0008 +StaticFirstBattleOffset=25 +CreateInitialRoamerMonFunctionStartOffset=0x161B94 +StaticSouthernIslandOffsets=[16, 17] +TMText[]=[3,15,0,1,0xA9,The TECHNICAL MACHINE I handed you contains [move].\p… … … … … …] +TMText[]=[4,14,0,1,0xB8,TATE: That TM04 contains... LIZA: [move]!\pTATE: It’s a move that’s perfect... LIZA: For any POKéMON!\p… … … … … …] +TMText[]=[5,0,29,12,0x0D,All my POKéMON does is [move]... No one dares to come near me...\pSigh... If you would, please take this TM away...] +TMText[]=[5,0,29,12,0x2F,TM05 contains [move].] +TMText[]=[8,3,3,1,0xAC,That TM08 contains [move].\p… … … … … …] +TMText[]=[9,0,19,32,0x0D,I like filling my mouth with seeds, then spitting them out fast!\pI like you, so you can have this!\pUse it on a POKéMON, and it will learn [move].\pWhat does that have to do with firing seeds? Well, nothing!] +TMText[]=[24,0,2,8,0x4C,WATTSON: Wahahahaha!\pI knew it, \v01\v05! I knew I’d made the right choice asking you!\pThis is my thanks - a TM containing [move]!\pGo on, you’ve earned it!] +TMText[]=[31,15,5,1,0x2F,TM31 contains [move]! It’s a move so horrible that I can’t describe it.] +TMText[]=[34,10,0,1,0xBB,That TM34 there contains [move]. You can count on it!\p… … … … … …] +TMText[]=[39,11,3,1,0x8F,That TM39 contains [move].\pIf you use a TM, it instantly teaches the move to a POKéMON.\pRemember, a TM can be used only once, so think before you use it.] +TMText[]=[40,12,1,1,0x97,TM40 contains [move].\p… … … … … …] +TMText[]=[41,9,2,2,0x2F,That’s, like, TM41, you know? Hey, it’s [move], you hearing me?\pHey, now, you listen here, like, I’m not laying a torment on you!] +TMText[]=[42,8,1,1,0x4FD,DAD: TM42 contains [move].\pIt might be able to turn a bad situation into an advantage.] +TMText[]=[47,24,10,1,0x19,STEVEN: Okay, thank you.\pYou went through all this trouble to deliver that. I need to thank you.\pLet me see... I’ll give you this TM.\pIt contains my favorite move, [move].] +TMText[]=[50,4,1,1,0xAA,That TM50 contains [move].] +MoveTutorText[]=[4,15,2,4,0x0D,Sigh…\pSOOTOPOLIS’s GYM LEADER is really lovably admirable.\pBut that also means I have many rivals for his attention.\pHe’s got appeal with a [move]. I couldn’t even catch his eye.\pPlease, let me teach your POKéMON the move [move]!] +MoveTutorText[]=[4,15,2,4,0x30,Okay, which POKéMON should I teach [move]?] +MoveTutorText[]=[15,0,6,15,0x0D,I can’t do this anymore!\pIt’s utterly hopeless!\pI’m a FIGHTING-type TRAINER, so I can’t win at the MOSSDEEP GYM no matter how hard I try!\pArgh! Punch! Punch! Punch! Punch! Punch! Punch!\pWhat, don’t look at me that way! I’m only hitting the ground!\pOr do you want me to teach your POKéMON [move]?] +MoveTutorText[]=[15,0,6,15,0x60,I want you to win at the MOSSDEEP GYM using that [move]!] +MoveTutorText[]=[12,7,0,5,0x0D,I don’t intend to be going nowhere fast in the sticks like this forever.\pYou watch me, I’ll get out to the city and become a huge hit.\pSeriously, I’m going to cause a huge explosion of popularity!\pIf you overheard that, I’ll happily teach [move] to your POKéMON!] +MoveTutorText[]=[12,7,0,5,0x30,Fine! [move] it is! Which POKéMON wants to learn it?] +MoveTutorText[]=[12,7,0,5,0x60,For a long time, I’ve taught POKéMON how to use [move], but I’ve yet to ignite my own explosion…\pMaybe it’s because deep down, I would rather stay here…] +MoveTutorText[]=[29,6,4,4,0x0D,There’s a move that is wickedly cool.\pIt’s called [move].\nWant me to teach it to a POKéMON?] +MoveTutorText[]=[8,5,0,5,0x0D,I want all sorts of things! But I used up my allowance…\pWouldn’t it be nice if there were a spell that made money appear when you waggle a finger?\pIf you want, I can teach your POKéMON the move [move].\pMoney won’t appear, but your POKéMON will do well in battle. Yes?] +MoveTutorText[]=[8,5,0,5,0x60,When a POKéMON uses [move], all sorts of nice things happen.] +MoveTutorText[]=[7,4,3,3,0x0D,Ah, young one!\pI am also a young one, but I mimic the styles and speech of the elderly folks of this town.\pWhat do you say, young one? Would you agree to it if I were to offer to teach the move [move]?] +MoveTutorText[]=[7,4,3,3,0x60,[move] is a move of great depth.\pCould you execute it to perfection as well as me…?] +MoveTutorText[]=[7,4,3,3,0x56,Oh, boo! I wanted to teach [move] to your POKéMON!] +MoveTutorText[]=[16,0,2,10,0x0D,Did you know that you can go from here a long way in that direction without changing direction?\pI might even be able to roll that way.\pDo you think your POKéMON will want to roll, too?\pI can teach one the move [move] if you’d like.] +MoveTutorText[]=[24,12,5,2,0x0D,Humph! My wife relies on HIDDEN POWER to stay awake.\pShe should just take a nap like I do.\pI can teach your POKéMON how to [move]. Interested?] +MoveTutorText[]=[24,12,5,2,0x60,I’ve never once gotten my wife’s coin trick right.\pI would be happy if I got it right even as I teach [move]…] +MoveTutorText[]=[14,13,21,4,0x0D,When I see the wide world from up here on the roof…\pI think about how nice it would be if there were more than just one me so I could enjoy all sorts of lives.\pOf course it’s not possible. Giggle…\pI know! Would you be interested in having a POKéMON learn [move]?] +MoveTutorText[]=[14,13,21,4,0x30,Giggle… Which POKéMON do you want me to teach [move]?] +MoveTutorText[]=[14,13,21,4,0x56,Oh, no?\pA POKéMON can do well in a battle using it, you know.] +MoveTutorText[]=[25,9,6,9,0x0D,Heh! My POKéMON totally rules! It’s cooler than any POKéMON!\pI was lipping off with a swagger in my step like that when the CHAIRMAN chewed me out.\pThat took the swagger out of my step.\pIf you’d like, I’ll teach the move [move] to a POKéMON of yours.] +MoveTutorText[]=[25,9,6,9,0x30,All right, which POKéMON wants to learn how to [move]?] +MoveTutorText[]=[25,9,6,9,0x60,I’ll just praise my POKéMON from now on without the [move].] +SpecialMusicStatics=[151,249,250,382,383,384,386] +NewIndexToMusicTweak=musicfix/em_musicfix +NewIndexToMusicPoolOffset=0xFE014C +ShopItemOffsets=[0x1DCDD4, 0x1DD1F0, 0x1FC260, 0x1FC27C, 0x1FE4F0, 0x1FF9E8, 0x1FFCD8, 0x2025A0, 0x207D8C, 0x207DB8, 0x20DC60, 0x211100, 0x214F30, 0x214F58, 0x217680, 0x21FB60, 0x21FB94, 0x21FC7C, 0x21FCA4, 0x21FE20, 0x21FE44, 0x2223E0, 0x2267AC, 0x229624, 0x267AE4, 0x2683E8, 0x268414] +SkipShops=[1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19, 20, 21, 22, 23, 24, 25, 26] +MainGameShops=[0, 4, 17, 18] +CRC32=1F1C08FB + +[Fire Red (U) 1.0] +Game=BPRE +Version=0 +Type=FRLG +TableFile=gba_english +FreeSpace=0x800000 +PokemonCount=411 +PokemonNameLength=11 +PokemonMovesets=0x25D7B4 +EggMoves=0x25EF0C +PokemonTMHMCompat=0x252BC8 +PokemonEvolutions=0x259754 +BattleTrappersBanned=[55,56,57,58,59] +StarterPokemon=0x169BB5 +TrainerData=0x23EAC8 +TrainerEntrySize=40 +TrainerCount=0x2E7 +TrainerClassNames=0x23E558 +TrainerClassCount=107 +TrainerClassNameLength=13 +TrainerNameLength=12 +DoublesTrainerClasses=[26, 40, 52, 53, 54, 92, 93, 94, 95, 96] +EliteFourIndices=[410, 411, 412, 413, 438, 439, 440] +ItemEntrySize=44 +ItemCount=374 +MoveCount=354 +MoveDescriptions=0x4886E8 +MoveNameLength=13 +AbilityNameLength=13 +TmMoves=0x45A5A4 +TmMovesDuplicate=0x45A80C +MoveTutorData=0x459B60 +MoveTutorMoves=15 +ItemImages=0x3D4294 +TmPals=[0xE91E64, 0xE91DC4, 0xE9201C, 0xE91E8C, 0xE91FA4, 0xE91FF4, 0xE91EDC, 0xE91FCC, 0xE91F54, 0xE91FCC, 0xE91F04, 0xE91E14, 0xE91EDC, 0xE91F7C, 0xE91E3C, 0xE91EB4, 0xE91DEC, 0xE91F2C] +IntroCryOffset=0x12FB38 +IntroSpriteOffset=0x130FA0 +IntroOtherOffset=0x130F4C +ItemBallPic=92 +TradeTableOffset=0x26CF8C +TradeTableSize=9 +TradesUnused=[] +RunIndoorsTweakOffset=0xBD494 +InstantTextTweak=instant_text/fr_10_instant_text +CatchingTutorialOpponentMonOffset=0x7F88C +PCPotionOffset=0x402220 +PickupTableStartLocator=8B000F00850019008600230087002D +PickupItemCount=16 +TypeEffectivenessOffset=0x24F050 +DeoxysStatPrefix=7F002301FFFF +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C472, 0x16C475, 0x16C4B5, 0x16C4E9], Level=[0x16C477]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16EC0C, 0x16EC13], Level=[0x16EC86]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16EC52, 0x16EC59], Level=[0x16EC86]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x163840, 0x163847], Level=[0x163842]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16389E, 0x1638A5], Level=[0x1638A0]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x1637CC, 0x1637D3, 0x163827], Level=[0x1637CE]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x1631C0, 0x1631C7, 0x16321B, 0x17009D, 0x1700A9], Level=[0x1631C2]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163B47, 0x163B4E, 0x163BA2], Level=[0x163B49]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x16250A, 0x16251E, 0x162564], Level=[0x162520]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x168049, 0x168050, 0x160CA2, 0x160CA8], Level=[0x16804B]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x168156, 0x16815D], Level=[0x168158]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163CBC, 0x163CC2], Level=[0x163CC4]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x1652E6, 0x1652F6, 0x165340, 0x16534B], Level=[0x1652FB]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x16503C, 0x16506B, 0x1650BA, 0x1650C5], Level=[0x165070]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x16518A, 0x165195, 0x1651DF, 0x1651EA], Level=[0x16519A]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E7E5, 0x16E7E9, 0x16E7F4, 0x16E6E6], Level=[0x16E7F6]} // Old Amber +StaticPokemon{}={Species=[0x16E75B, 0x16E75F, 0x16E76A, 0x16E66A], Level=[0x16E76C]} // Helix Fossil +StaticPokemon{}={Species=[0x16E7A0, 0x16E7A4, 0x16E7AF, 0x16E6A8], Level=[0x16E7B1]} // Dome Fossil +StaticPokemon{}={Species=[0x161ADE, 0x161AE1, 0x161B20, 0x161B53], Level=[0x161AE3]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F7C3, 0x16F7C6, 0x16F885], Level=[0x16F7C8]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CC18, 0x16CC94], Level=[0x16CCD7]} // Abra +StaticPokemon{}={Species=[0x16CC28, 0x16CC9F], Level=[0x16CCEC]} // Clefairy +StaticPokemon{}={Species=[0x16CC48, 0x16CCB5], Level=[0x16CD16]} // Scyther +StaticPokemon{}={Species=[0x16CC38, 0x16CCAA], Level=[0x16CD01]} // Dratini +StaticPokemon{}={Species=[0x16CC58, 0x16CCC0], Level=[0x16CD2B]} // Porygon +RoamingPokemon{}={Species=[0xA80224, 0x4642FC], Level=[0x141CC8, 0x141CDC]} // Raikou +RoamingPokemon{}={Species=[0xA8021C, 0x4642F4], Level=[0x141CC8, 0x141CDC]} // Entei +RoamingPokemon{}={Species=[0xA80220, 0x4642F8], Level=[0x141CC8, 0x141CDC]} // Suicune +RoamingPokemonTweak=hardcoded_statics/roamers/fr_roamers_10 +FossilLevelOffsets=[0x16E7F6, 0x16E76C, 0x16E7B1] +GhostMarowakTweak=hardcoded_statics/fr_marowak_10 +GhostMarowakSpeciesOffsets=[0xA80024, 0x1634D2, 0x1634FD] +GhostMarowakLevelOffsets=[0xA80012, 0x1634D4] +GhostMarowakGenderOffset=0xA80004 +GhostMarowakOffset=25 +TMText[]=[3,7,5,3,0x2A,TM03 teaches [move].\pUse it on a worthy POKéMON!] +TMText[]=[4,14,3,7,0x2A,TM04 is [move].] +TMText[]=[6,11,3,7,0x91,Sealed within that TM06 lies [move]!\pIt is a secret technique dating back some four hundred years.] +TMText[]=[16,10,5,2,0x316,TM16 contains [move].] +TMText[]=[19,10,16,7,0x9E,TM19 contains [move].\pWouldn’t you agree that it’s a wonderful move?] +TMText[]=[20,10,5,2,0x322,TM20 contains [move].] +TMText[]=[26,5,1,8,0x9E,TM26 contains [move].\pIt is a powerful technique.\pI made it when I ran the GYM here, far too long ago…] +TMText[]=[27,23,1,1,0x50,TM27 is a move called [move]…\pIf you treat your POKéMON good, it will return your love by working its hardest in battle.] +TMText[]=[28,7,1,1,0x0D,Those miserable ROCKETS!\pLook what they’ve done to my house!\pThey stole a TM for teaching POKéMON how to [move]!\pThat cost me a bundle, it did!] +TMText[]=[28,7,1,1,0x17,I figure what’s lost is lost.\pI decided to catch a POKéMON that could [move] without a TM.] +TMText[]=[29,14,8,1,0x41,You already know, don’t you? TM29 is [move].] +TMText[]=[33,10,5,2,0x32E,TM33 contains [move].] +TMText[]=[34,9,6,1,0x9E,TM34 contains [move]!\pTeach it to your favorite POKéMON!] +TMText[]=[38,12,0,8,0x2A,TM38 contains [move].\nTeach it to strong POKéMON.] +TMText[]=[38,12,0,8,0x99,[move] is the ultimate\ntechnique.\pDon't waste it on weak POKéMON.] +TMText[]=[39,6,2,1,0x99,A TM, Technical Machine, contains a technique for POKéMON.\pUsing a TM teaches the move it contains to a POKéMON.\pA TM is good for only one use.\pSo, when you use one, pick the POKéMON carefully.\pAnyways… TM39 contains [move].] +MoveTutorText[]=[0,3,22,5,0x0D,A hit of roaring ferocity!\pPacked with destructive power!\pWhen the chips are down, [move] is the ultimate attack! You agree, yes?\pNow! Let me teach it to your POKéMON!] +MoveTutorText[]=[0,3,22,5,0x60,Now, we are comrades in the way of [move]!\pYou should go before you’re seen by the misguided fool who trains only silly moves over there.] +MoveTutorText[]=[0,3,22,5,0x56,You’ll be back when you understand the worth of [move].] +MoveTutorText[]=[1,3,17,1,0x0D,Not many people come out here.\pIf I train here, I’m convinced that I’ll get stronger and stronger.\pYep, stronger and stronger…\pHow would you like to learn a strong move? It’s [move].] +MoveTutorText[]=[2,3,22,6,0x0D,A hit of brutal ferocity!\pPacked with destructive power!\pWhen you get right down to it, [move] is the ultimate attack! Don’t you agree?\pOkay! I’ll teach it to your POKéMON!] +MoveTutorText[]=[2,3,22,6,0x60,Now, we are soul mates in the way of [move]!\pYou should run before you’re seen by the deluded nitwit who trains only simple moves over there.] +MoveTutorText[]=[2,3,22,6,0x56,You’ll come crawling back when you realize the value of [move].] +MoveTutorText[]=[3,35,3,1,0x0D,Ready? Boing!\pWe’re having a wrestling match to see who wimps out first.\pIf you were to join us, you’d be squashed like a bug, though…\pHow about I teach [move] to a POKéMON of yours instead?] +MoveTutorText[]=[3,35,3,1,0x30,Which POKéMON wants to learn how to [move]?] +MoveTutorText[]=[4,1,40,13,0x0D,You should be proud of yourself, having battled your way through VICTORY ROAD so courageously.\pIn recognition of your feat, I’ll teach you [move].\pWould you like me to teach that technique?] +MoveTutorText[]=[4,1,40,13,0x30,Which POKéMON should I teach [move]?] +MoveTutorText[]=[5,10,2,1,0x0D,Oh, hi! I finally finished POKéMON.\pNot done yet? How about I teach you a good move?\pThe move I have in mind is [move].] +MoveTutorText[]=[5,10,2,1,0x30,Which POKéMON should I teach [move] to?] +MoveTutorText[]=[5,10,2,1,0x60,Are you using that [move] move I taught your POKéMON?] +MoveTutorText[]=[6,6,0,6,0x0D,The secrets of space… The mysteries of earth…\pThere are so many things about which we know so little.\pBut that should spur us to study harder, not toss in the towel.\pThe only thing you should toss…\pWell, how about [move]? Should I teach that to a POKéMON?] +MoveTutorText[]=[6,6,0,6,0x30,Which POKéMON wants to learn [move]?] +MoveTutorText[]=[7,14,1,4,0x0B,Oh wow! A POKé DOLL!\pFor me? Thank you!\pYou know what? I can teach the move [move].] +MoveTutorText[]=[7,14,1,4,0x2E,I really love [move]! Who’s going to learn it?] +MoveTutorText[]=[7,14,1,4,0x5B,Don’t you like [move]?] +MoveTutorText[]=[8,12,3,1,0x0D,Tch-tch-tch! I’ll teach you a nifty move.\pTeach it to a POKéMON, and watch the fun unfold!\pIt’s a move called [move]. Does it strike your fancy?] +MoveTutorText[]=[8,12,3,1,0x60,Tch-tch-tch! That’s the sound of a metronome.\pIt inspired me to start teaching [move] to interested trainers.] +MoveTutorText[]=[9,3,6,7,0x0D,Hello, there!\pI’ve seen you about, but I never had a chance to chat.\pIt must be good luck that brought us together finally.\pI’d like to celebrate by teaching you the move [move].] +MoveTutorText[]=[9,3,6,7,0x30,So, who’s the POKéMON that gets the chance to learn [move]?] +MoveTutorText[]=[10,3,1,1,0x0D,Yawn! I must have dozed off in the sun.\pI had this weird dream about a DROWZEE eating my dream.\pAnd… I learned how to teach [move]…\pOogh, this is too spooky!\pLet me teach it to a POKéMON so I can forget about it!] +MoveTutorText[]=[10,3,1,1,0x30,Which POKéMON wants to learn [move]?] +MoveTutorText[]=[11,1,48,5,0x0D,Eeek! No! Stop! Help!\pOh, you’re not with TEAM ROCKET. I’m sorry, I thought…\pWill you forgive me if I teach you the [move] technique?] +MoveTutorText[]=[11,1,48,5,0x30,Which POKéMON should I teach [move]?] +MoveTutorText[]=[11,1,48,5,0x60,[move] is a useful move, but it might not work on some POKéMON.] +MoveTutorText[]=[11,1,48,5,0x56,Oh… But [move] is convenient…] +MoveTutorText[]=[12,1,97,1,0x0D,Can you imagine? If this volcano were to erupt?\pThe explosion would be the end of us. How terrifying is that?\pWhile we’re terrified, would you like me to teach [move]?] +MoveTutorText[]=[12,1,97,1,0x30,You’re terribly brave!\pWhich POKéMON should I teach [move]?] +MoveTutorText[]=[12,1,97,1,0x60,Using [move] while on this volcano…\pWhat a terrifying thrill!] +MoveTutorText[]=[13,1,82,9,0x0D,When you’re up on a rocky mountain like this, rockslides are a threat.\pCan you imagine? Boulders tumbling down on you?\pThat’d be, like, waaaaaaaaaaah! Total terror!\pYou don’t seem to be scared. Want to try using [move]?] +MoveTutorText[]=[13,1,82,9,0x30,Which POKéMON should I teach [move]?] +MoveTutorText[]=[14,3,7,15,0x0D,Aww, I wish I was a KANGASKHAN baby.\pI’d love to be a substitute for the baby…\pAnd snuggle in the mother KANGASKHAN’s belly pouch.\pBut only POKéMON can do that…\pOn an unrelated note, want me to teach [move] to one of your POKéMON?] +MoveTutorText[]=[14,3,7,15,0x30,Which POKéMON wants to learn [move]?] +MoveTutorText[]=[14,3,7,15,0x56,Oh, really? [move] seems so fun…] +SpecialMusicStatics=[144,145,146,150,249,250,386] +NewIndexToMusicTweak=musicfix/fr_musicfix_10 +NewIndexToMusicPoolOffset=0xA80140 +ShopItemOffsets=[0x1649B8, 0x1676E4, 0x1676FC, 0x167718, 0x167738, 0x16A298, 0x16A708, 0x16ACD8, 0x16B390, 0x16B68C, 0x16BB38, 0x16BB74, 0x16BC30, 0x16BC84, 0x16BCBC, 0x16D518, 0x16EA48, 0x16EAF4, 0x16EFDC, 0x170B58, 0x1718B4, 0x171CD4, 0x171E8C] +SkipShops=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 15, 16, 17, 18, 19, 20, 21, 22] +MainGameShops=[12, 13, 14] +CRC32=DD88761C + +[Leaf Green (U) 1.0] +Game=BPGE +Version=0 +Type=FRLG +CopyTMText=1 +CopyFrom=Fire Red (U) 1.0 +PokemonMovesets=0x25D794 +EggMoves=0x25EEEC +PokemonTMHMCompat=0x252BA4 +PokemonEvolutions=0x259734 +StarterPokemon=0x169B91 +TrainerData=0x23EAA4 +TrainerClassNames=0x23E534 +MoveDescriptions=0x487FC4 +TmMoves=0x459FC4 +TmMovesDuplicate=0x45A22C +MoveTutorData=0x459580 +ItemImages=0x3D40D0 +TmPals=[0xE91EE4, 0xE91E44, 0xE9209C, 0xE91F0C, 0xE92024, 0xE92074, 0xE91F5C, 0xE9204C, 0xE91FD4, 0xE9204C, 0xE91F84, 0xE91E94, 0xE91F5C, 0xE91FFC, 0xE91EBC, 0xE91F34, 0xE91E6C, 0xE91FAC] +IntroCryOffset=0x12FB10 +IntroSpriteOffset=0x130F78 +IntroOtherOffset=0x130F24 +TradeTableOffset=0x26CF6C +RunIndoorsTweakOffset=0xBD468 +InstantTextTweak=instant_text/lg_10_instant_text +CatchingTutorialOpponentMonOffset=0x7F860 +PCPotionOffset=0x40205C +TypeEffectivenessOffset=0x24F02C +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C44E, 0x16C451, 0x16C491, 0x16C4C5], Level=[0x16C453]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16EBE8, 0x16EBEF], Level=[0x16EC62]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16EC2E, 0x16EC35], Level=[0x16EC62]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x16381C, 0x163823], Level=[0x16381E]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16387A, 0x163881], Level=[0x16387C]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x1637A8, 0x1637AF, 0x163803], Level=[0x1637AA]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x16319C, 0x1631A3, 0x1631F7, 0x170079, 0x170085], Level=[0x16319E]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163B23, 0x163B2A, 0x163B7E], Level=[0x163B25]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x1624E6, 0x1624FA, 0x162540], Level=[0x1624FC]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x168025, 0x16802C, 0x160C7E, 0x160C84], Level=[0x168027]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x168132, 0x168139], Level=[0x168134]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163C98, 0x163C9E], Level=[0x163CA0]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x1652C2, 0x1652D2, 0x16531C, 0x165327], Level=[0x1652D7]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x165018, 0x165047, 0x165096, 0x1650A1], Level=[0x16504C]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x165166, 0x165171, 0x1651BB, 0x1651C6], Level=[0x165176]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E7C1, 0x16E7C5, 0x16E7D0, 0x16E6C2], Level=[0x16E7D2]} // Old Amber +StaticPokemon{}={Species=[0x16E737, 0x16E73B, 0x16E746, 0x16E646], Level=[0x16E748]} // Helix Fossil +StaticPokemon{}={Species=[0x16E77C, 0x16E780, 0x16E78B, 0x16E684], Level=[0x16E78D]} // Dome Fossil +StaticPokemon{}={Species=[0x161ABA, 0x161ABD, 0x161AFC, 0x161B2F], Level=[0x161ABF]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F79F, 0x16F7A2, 0x16F861], Level=[0x16F7A4]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CBF4, 0x16CC70], Level=[0x16CCB3]} // Abra +StaticPokemon{}={Species=[0x16CC04, 0x16CC7B], Level=[0x16CCC8]} // Clefairy +StaticPokemon{}={Species=[0x16CC14, 0x16CCA7], Level=[0x16CD1C]} // Pinsir +StaticPokemon{}={Species=[0x16CC24, 0x16CC86], Level=[0x16CCDD]} // Dratini +StaticPokemon{}={Species=[0x16CC34, 0x16CC9C], Level=[0x16CD07]} // Porygon +RoamingPokemon{}={Species=[0xA80224, 0x463D1C], Level=[0x141CA0, 0x141CB4]} // Raikou +RoamingPokemon{}={Species=[0xA8021C, 0x463D14], Level=[0x141CA0, 0x141CB4]} // Entei +RoamingPokemon{}={Species=[0xA80220, 0x463D18], Level=[0x141CA0, 0x141CB4]} // Suicune +RoamingPokemonTweak=hardcoded_statics/roamers/lg_roamers_10 +FossilLevelOffsets=[0x16E7D2, 0x16E748, 0x16E78D] +GhostMarowakTweak=hardcoded_statics/lg_marowak_10 +GhostMarowakSpeciesOffsets=[0xA80024, 0x1634AE, 0x1634D9] +GhostMarowakLevelOffsets=[0xA80012, 0x1634B0] +GhostMarowakGenderOffset=0xA80004 +SpecialMusicStatics=[144,145,146,150,249,250,386] +NewIndexToMusicTweak=musicfix/lg_musicfix_10 +NewIndexToMusicPoolOffset=0xA80140 +ShopItemOffsets=[0x164994, 0x1676C0, 0x1676D8, 0x1676F4, 0x167714, 0x16A274, 0x16A6E4, 0x16ACB4, 0x16B36C, 0x16B668, 0x16BB14, 0x16BB50, 0x16BC0C, 0x16BC60, 0x16BC98, 0x16D4F4, 0x16EA24, 0x16EAD0, 0x16EFB8, 0x170B34, 0x171890, 0x171CB0, 0x171E68] +CRC32=D69C96CC + +[Fire Red (U) 1.1] +Game=BPRE +Version=1 +Type=FRLG +CopyTMText=1 +CopyFrom=Fire Red (U) 1.0 +PokemonMovesets=0x25D824 +EggMoves=0x25EF7C +PokemonTMHMCompat=0x252C38 +PokemonEvolutions=0x2597C4 +StarterPokemon=0x169C2D +TrainerData=0x23EB38 +TrainerClassNames=0x23E5C8 +MoveDescriptions=0x488748 +TmMoves=0x45A604 +TmMovesDuplicate=0x45A86C +MoveTutorData=0x459BC0 +ItemImages=0x3D4304 +TmPals=[0xE91E64, 0xE91DC4, 0xE9201C, 0xE91E8C, 0xE91FA4, 0xE91FF4, 0xE91EDC, 0xE91FCC, 0xE91F54, 0xE91FCC, 0xE91F04, 0xE91E14, 0xE91EDC, 0xE91F7C, 0xE91E3C, 0xE91EB4, 0xE91DEC, 0xE91F2C] +IntroCryOffset=0x12FBB0 +IntroSpriteOffset=0x131018 +IntroOtherOffset=0x130FC4 +TradeTableOffset=0x26CFFC +RunIndoorsTweakOffset=0xBD4A8 +TextSpeedValuesOffset=0x41F498 +InstantTextTweak=instant_text/fr_11_instant_text +CatchingTutorialOpponentMonOffset=0x7F8A0 +PCPotionOffset=0x402290 +TypeEffectivenessOffset=0x24F0C0 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C4EA, 0x16C4ED, 0x16C52D, 0x16C561], Level=[0x16C4EF]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16EC84, 0x16EC8B], Level=[0x16ECFE]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16ECCA, 0x16ECD1], Level=[0x16ECFE]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x1638B8, 0x1638BF], Level=[0x1638BA]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x163916, 0x16391D], Level=[0x163918]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x163844, 0x16384B, 0x16389F], Level=[0x163846]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x163238, 0x16323F, 0x163293, 0x170115, 0x170121], Level=[0x16323A]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163BBF, 0x163BC6, 0x163C1A], Level=[0x163BC1]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x162582, 0x162596, 0x1625DC], Level=[0x162598]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x1680C1, 0x1680C8, 0x160D1A, 0x160D20], Level=[0x1680C3]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x1681CE, 0x1681D5], Level=[0x1681D0]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163D34, 0x163D3A], Level=[0x163D3C]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x16535E, 0x16536E, 0x1653B8, 0x1653C3], Level=[0x165373]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x1650B4, 0x1650E3, 0x165132, 0x16513D], Level=[0x1650E8]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x165202, 0x16520D, 0x165257, 0x165262], Level=[0x165212]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E85D, 0x16E861, 0x16E86C, 0x16E75E], Level=[0x16E86E]} // Old Amber +StaticPokemon{}={Species=[0x16E7D3, 0x16E7D7, 0x16E7E2, 0x16E6E2], Level=[0x16E7E4]} // Helix Fossil +StaticPokemon{}={Species=[0x16E818, 0x16E81C, 0x16E827, 0x16E720], Level=[0x16E829]} // Dome Fossil +StaticPokemon{}={Species=[0x161B56, 0x161B59, 0x161B98, 0x161BCB], Level=[0x161B5B]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F83B, 0x16F83E, 0x16F8FD], Level=[0x16F840]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CC90, 0x16CD0C], Level=[0x16CD4F]} // Abra +StaticPokemon{}={Species=[0x16CCA0, 0x16CD17], Level=[0x16CD64]} // Clefairy +StaticPokemon{}={Species=[0x16CCC0, 0x16CD2D], Level=[0x16CD8E]} // Scyther +StaticPokemon{}={Species=[0x16CCB0, 0x16CD22], Level=[0x16CD79]} // Dratini +StaticPokemon{}={Species=[0x16CCD0, 0x16CD38], Level=[0x16CDA3]} // Porygon +RoamingPokemon{}={Species=[0xA80224, 0x46435C], Level=[0x141D40, 0x141D54]} // Raikou +RoamingPokemon{}={Species=[0xA8021C, 0x464354], Level=[0x141D40, 0x141D54]} // Entei +RoamingPokemon{}={Species=[0xA80220, 0x464358], Level=[0x141D40, 0x141D54]} // Suicune +RoamingPokemonTweak=hardcoded_statics/roamers/fr_roamers_11 +FossilLevelOffsets=[0x16E86E, 0x16E7E4, 0x16E829] +GhostMarowakTweak=hardcoded_statics/fr_marowak_11 +GhostMarowakSpeciesOffsets=[0xA80024, 0x16354A, 0x163575] +GhostMarowakLevelOffsets=[0xA80012, 0x16354C] +GhostMarowakGenderOffset=0xA80004 +SpecialMusicStatics=[144,145,146,150,249,250,386] +NewIndexToMusicTweak=musicfix/fr_musicfix_11 +NewIndexToMusicPoolOffset=0xA80140 +ShopItemOffsets=[0x164A30, 0x16775C, 0x167774, 0x167790, 0x1677B0, 0x16A310, 0x16A780, 0x16AD50, 0x16B408, 0x16B704, 0x16BBB0, 0x16BBEC, 0x16BCA8, 0x16BCFC, 0x16BD34, 0x16D590, 0x16EAC0, 0x16EB6C, 0x16F054, 0x170BD0, 0x17192C, 0x171D4C, 0x171F04] +CRC32=84EE4776 + +[Leaf Green (U) 1.1] +Game=BPGE +Version=1 +Type=FRLG +CopyTMText=1 +CopyFrom=Leaf Green (U) 1.0 +PokemonMovesets=0x25D804 +EggMoves=0x25EF5C +PokemonTMHMCompat=0x252C14 +PokemonEvolutions=0x2597A4 +StarterPokemon=0x169C09 +TrainerData=0x23EB14 +TrainerClassNames=0x23E5A4 +MoveDescriptions=0x488034 +TmMoves=0x45A034 +TmMovesDuplicate=0x45A29C +MoveTutorData=0x4595F0 +ItemImages=0x3D4140 +TmPals=[0xE91EE4, 0xE91E44, 0xE9209C, 0xE91F0C, 0xE92024, 0xE92074, 0xE91F5C, 0xE9204C, 0xE91FD4, 0xE9204C, 0xE91F84, 0xE91E94, 0xE91F5C, 0xE91FFC, 0xE91EBC, 0xE91F34, 0xE91E6C, 0xE91FAC] +IntroCryOffset=0x12FB88 +IntroSpriteOffset=0x130FF0 +IntroOtherOffset=0x130F9C +TradeTableOffset=0x26CFDC +RunIndoorsTweakOffset=0xBD47C +TextSpeedValuesOffset=0x41F2D4 +InstantTextTweak=instant_text/lg_11_instant_text +CatchingTutorialOpponentMonOffset=0x7F874 +PCPotionOffset=0x4020CC +TypeEffectivenessOffset=0x24F09C +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C4C6, 0x16C4C9, 0x16C509, 0x16C53D], Level=[0x16C4CB]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16EC60, 0x16EC67], Level=[0x16ECDA]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16ECA6, 0x16ECAD], Level=[0x16ECDA]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x163894, 0x16389B], Level=[0x163896]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x1638F2, 0x1638F9], Level=[0x1638F4]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x163820, 0x163827, 0x16387B], Level=[0x163822]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x163214, 0x16321B, 0x16326F, 0x1700F1, 0x1700FD], Level=[0x163216]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163B9B, 0x163BA2, 0x163BF6], Level=[0x163B9D]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x16255E, 0x162572, 0x1625B8], Level=[0x162574]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x16809D, 0x1680A4, 0x160CF6, 0x160CFC], Level=[0x16809F]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x1681AA, 0x1681B1], Level=[0x1681AC]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163D10, 0x163D16], Level=[0x163D18]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x16533A, 0x16534A, 0x165394, 0x16539F], Level=[0x16534F]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x165090, 0x1650BF, 0x16510E, 0x165119], Level=[0x1650C4]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x1651DE, 0x1651E9, 0x165233, 0x16523E], Level=[0x1651EE]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E839, 0x16E83D, 0x16E848, 0x16E73A], Level=[0x16E84A]} // Old Amber +StaticPokemon{}={Species=[0x16E7AF, 0x16E7B3, 0x16E7BE, 0x16E6BE], Level=[0x16E7C0]} // Helix Fossil +StaticPokemon{}={Species=[0x16E7F4, 0x16E7F8, 0x16E803, 0x16E6FC], Level=[0x16E805]} // Dome Fossil +StaticPokemon{}={Species=[0x161B32, 0x161B35, 0x161B74, 0x161BA7], Level=[0x161B37]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F817, 0x16F81A, 0x16F8D9], Level=[0x16F81C]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CC6C, 0x16CCE8], Level=[0x16CD2B]} // Abra +StaticPokemon{}={Species=[0x16CC7C, 0x16CCF3], Level=[0x16CD40]} // Clefairy +StaticPokemon{}={Species=[0x16CC8C, 0x16CD1F], Level=[0x16CD94]} // Pinsir +StaticPokemon{}={Species=[0x16CC9C, 0x16CCFE], Level=[0x16CD55]} // Dratini +StaticPokemon{}={Species=[0x16CCAC, 0x16CD14], Level=[0x16CD7F]} // Porygon +RoamingPokemon{}={Species=[0xA80224, 0x463D8C], Level=[0x141D18, 0x141D2C]} // Raikou +RoamingPokemon{}={Species=[0xA8021C, 0x463D84], Level=[0x141D18, 0x141D2C]} // Entei +RoamingPokemon{}={Species=[0xA80220, 0x463D88], Level=[0x141D18, 0x141D2C]} // Suicune +RoamingPokemonTweak=hardcoded_statics/roamers/lg_roamers_11 +FossilLevelOffsets=[0x16E84A, 0x16E7C0, 0x16E805] +GhostMarowakTweak=hardcoded_statics/lg_marowak_11 +GhostMarowakSpeciesOffsets=[0xA80024, 0x163526, 0x163551] +GhostMarowakLevelOffsets=[0xA80012, 0x163528] +GhostMarowakGenderOffset=0xA80004 +SpecialMusicStatics=[144,145,146,150,249,250,386] +NewIndexToMusicTweak=musicfix/lg_musicfix_11 +NewIndexToMusicPoolOffset=0xA80140 +ShopItemOffsets=[0x164A0C, 0x167738, 0x167750, 0x16776C, 0x16778C, 0x16A2EC, 0x16A75C, 0x16AD2C, 0x16B3E4, 0x16B6E0, 0x16BB8C, 0x16BBC8, 0x16BC84, 0x16BCD8, 0x16BD10, 0x16D56C, 0x16EA9C, 0x16EB48, 0x16F030, 0x170BAC, 0x171908, 0x171D28, 0x171EE0] +CRC32=DAFFECEC + +[Ruby (F)] +Game=AXVF +Version=0 +Type=Ruby +CopyFrom=Ruby (U) +PokemonStats=0x207064 +PokemonMovesets=0x210014 +EggMoves=0x211628 +PokemonTMHMCompat=0x20553C +PokemonEvolutions=0x20BFB4 +StarterPokemon=0x3FF3F4 +StarterItems=0x826C6 +TrainerData=0x1F8904 +TrainerClassNames=0x1F8610 +ItemData=0x3CCFC4 +MoveData=0x203578 +MoveDescriptions=0x3C8434 +MoveNames=0x200728 +AbilityNames=0x202694 +TmMoves=0x37D168 +PokemonFrontSprites=0x1F075C +PokemonNormalPalettes=0x1F29BC +IntroCryOffset=0xA6DA +IntroSpriteOffset=0xB48C +IntroPaletteOffset=0xB498 +IntroOtherOffset=0xB45A +TradeTableOffset=0x21DF10 +RunIndoorsTweakOffset=0xE6220 +CatchingTutorialOpponentMonOffset=0x8201C +CatchingTutorialPlayerMonOffset=0x10FB2A +PCPotionOffset=0x40DC10 +TypeEffectivenessOffset=0x201B28 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x157B23, 0x157B51], Level=[0x157B53]} // Lileep +StaticPokemon{}={Species=[0x157B76, 0x157BA4], Level=[0x157BA6]} // Anorith +StaticPokemon{}={Species=[0x1A58A8, 0x15E2E6, 0x15E2ED], Level=[0x15E2E8]} // Groudon +StaticPokemon{}={Species=[0x15D049, 0x15D052], Level=[0x15D054]} // Regirock +StaticPokemon{}={Species=[0x15F461, 0x15F46A], Level=[0x15F46C]} // Regice +StaticPokemon{}={Species=[0x15F514, 0x15F51D], Level=[0x15F51F]} // Registeel +StaticPokemon{}={Species=[0x16108E, 0x1610B4], Level=[0x1610B6]} // Latias (Southern Island) +StaticPokemon{}={Species=[0x15F7D9, 0x15F7E0], Level=[0x15F7DB]} // Rayquaza +StaticPokemon{}={Species=[0x1A59E7, 0x1A59F0], Level=[0x1A59F2]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x151DA8, 0x151DB1], Level=[0x151DB3]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15EDC3, 0x15EDCA], Level=[0x15EDC5]} // Voltorb 1 +StaticPokemon{}={Species=[0x15EDE1, 0x15EDE8], Level=[0x15EDE3]} // Voltorb 2 +StaticPokemon{}={Species=[0x15EDFF, 0x15EE06], Level=[0x15EE01]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A5905, 0x1A590C], Level=[0x1A5907]} // Electrode 1 +StaticPokemon{}={Species=[0x1A5923, 0x1A592A], Level=[0x1A5925]} // Electrode 2 +StaticPokemon{}={Species=[0x14EC5A]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AF6F, 0x15AF7F], Level=[0x15AF71]} // Beldum +StaticPokemon{}={Species=[0x164259], Level=[0x16425B]} // Castform +RoamingPokemon{}={Species=[0x110E88, 0x134780], Level=[0x1346E4, 0x1346F4]} // Latios +FindMapsWithMonFunctionStartOffset=0x110E08 +CreateInitialRoamerMonFunctionStartOffset=0x1346C8 +ShopItemOffsets=[0x14BF90, 0x14C374, 0x15345C, 0x153478, 0x153B00, 0x153DA4, 0x153E40, 0x154394, 0x155790, 0x1557BC, 0x1568E8, 0x157898, 0x1580C0, 0x1580E8, 0x158540, 0x15A404, 0x15A438, 0x15A468, 0x15A490, 0x15A4F0, 0x15A514, 0x15AE00, 0x15B6F4, 0x15BF80] +CRC32=690FD310 + +[Ruby (F) 1.1] +Game=AXVF +Version=1 +Type=Ruby +CopyStaticPokemon=1 +CopyFrom=Ruby (F) +CRC32=9F981F72 + +[Sapphire (F)] +Game=AXPF +Version=0 +Type=Sapp +CopyFrom=Ruby (F) +PokemonStats=0x206FF4 +PokemonMovesets=0x20FFA4 +EggMoves=0x2115B8 +PokemonTMHMCompat=0x2054CC +PokemonEvolutions=0x20BF44 +StarterPokemon=0x3FEF24 +TrainerData=0x1F8894 +TrainerClassNames=0x1F85A0 +ItemData=0x3CCAF4 +MoveData=0x203508 +MoveDescriptions=0x3C7F64 +MoveNames=0x2006B8 +AbilityNames=0x202624 +TmMoves=0x37D0F8 +PokemonFrontSprites=0x1F06EC +PokemonNormalPalettes=0x1F294C +TradeTableOffset=0x21DEA0 +RunIndoorsTweakOffset=0xE6220 +PCPotionOffset=0x40D740 +TypeEffectivenessOffset=0x201AB8 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x157AB3, 0x157AE1], Level=[0x157AE3]} // Lileep +StaticPokemon{}={Species=[0x157B06, 0x157B34], Level=[0x157B36]} // Anorith +StaticPokemon{}={Species=[0x1A5838, 0x15E276, 0x15E27D], Level=[0x15E278]} // Kyogre +StaticPokemon{}={Species=[0x15CFD9, 0x15CFE2], Level=[0x15CFE4]} // Regirock +StaticPokemon{}={Species=[0x15F3F1, 0x15F3FA], Level=[0x15F3FC]} // Regice +StaticPokemon{}={Species=[0x15F4A4, 0x15F4AD], Level=[0x15F4AF]} // Registeel +StaticPokemon{}={Species=[0x16101E, 0x161044], Level=[0x161046]} // Latios (Southern Island) +StaticPokemon{}={Species=[0x15F769, 0x15F770], Level=[0x15F76B]} // Rayquaza +StaticPokemon{}={Species=[0x1A5977, 0x1A5980], Level=[0x1A5982]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x151D3C, 0x151D45], Level=[0x151D47]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15ED53, 0x15ED5A], Level=[0x15ED55]} // Voltorb 1 +StaticPokemon{}={Species=[0x15ED71, 0x15ED78], Level=[0x15ED73]} // Voltorb 2 +StaticPokemon{}={Species=[0x15ED8F, 0x15ED96], Level=[0x15ED91]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A5895, 0x1A589C], Level=[0x1A5897]} // Electrode 1 +StaticPokemon{}={Species=[0x1A58B3, 0x1A58BA], Level=[0x1A58B5]} // Electrode 2 +StaticPokemon{}={Species=[0x14EBEE]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AEFF, 0x15AF0F], Level=[0x15AF01]} // Beldum +StaticPokemon{}={Species=[0x1641E9], Level=[0x1641EB]} // Castform +RoamingPokemon{}={Species=[0x110E84, 0x134784], Level=[0x1346E2, 0x1346F2]} // Latias +ShopItemOffsets=[0x14BF90, 0x14C374, 0x1533EC, 0x153408, 0x153A90, 0x153D34, 0x153DD0, 0x154324, 0x155720, 0x15574C, 0x156878, 0x157828, 0x158050, 0x158078, 0x1584D0, 0x15A394, 0x15A3C8, 0x15A3F8, 0x15A420, 0x15A480, 0x15A4A4, 0x15AD90, 0x15B684, 0x15BF10] +CRC32=3581A05F + +[Sapphire (F) 1.1] +Game=AXPF +Version=1 +Type=Sapp +CopyStaticPokemon=1 +CopyFrom=Sapphire (F) +CRC32=2AE49146 + +[Emerald (F)] +Game=BPEF +Version=0 +Type=Em +CopyFrom=Emerald (U) +PokemonMovesets=0x330EEC +EggMoves=0x332948 +PokemonTMHMCompat=0x326408 +PokemonEvolutions=0x32CE8C +StarterPokemon=0x5B63E4 +StarterItems=0xB118E +TrainerData=0x317B60 +TrainerClassNames=0x317804 +MossdeepStevenTeamOffset=0x5E1BCC +MoveDescriptions=0x620920 +TmMoves=0x619F1C +TmMovesDuplicate=0x61A3C8 +MoveTutorData=0x619394 +ItemImages=0x618798 +TmPals=[0xDB5F6C, 0xDB5ECC, 0xDB6124, 0xDB5F94, 0xDB60AC, 0xDB60FC, 0xDB5FE4, 0xDB60D4, 0xDB605C, 0xDB60D4, 0xDB600C, 0xDB5F1C, 0xDB5FE4, 0xDB6084, 0xDB5F44, 0xDB5FBC, 0xDB5EF4, 0xDB6034] +IntroCryOffset=0x30B0C +IntroSpriteOffset=0x31924 +TradeTableOffset=0x340A54 +RunIndoorsTweakOffset=0x119E2C +TextSpeedValuesOffset=0x61341C +CatchingTutorialOpponentMonOffset=0xB0884 +CatchingTutorialPlayerMonOffset=0x1390B6 +PCPotionOffset=0x5E43FC +TypeEffectivenessOffset=0x322818 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x2142C0, 0x2142E5, 0x2142E8, 0x21436A, 0x214378], Level=[0x2142EA]} // Lileep +StaticPokemon{}={Species=[0x2142D2, 0x214388, 0x21438B, 0x21440D, 0x21441B], Level=[0x21438D]} // Anorith +StaticPokemon{}={Species=[0x23FEE1, 0x23FEEF, 0x23FF44, 0x1E5FD2, 0x1E6052, 0x1E610E, 0x1E618E], Level=[0x23FEF1]} // Kyogre +StaticPokemon{}={Species=[0x23FFB2, 0x23FFC0, 0x240015, 0x1E6012, 0x1E614E], Level=[0x23FFC2]} // Groudon +StaticPokemon{}={Species=[0x231B1F, 0x231B28, 0x231B6E], Level=[0x231B2A]} // Regirock +StaticPokemon{}={Species=[0x23DC62, 0x23DC6B, 0x23DCB1], Level=[0x23DC6D]} // Regice +StaticPokemon{}={Species=[0x23DD64, 0x23DD6D, 0x23DDB3], Level=[0x23DD6F]} // Registeel +StaticPokemon{}={Species=[0x23E449, 0x23E452, 0x23E498, 0x23E4E7, 0x23E505, 0x1E62B0, 0x1E62CE, 0x1E6364, 0x1E6382], Level=[0x23E454]} // Rayquaza +StaticPokemon{}={Species=[0x277645, 0x27764E], Level=[0x277650]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x1F68F4, 0x1F68FD], Level=[0x1F68FF]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x23C389, 0x23C390], Level=[0x23C38B]} // Voltorb 1 +StaticPokemon{}={Species=[0x23C3D6, 0x23C3DD], Level=[0x23C3D8]} // Voltorb 2 +StaticPokemon{}={Species=[0x23C423, 0x23C42A], Level=[0x23C425]} // Voltorb 3 +StaticPokemon{}={Species=[0x23827E, 0x238285], Level=[0x238280]} // Electrode 1 +StaticPokemon{}={Species=[0x2382CB, 0x2382D2], Level=[0x2382CD]} // Electrode 2 +StaticPokemon{}={Species=[0x247FC7, 0x247FD5], Level=[0x247FD7]} // Sudowoodo in Battle Frontier +StaticPokemon{}={Species=[0x247D3E, 0x247E53], Level=[0x247E58]} // Latios on Southern Island +StaticPokemon{}={Species=[0x247D49, 0x247E66], Level=[0x247E6B]} // Latias on Southern Island +StaticPokemon{}={Species=[0x26CE61, 0x26CE71, 0x26CEBB, 0x26CEC6], Level=[0x26CE76]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x26CC78, 0x26CCB2, 0x26CD07, 0x26CD12], Level=[0x26CCB7]} // Mew on Faraway Island +StaticPokemon{}={Species=[0x26E00D, 0x26E03C, 0x26E08B, 0x26E096], Level=[0x26E041]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x26E155, 0x26E160, 0x26E1AA, 0x26E1B5], Level=[0x26E165]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x1EB1A5]} // Wynaut Egg +StaticPokemon{}={Species=[0x22607E, 0x226081, 0x226103, 0x226114], Level=[0x226083]} // Beldum +StaticPokemon{}={Species=[0x275296, 0x275299, 0x275325], Level=[0x27529B]} // Castform +RoamingPokemon{}={Species=[0x16187C], Level=[0x1618AE, 0x1618BA]} // Latios +RoamingPokemon{}={Species=[0x161884], Level=[0x1618AE, 0x1618BA]} // Latias +CreateInitialRoamerMonFunctionStartOffset=0x161860 +ShopItemOffsets=[0x1DCB78, 0x1DCF94, 0x1FD998, 0x1FD9B4, 0x1FFEC0, 0x201584, 0x2018D8, 0x2043A4, 0x209E84, 0x209EB0, 0x210184, 0x2138EC, 0x217B24, 0x217B4C, 0x21A544, 0x223138, 0x22316C, 0x223278, 0x2232A0, 0x223458, 0x22347C, 0x225B5C, 0x22A368, 0x22D460, 0x26C96C, 0x26D264, 0x26D290] +CRC32=A3FDCCB1 + +[Ruby (G)] +Game=AXVD +Version=0 +Type=Ruby +CopyFrom=Ruby (U) +PokemonStats=0x20BBE8 +PokemonMovesets=0x214B98 +EggMoves=0x2161AC +PokemonTMHMCompat=0x20A0C0 +PokemonEvolutions=0x210B38 +StarterPokemon=0x403BF0 +StarterItems=0x825DE +TrainerData=0x1FD478 +TrainerClassNames=0x1FD184 +ItemData=0x3D13DC +MoveData=0x2080FC +MoveDescriptions=0x3CC978 +MoveNames=0x20529C +AbilityNames=0x207218 +TmMoves=0x381CEC +PokemonFrontSprites=0x1F52D0 +PokemonNormalPalettes=0x1F7530 +IntroCryOffset=0xA6DA +IntroSpriteOffset=0xB48C +IntroPaletteOffset=0xB498 +IntroOtherOffset=0xB45A +TradeTableOffset=0x222A94 +RunIndoorsTweakOffset=0xE613C +CatchingTutorialOpponentMonOffset=0x81F34 +CatchingTutorialPlayerMonOffset=0x10FA22 +PCPotionOffset=0x412770 +TypeEffectivenessOffset=0x20669C +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x157A3B, 0x157A69], Level=[0x157A6B]} // Lileep +StaticPokemon{}={Species=[0x157A8E, 0x157ABC], Level=[0x157ABE]} // Anorith +StaticPokemon{}={Species=[0x1A8695, 0x15E1FE, 0x15E205], Level=[0x15E200]} // Groudon +StaticPokemon{}={Species=[0x15CF61, 0x15CF6A], Level=[0x15CF6C]} // Regirock +StaticPokemon{}={Species=[0x15F379, 0x15F382], Level=[0x15F384]} // Regice +StaticPokemon{}={Species=[0x15F42C, 0x15F435], Level=[0x15F437]} // Registeel +StaticPokemon{}={Species=[0x160FA6, 0x160FCC], Level=[0x160FCE]} // Latias (Southern Island) +StaticPokemon{}={Species=[0x15F6F1, 0x15F6F8], Level=[0x15F6F3]} // Rayquaza +StaticPokemon{}={Species=[0x1A87D4, 0x1A87DD], Level=[0x1A87DF]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x151CC0, 0x151CC9], Level=[0x151CCB]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15ECDB, 0x15ECE2], Level=[0x15ECDD]} // Voltorb 1 +StaticPokemon{}={Species=[0x15ECF9, 0x15ED00], Level=[0x15ECFB]} // Voltorb 2 +StaticPokemon{}={Species=[0x15ED17, 0x15ED1E], Level=[0x15ED19]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A86F2, 0x1A86F9], Level=[0x1A86F4]} // Electrode 1 +StaticPokemon{}={Species=[0x1A8710, 0x1A8717], Level=[0x1A8712]} // Electrode 2 +StaticPokemon{}={Species=[0x14EB72]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AE87, 0x15AE97], Level=[0x15AE89]} // Beldum +StaticPokemon{}={Species=[0x164171], Level=[0x164173]} // Castform +RoamingPokemon{}={Species=[0x110D80, 0x134674], Level=[0x1345D8, 0x1345E8]} // Latios +FindMapsWithMonFunctionStartOffset=0x110D00 +CreateInitialRoamerMonFunctionStartOffset=0x1345BC +ShopItemOffsets=[0x14BEA8, 0x14C28C, 0x153374, 0x153390, 0x153A18, 0x153CBC, 0x153D58, 0x1542AC, 0x1556A8, 0x1556D4, 0x156800, 0x1577B0, 0x157FD8, 0x158000, 0x158458, 0x15A31C, 0x15A350, 0x15A380, 0x15A3A8, 0x15A408, 0x15A42C, 0x15AD18, 0x15B60C, 0x15BE98] +CRC32=15E1E280 + +[Ruby (G) 1.1] +Game=AXVD +Version=1 +Type=Ruby +CopyStaticPokemon=1 +CopyFrom=Ruby (G) +CRC32=CAE89464 + +[Sapphire (G)] +Game=AXPD +Version=0 +Type=Sapp +CopyFrom=Ruby (G) +PokemonStats=0x20BB7C +PokemonMovesets=0x214B2C +EggMoves=0x216140 +PokemonTMHMCompat=0x20A054 +PokemonEvolutions=0x210ACC +StarterPokemon=0x403B5C +TrainerData=0x1FD40C +TrainerClassNames=0x1FD118 +ItemData=0x3D1348 +MoveData=0x208090 +MoveDescriptions=0x3CC8E4 +MoveNames=0x20523D +AbilityNames=0x2071AC +TmMoves=0x381C80 +PokemonFrontSprites=0x1F5264 +PokemonNormalPalettes=0x1F74C4 +TradeTableOffset=0x222A28 +RunIndoorsTweakOffset=0xE613C +PCPotionOffset=0x4126DC +TypeEffectivenessOffset=0x206630 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x1579CF, 0x1579FD], Level=[0x1579FF]} // Lileep +StaticPokemon{}={Species=[0x157A22, 0x157A50], Level=[0x157A52]} // Anorith +StaticPokemon{}={Species=[0x1A8629, 0x15E192, 0x15E199], Level=[0x15E194]} // Kyogre +StaticPokemon{}={Species=[0x15CEF5, 0x15CEFE], Level=[0x15CF00]} // Regirock +StaticPokemon{}={Species=[0x15F30D, 0x15F316], Level=[0x15F318]} // Regice +StaticPokemon{}={Species=[0x15F3C0, 0x15F3C9], Level=[0x15F3CB]} // Registeel +StaticPokemon{}={Species=[0x160F3A, 0x160F60], Level=[0x160F62]} // Latios (Southern Island) +StaticPokemon{}={Species=[0x15F685, 0x15F68C], Level=[0x15F687]} // Rayquaza +StaticPokemon{}={Species=[0x1A8768, 0x1A8771], Level=[0x1A8773]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x151C58, 0x151C61], Level=[0x151C63]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15EC6F, 0x15EC76], Level=[0x15EC71]} // Voltorb 1 +StaticPokemon{}={Species=[0x15EC8D, 0x15EC94], Level=[0x15EC8F]} // Voltorb 2 +StaticPokemon{}={Species=[0x15ECAB, 0x15ECB2], Level=[0x15ECAD]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A8686, 0x1A868D], Level=[0x1A8688]} // Electrode 1 +StaticPokemon{}={Species=[0x1A86A4, 0x1A86AB], Level=[0x1A86A6]} // Electrode 2 +StaticPokemon{}={Species=[0x14EB0A]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AE1B, 0x15AE2B], Level=[0x15AE1D]} // Beldum +StaticPokemon{}={Species=[0x164105], Level=[0x164107]} // Castform +RoamingPokemon{}={Species=[0x110D7C, 0x13467C], Level=[0x1345DA, 0x1345EA]} // Latias +ShopItemOffsets=[0x14BEAC, 0x14C290, 0x153308, 0x153324, 0x1539AC, 0x153C50, 0x153CEC, 0x154240, 0x15563C, 0x155668, 0x156794, 0x157744, 0x157F6C, 0x157F94, 0x1583EC, 0x15A2B0, 0x15A2E4, 0x15A314, 0x15A33C, 0x15A39C, 0x15A3C0, 0x15ACAC, 0x15B5A0, 0x15BE2C] +CRC32=6FCD7A98 + +[Sapphire (G) 1.1] +Game=AXPD +Version=1 +Type=Sapp +CopyStaticPokemon=1 +CopyFrom=Sapphire (G) +CRC32=B0C40C7C + +[Emerald (G)] +Game=BPED +Version=0 +Type=Em +CopyFrom=Emerald (U) +PokemonMovesets=0x33DD3C +EggMoves=0x33F798 +PokemonTMHMCompat=0x333258 +PokemonEvolutions=0x339CDC +StarterPokemon=0x5C2FB0 +StarterItems=0xB1196 +TrainerData=0x3249A0 +TrainerClassNames=0x324644 +MossdeepStevenTeamOffset=0x5EEA98 +MoveDescriptions=0x62DA80 +TmMoves=0x62705C +TmMovesDuplicate=0x627508 +MoveTutorData=0x6264D4 +ItemImages=0x6258D8 +TmPals=[0xDB5FA4, 0xDB5F04, 0xDB615C, 0xDB5FCC, 0xDB60E4, 0xDB6134, 0xDB601C, 0xDB610C, 0xDB6094, 0xDB610C, 0xDB6044, 0xDB5F54, 0xDB601C, 0xDB60BC, 0xDB5F7C, 0xDB5FF4, 0xDB5F2C, 0xDB606C] +IntroCryOffset=0x30B10 +IntroSpriteOffset=0x31928 +TradeTableOffset=0x34D89C +RunIndoorsTweakOffset=0x119E0C +TextSpeedValuesOffset=0x62055C +CatchingTutorialOpponentMonOffset=0xB088C +CatchingTutorialPlayerMonOffset=0x139096 +PCPotionOffset=0x5F12C8 +TypeEffectivenessOffset=0x32F658 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x215CD3, 0x215CF8, 0x215CFB, 0x215D7D, 0x215D8B], Level=[0x215CFD]} // Lileep +StaticPokemon{}={Species=[0x215CE5, 0x215D9B, 0x215D9E, 0x215E20, 0x215E2E], Level=[0x215DA0]} // Anorith +StaticPokemon{}={Species=[0x24354E, 0x24355C, 0x2435B1, 0x1E6397, 0x1E6417, 0x1E64D3, 0x1E6553], Level=[0x24355E]} // Kyogre +StaticPokemon{}={Species=[0x24361F, 0x24362D, 0x243682, 0x1E63D7, 0x1E6513], Level=[0x24362F]} // Groudon +StaticPokemon{}={Species=[0x234815, 0x23481E, 0x234864], Level=[0x234820]} // Regirock +StaticPokemon{}={Species=[0x2411BD, 0x2411C6, 0x24120C], Level=[0x2411C8]} // Regice +StaticPokemon{}={Species=[0x2412BF, 0x2412C8, 0x24130E], Level=[0x2412CA]} // Registeel +StaticPokemon{}={Species=[0x2419BC, 0x2419C5, 0x241A0B, 0x241A5A, 0x241A78, 0x1E6675, 0x1E6693, 0x1E6729, 0x1E6747], Level=[0x2419C7]} // Rayquaza +StaticPokemon{}={Species=[0x27DA85, 0x27DA8E], Level=[0x27DA90]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x1F7227, 0x1F7230], Level=[0x1F7232]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x23F82B, 0x23F832], Level=[0x23F82D]} // Voltorb 1 +StaticPokemon{}={Species=[0x23F878, 0x23F87F], Level=[0x23F87A]} // Voltorb 2 +StaticPokemon{}={Species=[0x23F8C5, 0x23F8CC], Level=[0x23F8C7]} // Voltorb 3 +StaticPokemon{}={Species=[0x23B4C0, 0x23B4C7], Level=[0x23B4C2]} // Electrode 1 +StaticPokemon{}={Species=[0x23B50D, 0x23B514], Level=[0x23B50F]} // Electrode 2 +StaticPokemon{}={Species=[0x24B918, 0x24B926], Level=[0x24B928]} // Sudowoodo in Battle Frontier +StaticPokemon{}={Species=[0x24B68F, 0x24B7A4], Level=[0x24B7A9]} // Latios on Southern Island +StaticPokemon{}={Species=[0x24B69A, 0x24B7B7], Level=[0x24B7BC]} // Latias on Southern Island +StaticPokemon{}={Species=[0x272CA4, 0x272CB4, 0x272CFE, 0x272D09], Level=[0x272CB9]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x272AB8, 0x272AF2, 0x272B47, 0x272B52], Level=[0x272AF7]} // Mew on Faraway Island +StaticPokemon{}={Species=[0x273F63, 0x273F92, 0x273FE1, 0x273FEC], Level=[0x273F97]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x2740AB, 0x2740B6, 0x274100, 0x27410B], Level=[0x2740BB]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x1EB5E8]} // Wynaut Egg +StaticPokemon{}={Species=[0x228813, 0x228816, 0x228898, 0x2288A9], Level=[0x228818]} // Beldum +StaticPokemon{}={Species=[0x27B588, 0x27B58B, 0x27B617], Level=[0x27B58D]} // Castform +RoamingPokemon{}={Species=[0x161754], Level=[0x161786, 0x161792]} // Latios +RoamingPokemon{}={Species=[0x16175C], Level=[0x161786, 0x161792]} // Latias +CreateInitialRoamerMonFunctionStartOffset=0x161738 +ShopItemOffsets=[0x1DCAB0, 0x1DCECC, 0x1FE648, 0x1FE664, 0x200C6C, 0x2022FC, 0x202648, 0x20516C, 0x20B150, 0x20B17C, 0x2118E4, 0x2152C0, 0x219878, 0x2198A0, 0x21C4C4, 0x22562C, 0x225660, 0x225770, 0x225798, 0x225954, 0x225978, 0x22830C, 0x22CC54, 0x22FE60, 0x272774, 0x2730A4, 0x2730D0] +CRC32=34C9DF89 + +[Ruby (S)] +Game=AXVS +Version=0 +Type=Ruby +CopyFrom=Ruby (U) +PokemonStats=0x203994 +PokemonMovesets=0x20C944 +EggMoves=0x20DF58 +PokemonTMHMCompat=0x201E6C +PokemonEvolutions=0x2088E4 +StarterPokemon=0x3FB50C +StarterItems=0x82666 +TrainerData=0x1F521C +TrainerClassNames=0x1F4F28 +ItemData=0x3C8FFC +MoveData=0x1FFEA8 +MoveDescriptions=0x3C4504 +MoveNames=0x1FD040 +AbilityNames=0x1FEFC4 +TmMoves=0x379A9C +PokemonFrontSprites=0x1ED074 +PokemonNormalPalettes=0x1EF2D4 +IntroCryOffset=0xA6D2 +IntroSpriteOffset=0xB484 +IntroPaletteOffset=0xB490 +IntroOtherOffset=0xB452 +TradeTableOffset=0x21A840 +RunIndoorsTweakOffset=0xE620C +CatchingTutorialOpponentMonOffset=0x81FBC +CatchingTutorialPlayerMonOffset=0x10FB26 +PCPotionOffset=0x409FE0 +TypeEffectivenessOffset=0x1FE440 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x157B5F, 0x157B8D], Level=[0x157B8F]} // Lileep +StaticPokemon{}={Species=[0x157BB2, 0x157BE0], Level=[0x157BE2]} // Anorith +StaticPokemon{}={Species=[0x1A376D, 0x15E322, 0x15E329], Level=[0x15E324]} // Groudon +StaticPokemon{}={Species=[0x15D085, 0x15D08E], Level=[0x15D090]} // Regirock +StaticPokemon{}={Species=[0x15F49D, 0x15F4A6], Level=[0x15F4A8]} // Regice +StaticPokemon{}={Species=[0x15F550, 0x15F559], Level=[0x15F55B]} // Registeel +StaticPokemon{}={Species=[0x1610CA, 0x1610F0], Level=[0x1610F2]} // Latias (Southern Island) +StaticPokemon{}={Species=[0x15F815, 0x15F81C], Level=[0x15F817]} // Rayquaza +StaticPokemon{}={Species=[0x1A38AC, 0x1A38B5], Level=[0x1A38B7]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x151DE4, 0x151DED], Level=[0x151DEF]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15EDFF, 0x15EE06], Level=[0x15EE01]} // Voltorb 1 +StaticPokemon{}={Species=[0x15EE1D, 0x15EE24], Level=[0x15EE1F]} // Voltorb 2 +StaticPokemon{}={Species=[0x15EE3B, 0x15EE42], Level=[0x15EE3D]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A37CA, 0x1A37D1], Level=[0x1A37CC]} // Electrode 1 +StaticPokemon{}={Species=[0x1A37E8, 0x1A37EF], Level=[0x1A37EA]} // Electrode 2 +StaticPokemon{}={Species=[0x14EC96]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AFAB, 0x15AFBB], Level=[0x15AFAD]} // Beldum +StaticPokemon{}={Species=[0x164295], Level=[0x164297]} // Castform +RoamingPokemon{}={Species=[0x110E80, 0x1347BC], Level=[0x134720, 0x134730]} // Latios +FindMapsWithMonFunctionStartOffset=0x110E00 +CreateInitialRoamerMonFunctionStartOffset=0x134704 +ShopItemOffsets=[0x14BFCC, 0x14C3B0, 0x153498, 0x1534B4, 0x153B3C, 0x153DE0, 0x153E7C, 0x1543D0, 0x1557CC, 0x1557F8, 0x156924, 0x1578D4, 0x1580FC, 0x158124, 0x15857C, 0x15A440, 0x15A474, 0x15A4A4, 0x15A4CC, 0x15A52C, 0x15A550, 0x15AE3C, 0x15B730, 0x15BFBC] +CRC32=EB0729CF + +[Ruby (S) 1.1] +Game=AXVS +Version=1 +Type=Ruby +CopyStaticPokemon=1 +CopyFrom=Ruby (S) +CRC32=B980FFF5 + +[Sapphire (S)] +Game=AXPS +Version=0 +Type=Sapp +CopyFrom=Ruby (S) +PokemonStats=0x203924 +PokemonMovesets=0x20C8D4 +EggMoves=0x20DEE8 +PokemonTMHMCompat=0x201DFC +PokemonEvolutions=0x208874 +StarterPokemon=0x3FB248 +TrainerData=0x1F51AC +TrainerClassNames=0x1F4EB8 +ItemData=0x3C8D38 +MoveData=0x1FFE38 +MoveDescriptions=0x3C4240 +MoveNames=0x1FCFD0 +AbilityNames=0x1FEF54 +TmMoves=0x379A2C +PokemonFrontSprites=0x1ED004 +PokemonNormalPalettes=0x1EF264 +TradeTableOffset=0x21A7D0 +RunIndoorsTweakOffset=0xE620C +PCPotionOffset=0x409D1C +TypeEffectivenessOffset=0x1FE3D0 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x157AEF, 0x157B1D], Level=[0x157B1F]} // Lileep +StaticPokemon{}={Species=[0x157B42, 0x157B70], Level=[0x157B72]} // Anorith +StaticPokemon{}={Species=[0x1A36FD, 0x15E2B2, 0x15E2B9], Level=[0x15E2B4]} // Kyogre +StaticPokemon{}={Species=[0x15D015, 0x15D01E], Level=[0x15D020]} // Regirock +StaticPokemon{}={Species=[0x15F42D, 0x15F436], Level=[0x15F438]} // Regice +StaticPokemon{}={Species=[0x15F4E0, 0x15F4E9], Level=[0x15F4EB]} // Registeel +StaticPokemon{}={Species=[0x16105A, 0x161080], Level=[0x161082]} // Latios (Southern Island) +StaticPokemon{}={Species=[0x15F7A5, 0x15F7AC], Level=[0x15F7A7]} // Rayquaza +StaticPokemon{}={Species=[0x1A383C, 0x1A3845], Level=[0x1A3847]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x151D78, 0x151D81], Level=[0x151D83]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15ED8F, 0x15ED96], Level=[0x15ED91]} // Voltorb 1 +StaticPokemon{}={Species=[0x15EDAD, 0x15EDB4], Level=[0x15EDAF]} // Voltorb 2 +StaticPokemon{}={Species=[0x15EDCB, 0x15EDD2], Level=[0x15EDCD]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A375A, 0x1A3761], Level=[0x1A375C]} // Electrode 1 +StaticPokemon{}={Species=[0x1A3778, 0x1A377F], Level=[0x1A377A]} // Electrode 2 +StaticPokemon{}={Species=[0x14EC2A]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AF3B, 0x15AF4B], Level=[0x15AF3D]} // Beldum +StaticPokemon{}={Species=[0x164225], Level=[0x164227]} // Castform +RoamingPokemon{}={Species=[0x110E7C, 0x1347C0], Level=[0x13471E, 0x13472E]} // Latias +ShopItemOffsets=[0x14BFCC, 0x14C3B0, 0x153428, 0x153444, 0x153ACC, 0x153D70, 0x153E0C, 0x154360, 0x15575C, 0x155788, 0x1568B4, 0x157864, 0x15808C, 0x1580B4, 0x15850C, 0x15A3D0, 0x15A404, 0x15A434, 0x15A45C, 0x15A4BC, 0x15A4E0, 0x15ADCC, 0x15B6C0, 0x15BF4C] +CRC32=A04F5F0B + +[Sapphire (S) 1.1] +Game=AXPS +Version=1 +Type=Sapp +CopyStaticPokemon=1 +CopyFrom=Sapphire (S) +CRC32=F2C88931 + +[Emerald (S)] +Game=BPES +Version=0 +Type=Em +CopyFrom=Emerald (U) +PokemonMovesets=0x32F638 +EggMoves=0x331094 +PokemonTMHMCompat=0x324B54 +PokemonEvolutions=0x32B5D8 +StarterPokemon=0x5B49FC +StarterItems=0xBE596 +TrainerData=0x316294 +TrainerClassNames=0x315F38 +MossdeepStevenTeamOffset=0x5E04A4 +MoveDescriptions=0x61EE38 +TmMoves=0x6189D4 +TmMovesDuplicate=0x618E80 +MoveTutorData=0x617E4C +ItemImages=0x617250 +TmPals=[0xDB5F00, 0xDB5E60, 0xDB60B8, 0xDB5F28, 0xDB6040, 0xDB6090, 0xDB5F78, 0xDB6068, 0xDB5FF0, 0xDB6068, 0xDB5FA0, 0xDB5EB0, 0xDB5F78, 0xDB6018, 0xDB5ED8, 0xDB5F50, 0xDB5E88, 0xDB5FC8] +IntroCryOffset=0x30B0C +IntroSpriteOffset=0x31924 +TradeTableOffset=0x33F1AC +RunIndoorsTweakOffset=0x119E00 +TextSpeedValuesOffset=0x611ED4 +CatchingTutorialOpponentMonOffset=0xB0884 +CatchingTutorialPlayerMonOffset=0x13908A +PCPotionOffset=0x5E2CD4 +TypeEffectivenessOffset=0x320F4C +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x21307F, 0x2130A4, 0x2130A7, 0x213129, 0x213137], Level=[0x2130A9]} // Lileep +StaticPokemon{}={Species=[0x213091, 0x213147, 0x21314A, 0x2131CC, 0x2131DA], Level=[0x21314C]} // Anorith +StaticPokemon{}={Species=[0x23DCC5, 0x23DCD3, 0x23DD28, 0x1E5B08, 0x1E5B88, 0x1E5C44, 0x1E5CC4], Level=[0x23DCD5]} // Kyogre +StaticPokemon{}={Species=[0x23DD96, 0x23DDA4, 0x23DDF9, 0x1E5B48, 0x1E5C84], Level=[0x23DDA6]} // Groudon +StaticPokemon{}={Species=[0x22FD3F, 0x22FD48, 0x22FD8E], Level=[0x22FD4A]} // Regirock +StaticPokemon{}={Species=[0x23BB32, 0x23BB3B, 0x23BB81], Level=[0x23BB3D]} // Regice +StaticPokemon{}={Species=[0x23BC34, 0x23BC3D, 0x23BC83], Level=[0x23BC3F]} // Registeel +StaticPokemon{}={Species=[0x23C305, 0x23C30E, 0x23C354, 0x23C3A3, 0x23C3C1, 0x1E5DE6, 0x1E5E04, 0x1E5E9A, 0x1E5EB8], Level=[0x23C310]} // Rayquaza +StaticPokemon{}={Species=[0x276188, 0x276191], Level=[0x276193]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x1F5CDA, 0x1F5CE3], Level=[0x1F5CE5]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x23A38F, 0x23A396], Level=[0x23A391]} // Voltorb 1 +StaticPokemon{}={Species=[0x23A3DC, 0x23A3E3], Level=[0x23A3DE]} // Voltorb 2 +StaticPokemon{}={Species=[0x23A429, 0x23A430], Level=[0x23A42B]} // Voltorb 3 +StaticPokemon{}={Species=[0x236418, 0x23641F], Level=[0x23641A]} // Electrode 1 +StaticPokemon{}={Species=[0x236465, 0x23646C], Level=[0x236467]} // Electrode 2 +StaticPokemon{}={Species=[0x245C8C, 0x245C9A], Level=[0x245C9C]} // Sudowoodo in Battle Frontier +StaticPokemon{}={Species=[0x245A03, 0x245B18], Level=[0x245B1D]} // Latios on Southern Island +StaticPokemon{}={Species=[0x245A0E, 0x245B2B], Level=[0x245B30]} // Latias on Southern Island +StaticPokemon{}={Species=[0x26B948, 0x26B958, 0x26B9A2, 0x26B9AD], Level=[0x26B95D]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x26B75A, 0x26B794, 0x26B7E9, 0x26B7F4], Level=[0x26B799]} // Mew on Faraway Island +StaticPokemon{}={Species=[0x26CBEA, 0x26CC19, 0x26CC68, 0x26CC73], Level=[0x26CC1E]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x26CD32, 0x26CD3D, 0x26CD87, 0x26CD92], Level=[0x26CD42]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x1EA97A]} // Wynaut Egg +StaticPokemon{}={Species=[0x22477B, 0x22477E, 0x224800, 0x224811], Level=[0x224780]} // Beldum +StaticPokemon{}={Species=[0x273D60, 0x273D63, 0x273DEF], Level=[0x273D65]} // Castform +RoamingPokemon{}={Species=[0x161854], Level=[0x161886, 0x161892]} // Latios +RoamingPokemon{}={Species=[0x16185C], Level=[0x161886, 0x161892]} // Latias +CreateInitialRoamerMonFunctionStartOffset=0x161838 +ShopItemOffsets=[0x1DCB78, 0x1DCF94, 0x1FCAA4, 0x1FCAC0, 0x1FEEEC, 0x2004F4, 0x200814, 0x203228, 0x208C38, 0x208C64, 0x20EF28, 0x2126F8, 0x21683C, 0x216864, 0x2190A4, 0x221898, 0x2218CC, 0x2219E0, 0x221A08, 0x221B9C, 0x221BC0, 0x2242AC, 0x228858, 0x22B7C8, 0x26B41C, 0x26BD48, 0x26BD74] +CRC32=8C4D3108 + +[Ruby (I)] +Game=AXVI +Version=0 +Type=Ruby +CopyFrom=Ruby (U) +PokemonStats=0x2008F0 +PokemonMovesets=0x2098A0 +EggMoves=0x20AEB4 +PokemonTMHMCompat=0x1FEDC8 +PokemonEvolutions=0x205840 +StarterPokemon=0x3F8714 +StarterItems=0x8257E +TrainerData=0x1F2198 +TrainerClassNames=0x1F1EA4 +ItemData=0x3C5FF8 +MoveData=0x1FCE04 +MoveDescriptions=0x3C158C +MoveNames=0x1F9FBC +AbilityNames=0x1FBF20 +TmMoves=0x3769F4 +PokemonFrontSprites=0x1E9FF0 +PokemonNormalPalettes=0x1EC250 +IntroCryOffset=0xA6D2 +IntroSpriteOffset=0xB484 +IntroPaletteOffset=0xB490 +IntroOtherOffset=0xB452 +TradeTableOffset=0x21779C +RunIndoorsTweakOffset=0xE6118 +CatchingTutorialOpponentMonOffset=0x81ED4 +CatchingTutorialPlayerMonOffset=0x10FA22 +PCPotionOffset=0x406FE0 +TypeEffectivenessOffset=0x1FB3BC +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x157A87, 0x157AB5], Level=[0x157AB7]} // Lileep +StaticPokemon{}={Species=[0x157ADA, 0x157B08], Level=[0x157B0A]} // Anorith +StaticPokemon{}={Species=[0x1A0E4D, 0x15E24A, 0x15E251], Level=[0x15E24C]} // Groudon +StaticPokemon{}={Species=[0x15CFAD, 0x15CFB6], Level=[0x15CFB8]} // Regirock +StaticPokemon{}={Species=[0x15F3C5, 0x15F3CE], Level=[0x15F3D0]} // Regice +StaticPokemon{}={Species=[0x15F478, 0x15F481], Level=[0x15F483]} // Registeel +StaticPokemon{}={Species=[0x160FF2, 0x161018], Level=[0x16101A]} // Latias (Southern Island) +StaticPokemon{}={Species=[0x15F73D, 0x15F744], Level=[0x15F73F]} // Rayquaza +StaticPokemon{}={Species=[0x1A0F8C, 0x1A0F95], Level=[0x1A0F97]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x151D0C, 0x151D15], Level=[0x151D17]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15ED27, 0x15ED2E], Level=[0x15ED29]} // Voltorb 1 +StaticPokemon{}={Species=[0x15ED45, 0x15ED4C], Level=[0x15ED47]} // Voltorb 2 +StaticPokemon{}={Species=[0x15ED63, 0x15ED6A], Level=[0x15ED65]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A0EAA, 0x1A0EB1], Level=[0x1A0EAC]} // Electrode 1 +StaticPokemon{}={Species=[0x1A0EC8, 0x1A0ECF], Level=[0x1A0ECA]} // Electrode 2 +StaticPokemon{}={Species=[0x14EBBE]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AED3, 0x15AEE3], Level=[0x15AED5]} // Beldum +StaticPokemon{}={Species=[0x1641BD], Level=[0x1641BF]} // Castform +RoamingPokemon{}={Species=[0x110D80, 0x1346BC], Level=[0x134620, 0x134630]} // Latios +FindMapsWithMonFunctionStartOffset=0x110D00 +CreateInitialRoamerMonFunctionStartOffset=0x134604 +ShopItemOffsets=[0x14BEF4, 0x14C2D8, 0x1533C0, 0x1533DC, 0x153A64, 0x153D08, 0x153DA4, 0x1542F8, 0x1556F4, 0x155720, 0x15684C, 0x1577FC, 0x158024, 0x15804C, 0x1584A4, 0x15A368, 0x15A39C, 0x15A3CC, 0x15A3F4, 0x15A454, 0x15A478, 0x15AD64, 0x15B658, 0x15BEE4] +CRC32=C18231A9 + +[Ruby (I) 1.1] +Game=AXVI +Version=1 +Type=Ruby +CopyStaticPokemon=1 +CopyFrom=Ruby (I) +CRC32=9305E793 + +[Sapphire (I)] +Game=AXPI +Version=0 +Type=Sapp +CopyFrom=Ruby (I) +PokemonStats=0x200880 +PokemonMovesets=0x209830 +EggMoves=0x20AE44 +PokemonTMHMCompat=0x1FED58 +PokemonEvolutions=0x2057D0 +StarterPokemon=0x3F83B8 +TrainerData=0x1F2128 +TrainerClassNames=0x1F1E34 +ItemData=0x3C5C9C +MoveData=0x1FCD94 +MoveDescriptions=0x3C1230 +MoveNames=0x1F9F4C +AbilityNames=0x1FBEB0 +TmMoves=0x376984 +PokemonFrontSprites=0x1E9F80 +PokemonNormalPalettes=0x1EC1E0 +TradeTableOffset=0x21772C +RunIndoorsTweakOffset=0xE6118 +PCPotionOffset=0x406C84 +TypeEffectivenessOffset=0x1FB34C +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x157A17, 0x157A45], Level=[0x157A47]} // Lileep +StaticPokemon{}={Species=[0x157A6A, 0x157A98], Level=[0x157A9A]} // Anorith +StaticPokemon{}={Species=[0x1A0DDD, 0x15E1DA, 0x15E1E1], Level=[0x15E1DC]} // Kyogre +StaticPokemon{}={Species=[0x15CF3D, 0x15CF46], Level=[0x15CF48]} // Regirock +StaticPokemon{}={Species=[0x15F355, 0x15F35E], Level=[0x15F360]} // Regice +StaticPokemon{}={Species=[0x15F408, 0x15F411], Level=[0x15F413]} // Registeel +StaticPokemon{}={Species=[0x160F82, 0x160FA8], Level=[0x160FAA]} // Latios (Southern Island) +StaticPokemon{}={Species=[0x15F6CD, 0x15F6D4], Level=[0x15F6CF]} // Rayquaza +StaticPokemon{}={Species=[0x1A0F1C, 0x1A0F25], Level=[0x1A0F27]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x151CA0, 0x151CA9], Level=[0x151CAB]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x15ECB7, 0x15ECBE], Level=[0x15ECB9]} // Voltorb 1 +StaticPokemon{}={Species=[0x15ECD5, 0x15ECDC], Level=[0x15ECD7]} // Voltorb 2 +StaticPokemon{}={Species=[0x15ECF3, 0x15ECFA], Level=[0x15ECF5]} // Voltorb 3 +StaticPokemon{}={Species=[0x1A0E3A, 0x1A0E41], Level=[0x1A0E3C]} // Electrode 1 +StaticPokemon{}={Species=[0x1A0E58, 0x1A0E5F], Level=[0x1A0E5A]} // Electrode 2 +StaticPokemon{}={Species=[0x14EB52]} // Wynaut Egg +StaticPokemon{}={Species=[0x15AE63, 0x15AE73], Level=[0x15AE65]} // Beldum +StaticPokemon{}={Species=[0x16414D], Level=[0x16414F]} // Castform +RoamingPokemon{}={Species=[0x110D7C, 0x1346C0], Level=[0x13461E, 0x13462E]} // Latias +ShopItemOffsets=[0x14BEF4, 0x14C2D8, 0x153350, 0x15336C, 0x1539F4, 0x153C98, 0x153D34, 0x154288, 0x155684, 0x1556B0, 0x1567DC, 0x15778C, 0x157FB4, 0x157FDC, 0x158434, 0x15A2F8, 0x15A32C, 0x15A35C, 0x15A384, 0x15A3E4, 0x15A408, 0x15ACF4, 0x15B5E8, 0x15BE74] +CRC32=0C7992A9 + +[Sapphire (I) 1.1] +Game=AXPI +Version=1 +Type=Sapp +CopyStaticPokemon=1 +CopyFrom=Sapphire (I) +CRC32=5EFE4493 + +[Emerald (I)] +Game=BPEI +Version=0 +Type=Em +CopyFrom=Emerald (U) +PokemonMovesets=0x328D7C +EggMoves=0x32A7D8 +PokemonTMHMCompat=0x31E298 +PokemonEvolutions=0x324D1C +StarterPokemon=0x5AE994 +StarterItems=0xBE596 +TrainerData=0x30F9F4 +TrainerClassNames=0x30F698 +MossdeepStevenTeamOffset=0x5DA218 +MoveDescriptions=0x619038 +TmMoves=0x612730 +TmMovesDuplicate=0x612BDC +MoveTutorData=0x611BA8 +ItemImages=0x610FAC +TmPals=[0xDB5FA4, 0xDB5F04, 0xDB615C, 0xDB5FCC, 0xDB60E4, 0xDB6134, 0xDB601C, 0xDB610C, 0xDB6094, 0xDB610C, 0xDB6044, 0xDB5F54, 0xDB601C, 0xDB60BC, 0xDB5F7C, 0xDB5FF4, 0xDB5F2C, 0xDB606C] +IntroCryOffset=0x30B10 +IntroSpriteOffset=0x31928 +TradeTableOffset=0x3388CC +RunIndoorsTweakOffset=0x119DF8 +TextSpeedValuesOffset=0x60BC30 +CatchingTutorialOpponentMonOffset=0xB0884 +CatchingTutorialPlayerMonOffset=0x139082 +PCPotionOffset=0x5DCA48 +TypeEffectivenessOffset=0x31A6AC +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x211669, 0x21168E, 0x211691, 0x211713, 0x211721], Level=[0x211693]} // Lileep +StaticPokemon{}={Species=[0x21167B, 0x211731, 0x211734, 0x2117B6, 0x2117C4], Level=[0x211736]} // Anorith +StaticPokemon{}={Species=[0x23B0B6, 0x23B0C4, 0x23B119, 0x1E54AB, 0x1E552B, 0x1E55E7, 0x1E5667], Level=[0x23B0C6]} // Kyogre +StaticPokemon{}={Species=[0x23B187, 0x23B195, 0x23B1EA, 0x1E54EB, 0x1E5627], Level=[0x23B197]} // Groudon +StaticPokemon{}={Species=[0x22D8D2, 0x22D8DB, 0x22D921], Level=[0x22D8DD]} // Regirock +StaticPokemon{}={Species=[0x239092, 0x23909B, 0x2390E1], Level=[0x23909D]} // Regice +StaticPokemon{}={Species=[0x239194, 0x23919D, 0x2391E3], Level=[0x23919F]} // Registeel +StaticPokemon{}={Species=[0x23983F, 0x239848, 0x23988E, 0x2398DD, 0x2398FB, 0x1E5789, 0x1E57A7, 0x1E583D, 0x1E585B], Level=[0x23984A]} // Rayquaza +StaticPokemon{}={Species=[0x271FDB, 0x271FE4], Level=[0x271FE6]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x1F51E3, 0x1F51EC], Level=[0x1F51EE]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x23794B, 0x237952], Level=[0x23794D]} // Voltorb 1 +StaticPokemon{}={Species=[0x237998, 0x23799F], Level=[0x23799A]} // Voltorb 2 +StaticPokemon{}={Species=[0x2379E5, 0x2379EC], Level=[0x2379E7]} // Voltorb 3 +StaticPokemon{}={Species=[0x233AB2, 0x233AB9], Level=[0x233AB4]} // Electrode 1 +StaticPokemon{}={Species=[0x233AFF, 0x233B06], Level=[0x233B01]} // Electrode 2 +StaticPokemon{}={Species=[0x242D25, 0x242D33], Level=[0x242D35]} // Sudowoodo in Battle Frontier +StaticPokemon{}={Species=[0x242A9C, 0x242BB1], Level=[0x242BB6]} // Latios on Southern Island +StaticPokemon{}={Species=[0x242AA7, 0x242BC4], Level=[0x242BC9]} // Latias on Southern Island +StaticPokemon{}={Species=[0x267A89, 0x267A99, 0x267AE3, 0x267AEE], Level=[0x267A9E]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x2678A1, 0x2678DB, 0x267930, 0x26793B], Level=[0x2678E0]} // Mew on Faraway Island +StaticPokemon{}={Species=[0x268C5D, 0x268C8C, 0x268CDB, 0x268CE6], Level=[0x268C91]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x268DA5, 0x268DB0, 0x268DFA, 0x268E05], Level=[0x268DB5]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x1EA261]} // Wynaut Egg +StaticPokemon{}={Species=[0x222657, 0x22265A, 0x2226DC, 0x2226ED], Level=[0x22265C]} // Beldum +StaticPokemon{}={Species=[0x26FC1D, 0x26FC20, 0x26FCAC], Level=[0x26FC22]} // Castform +RoamingPokemon{}={Species=[0x161744], Level=[0x161776, 0x161782]} // Latios +RoamingPokemon{}={Species=[0x16174C], Level=[0x161776, 0x161782]} // Latias +CreateInitialRoamerMonFunctionStartOffset=0x161728 +ShopItemOffsets=[0x1DC9DC, 0x1DCDF8, 0x1FBDF0, 0x1FBE0C, 0x1FE0B8, 0x1FF604, 0x1FF8EC, 0x2021FC, 0x207868, 0x207894, 0x20D808, 0x210D10, 0x214BCC, 0x214BF4, 0x2173A4, 0x21F918, 0x21F94C, 0x21FA34, 0x21FA5C, 0x21FC04, 0x21FC28, 0x2221B8, 0x2265DC, 0x229518, 0x267574, 0x267E8C, 0x267EB8] +CRC32=A0AEC80A + +[Fire Red (F)] +Game=BPRF +Version=0 +Type=FRLG +CopyFrom=Fire Red (U) 1.0 +PokemonMovesets=0x257C04 +EggMoves=0x25935C +PokemonTMHMCompat=0x24D018 +PokemonEvolutions=0x253BA4 +StarterPokemon=0x169BDC +TrainerData=0x238ED4 +TrainerClassNames=0x238964 +MoveDescriptions=0x47E7DC +TmMoves=0x453BA8 +TmMovesDuplicate=0x453E10 +MoveTutorData=0x453164 +ItemImages=0x3CE114 +TmPals=[0xE91DB8, 0xE91D18, 0xE91F70, 0xE91DE0, 0xE91EF8, 0xE91F48, 0xE91E30, 0xE91F20, 0xE91EA8, 0xE91F20, 0xE91E58, 0xE91D68, 0xE91E30, 0xE91ED0, 0xE91D90, 0xE91E08, 0xE91D40, 0xE91E80] +IntroCryOffset=0x12FC44 +IntroSpriteOffset=0x1310AC +IntroOtherOffset=0x131058 +TradeTableOffset=0x2673DC +RunIndoorsTweakOffset=0xBD640 +TextSpeedValuesOffset=0x4178FC +CatchingTutorialOpponentMonOffset=0x7F8B0 +PCPotionOffset=0x3FAC28 +TypeEffectivenessOffset=0x24945C +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C49A, 0x16C49D, 0x16C4DD, 0x16C511], Level=[0x16C49F]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16EC34, 0x16EC3B], Level=[0x16ECAE]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16EC7A, 0x16EC81], Level=[0x16ECAE]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x163862, 0x163869], Level=[0x163864]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x1638C0, 0x1638C7], Level=[0x1638C2]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x1637EE, 0x1637F5, 0x163849], Level=[0x1637F0]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x1631E2, 0x1631E9, 0x16323D, 0x1700C8, 0x1700D4], Level=[0x1631E4]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163B69, 0x163B70, 0x163BC4], Level=[0x163B6B]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x16252C, 0x162540, 0x162586], Level=[0x162542]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x16806D, 0x168073, 0x160CBE, 0x160CC4], Level=[0x16806F]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x168179, 0x16817F], Level=[0x16817B]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163CDE, 0x163CE4], Level=[0x163CE6]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x16530A, 0x16531A, 0x165364, 0x16536F], Level=[0x16531F]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x165060, 0x16508F, 0x1650DE, 0x1650E9], Level=[0x165094]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x1651AE, 0x1651B9, 0x165203, 0x16520E], Level=[0x1651BE]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E80D, 0x16E811, 0x16E81C, 0x16E70E], Level=[0x16E81E]} // Old Amber +StaticPokemon{}={Species=[0x16E783, 0x16E787, 0x16E792, 0x16E692], Level=[0x16E794]} // Helix Fossil +StaticPokemon{}={Species=[0x16E7C8, 0x16E7CC, 0x16E7D7, 0x16E6D0], Level=[0x16E7D9]} // Dome Fossil +StaticPokemon{}={Species=[0x161AFA, 0x161AFD, 0x161B3F, 0x161B75], Level=[0x161AFF]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F7EB, 0x16F7EE, 0x16F8B0], Level=[0x16F7F0]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CC40, 0x16CCBC], Level=[0x16CCFF]} // Abra +StaticPokemon{}={Species=[0x16CC50, 0x16CCC7], Level=[0x16CD14]} // Clefairy +StaticPokemon{}={Species=[0x16CC70, 0x16CCDD], Level=[0x16CD3E]} // Scyther +StaticPokemon{}={Species=[0x16CC60, 0x16CCD2], Level=[0x16CD29]} // Dratini +StaticPokemon{}={Species=[0x16CC80, 0x16CCE8], Level=[0x16CD53]} // Porygon +FossilLevelOffsets=[0x16E81E, 0x16E794, 0x16E7D9] +ShopItemOffsets=[0x1649DC, 0x167708, 0x167720, 0x16773C, 0x16775C, 0x16A2C0, 0x16A730, 0x16AD00, 0x16B3B8, 0x16B6B4, 0x16BB60, 0x16BB9C, 0x16BC58, 0x16BCAC, 0x16BCE4, 0x16D540, 0x16EA70, 0x16EB1C, 0x16F004, 0x170B84, 0x1718E0, 0x171D00, 0x171EB8] +CRC32=5DC668F6 + +[Leaf Green (F)] +Game=BPGF +Version=0 +Type=FRLG +CopyFrom=Fire Red (F) +PokemonMovesets=0x257BE4 +EggMoves=0x25933C +PokemonTMHMCompat=0x24CFF4 +PokemonEvolutions=0x253B84 +StarterPokemon=0x169BB8 +TrainerData=0x238EB0 +TrainerClassNames=0x238940 +MoveDescriptions=0x47D504 +TmMoves=0x452968 +TmMovesDuplicate=0x452BD0 +MoveTutorData=0x451F24 +ItemImages=0x3CDF50 +TmPals=[0xE91E38, 0xE91D98, 0xE91FF0, 0xE91E60, 0xE91F78, 0xE91FC8, 0xE91EB0, 0xE91FA0, 0xE91F28, 0xE91FA0, 0xE91ED8, 0xE91DE8, 0xE91EB0, 0xE91F50, 0xE91E10, 0xE91E88, 0xE91DC0, 0xE91F00] +IntroCryOffset=0x12FC1C +IntroSpriteOffset=0x131084 +IntroOtherOffset=0x131030 +TradeTableOffset=0x2673BC +RunIndoorsTweakOffset=0xBD614 +TextSpeedValuesOffset=0x417738 +CatchingTutorialOpponentMonOffset=0x7F884 +PCPotionOffset=0x3FAA64 +TypeEffectivenessOffset=0x249438 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C476, 0x16C479, 0x16C4B9, 0x16C4ED], Level=[0x16C47B]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16EC10, 0x16EC17], Level=[0x16EC8A]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16EC56, 0x16EC5D], Level=[0x16EC8A]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x16383E, 0x163845], Level=[0x163840]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16389C, 0x1638A3], Level=[0x16389E]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x1637CA, 0x1637D1, 0x163825], Level=[0x1637CC]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x1631BE, 0x1631C5, 0x163219, 0x1700A4, 0x1700B0], Level=[0x1631C0]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163B45, 0x163B4C, 0x163BA0], Level=[0x163B47]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x162508, 0x16251C, 0x162562], Level=[0x16251E]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x168049, 0x16804F, 0x160C9A, 0x160CA0], Level=[0x16804B]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x168155, 0x16815B], Level=[0x168157]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163CBA, 0x163CC0], Level=[0x163CC2]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x1652E6, 0x1652F6, 0x165340, 0x16534B], Level=[0x1652FB]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x16503C, 0x16506B, 0x1650BA, 0x1650C5], Level=[0x165070]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x16518A, 0x165195, 0x1651DF, 0x1651EA], Level=[0x16519A]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E7E9, 0x16E7ED, 0x16E7F8, 0x16E6EA], Level=[0x16E7FA]} // Old Amber +StaticPokemon{}={Species=[0x16E75F, 0x16E763, 0x16E76E, 0x16E66E], Level=[0x16E770]} // Helix Fossil +StaticPokemon{}={Species=[0x16E7A4, 0x16E7A8, 0x16E7B3, 0x16E6AC], Level=[0x16E7B5]} // Dome Fossil +StaticPokemon{}={Species=[0x161AD6, 0x161AD9, 0x161B1B, 0x161B51], Level=[0x161ADB]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F7C7, 0x16F7CA, 0x16F88C], Level=[0x16F7CC]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CC1C, 0x16CC98], Level=[0x16CCDB]} // Abra +StaticPokemon{}={Species=[0x16CC2C, 0x16CCA3], Level=[0x16CCF0]} // Clefairy +StaticPokemon{}={Species=[0x16CC3C, 0x16CCCF], Level=[0x16CD44]} // Pinsir +StaticPokemon{}={Species=[0x16CC4C, 0x16CCAE], Level=[0x16CD05]} // Dratini +StaticPokemon{}={Species=[0x16CC5C, 0x16CCC4], Level=[0x16CD2F]} // Porygon +FossilLevelOffsets=[0x16E7FA, 0x16E770, 0x16E7B5] +ShopItemOffsets=[0x1649B8, 0x1676E4, 0x1676FC, 0x167718, 0x167738, 0x16A29C, 0x16A70C, 0x16ACDC, 0x16B394, 0x16B690, 0x16BB3C, 0x16BB78, 0x16BC34, 0x16BC88, 0x16BCC0, 0x16D51C, 0x16EA4C, 0x16EAF8, 0x16EFE0, 0x170B60, 0x1718BC, 0x171CDC, 0x171E94] +CRC32=BA3285E3 + +[Fire Red (G)] +Game=BPRD +Version=0 +Type=FRLG +CopyFrom=Fire Red (U) 1.0 +PokemonMovesets=0x25D6D8 +EggMoves=0x25EE30 +PokemonTMHMCompat=0x252AEC +PokemonEvolutions=0x259678 +StarterPokemon=0x169B20 +TrainerData=0x23E998 +TrainerClassNames=0x23E428 +MoveDescriptions=0x486BEC +TmMoves=0x45B670 +TmMovesDuplicate=0x45B8D8 +MoveTutorData=0x45AC2C +ItemImages=0x3D3BE8 +TmPals=[0xE91E84, 0xE91DE4, 0xE9203C, 0xE91EAC, 0xE91FC4, 0xE92014, 0xE91EFC, 0xE91FEC, 0xE91F74, 0xE91FEC, 0xE91F24, 0xE91E34, 0xE91EFC, 0xE91F9C, 0xE91E5C, 0xE91ED4, 0xE91E0C, 0xE91F4C] +IntroCryOffset=0x12FB88 +IntroSpriteOffset=0x130FF0 +IntroOtherOffset=0x130F9C +TradeTableOffset=0x26CEB0 +RunIndoorsTweakOffset=0xBD580 +TextSpeedValuesOffset=0x41F764 +CatchingTutorialOpponentMonOffset=0x7F7F0 +PCPotionOffset=0x402224 +TypeEffectivenessOffset=0x24EF20 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C3DE, 0x16C3E1, 0x16C421, 0x16C455], Level=[0x16C3E3]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16EB78, 0x16EB7F], Level=[0x16EBF2]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16EBBE, 0x16EBC5], Level=[0x16EBF2]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x1637A6, 0x1637AD], Level=[0x1637A8]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x163804, 0x16380B], Level=[0x163806]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x163732, 0x163739, 0x16378D], Level=[0x163734]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x163126, 0x16312D, 0x163181, 0x17000C, 0x170018], Level=[0x163128]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163AAD, 0x163AB4, 0x163B08], Level=[0x163AAF]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x162470, 0x162484, 0x1624CA], Level=[0x162486]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x167FB1, 0x167FB7, 0x160C02, 0x160C08], Level=[0x167FB3]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x1680BD, 0x1680C3], Level=[0x1680BF]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163C22, 0x163C28], Level=[0x163C2A]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x16524E, 0x16525E, 0x1652A8, 0x1652B3], Level=[0x165263]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x164FA4, 0x164FD3, 0x165022, 0x16502D], Level=[0x164FD8]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x1650F2, 0x1650FD, 0x165147, 0x165152], Level=[0x165102]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E751, 0x16E755, 0x16E760, 0x16E652], Level=[0x16E762]} // Old Amber +StaticPokemon{}={Species=[0x16E6C7, 0x16E6CB, 0x16E6D6, 0x16E5D6], Level=[0x16E6D8]} // Helix Fossil +StaticPokemon{}={Species=[0x16E70C, 0x16E710, 0x16E71B, 0x16E614], Level=[0x16E71D]} // Dome Fossil +StaticPokemon{}={Species=[0x161A3E, 0x161A41, 0x161A83, 0x161AB9], Level=[0x161A43]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F72F, 0x16F732, 0x16F7F4], Level=[0x16F734]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CB84, 0x16CC00], Level=[0x16CC43]} // Abra +StaticPokemon{}={Species=[0x16CB94, 0x16CC0B], Level=[0x16CC58]} // Clefairy +StaticPokemon{}={Species=[0x16CBB4, 0x16CC21], Level=[0x16CC82]} // Scyther +StaticPokemon{}={Species=[0x16CBA4, 0x16CC16], Level=[0x16CC6D]} // Dratini +StaticPokemon{}={Species=[0x16CBC4, 0x16CC2C], Level=[0x16CC97]} // Porygon +FossilLevelOffsets=[0x16E762, 0x16E6D8, 0x16E71D] +ShopItemOffsets=[0x164920, 0x16764C, 0x167664, 0x167680, 0x1676A0, 0x16A204, 0x16A674, 0x16AC44, 0x16B2FC, 0x16B5F8, 0x16BAA4, 0x16BAE0, 0x16BB9C, 0x16BBF0, 0x16BC28, 0x16D484, 0x16E9B4, 0x16EA60, 0x16EF48, 0x170AC8, 0x171824, 0x171C44, 0x171DFC] +CRC32=1A81EEDF + +[Leaf Green (G)] +Game=BPGD +Version=0 +Type=FRLG +CopyFrom=Fire Red (G) +PokemonMovesets=0x25D6B8 +EggMoves=0x25EE10 +PokemonTMHMCompat=0x252AC8 +PokemonEvolutions=0x259658 +StarterPokemon=0x169AFC +TrainerData=0x23E974 +TrainerClassNames=0x23E404 +MoveDescriptions=0x485D58 +TmMoves=0x45A874 +TmMovesDuplicate=0x45AADC +MoveTutorData=0x459E30 +ItemImages=0x3D3A24 +TmPals=[0xE91F04, 0xE91E64, 0xE920BC, 0xE91F2C, 0xE92044, 0xE92094, 0xE91F7C, 0xE9206C, 0xE91FF4, 0xE9206C, 0xE91FA4, 0xE91EB4, 0xE91F7C, 0xE9201C, 0xE91EDC, 0xE91F54, 0xE91E8C, 0xE91FCC] +IntroCryOffset=0x12FB60 +IntroSpriteOffset=0x130FC8 +IntroOtherOffset=0x130F74 +TradeTableOffset=0x26CE90 +RunIndoorsTweakOffset=0xBD554 +TextSpeedValuesOffset=0x41F5A0 +CatchingTutorialOpponentMonOffset=0x7F7C4 +PCPotionOffset=0x402060 +TypeEffectivenessOffset=0x24EEFC +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C3BA, 0x16C3BD, 0x16C3FD, 0x16C431], Level=[0x16C3BF]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16EB54, 0x16EB5B], Level=[0x16EBCE]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16EB9A, 0x16EBA1], Level=[0x16EBCE]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x163782, 0x163789], Level=[0x163784]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x1637E0, 0x1637E7], Level=[0x1637E2]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16370E, 0x163715, 0x163769], Level=[0x163710]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x163102, 0x163109, 0x16315D, 0x16FFE8, 0x16FFF4], Level=[0x163104]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163A89, 0x163A90, 0x163AE4], Level=[0x163A8B]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x16244C, 0x162460, 0x1624A6], Level=[0x162462]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x167F8D, 0x167F93, 0x160BDE, 0x160BE4], Level=[0x167F8F]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x168099, 0x16809F], Level=[0x16809B]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163BFE, 0x163C04], Level=[0x163C06]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x16522A, 0x16523A, 0x165284, 0x16528F], Level=[0x16523F]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x164F80, 0x164FAF, 0x164FFE, 0x165009], Level=[0x164FB4]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x1650CE, 0x1650D9, 0x165123, 0x16512E], Level=[0x1650DE]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E72D, 0x16E731, 0x16E73C, 0x16E62E], Level=[0x16E73E]} // Old Amber +StaticPokemon{}={Species=[0x16E6A3, 0x16E6A7, 0x16E6B2, 0x16E5B2], Level=[0x16E6B4]} // Helix Fossil +StaticPokemon{}={Species=[0x16E6E8, 0x16E6EC, 0x16E6F7, 0x16E5F0], Level=[0x16E6F9]} // Dome Fossil +StaticPokemon{}={Species=[0x161A1A, 0x161A1D, 0x161A5F, 0x161A95], Level=[0x161A1F]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F70B, 0x16F70E, 0x16F7D0], Level=[0x16F710]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CB60, 0x16CBDC], Level=[0x16CC1F]} // Abra +StaticPokemon{}={Species=[0x16CB70, 0x16CBE7], Level=[0x16CC34]} // Clefairy +StaticPokemon{}={Species=[0x16CB80, 0x16CC13], Level=[0x16CC88]} // Pinsir +StaticPokemon{}={Species=[0x16CB90, 0x16CBF2], Level=[0x16CC49]} // Dratini +StaticPokemon{}={Species=[0x16CBA0, 0x16CC08], Level=[0x16CC73]} // Porygon +FossilLevelOffsets=[0x16E73E, 0x16E6B4, 0x16E6B4] +ShopItemOffsets=[0x1648FC, 0x167628, 0x167640, 0x16765C, 0x16767C, 0x16A1E0, 0x16A650, 0x16AC20, 0x16B2D8, 0x16B5D4, 0x16BA80, 0x16BABC, 0x16BB78, 0x16BBCC, 0x16BC04, 0x16D460, 0x16E990, 0x16EA3C, 0x16EF24, 0x170AA4, 0x171800, 0x171C20, 0x171DD8] +CRC32=D12F1FDD + +[Fire Red (S)] +Game=BPRS +Version=0 +Type=FRLG +CopyFrom=Fire Red (U) 1.0 +PokemonMovesets=0x258F7C +EggMoves=0x25A6D4 +PokemonTMHMCompat=0x24E390 +PokemonEvolutions=0x254F1C +StarterPokemon=0x169C4C +TrainerData=0x23A234 +TrainerClassNames=0x239CC4 +MoveDescriptions=0x47EF78 +TmMoves=0x454C1C +TmMovesDuplicate=0x454E84 +MoveTutorData=0x4541D8 +ItemImages=0x3CF48C +TmPals=[0xE91D98, 0xE91CF8, 0xE91F50, 0xE91DC0, 0xE91ED8, 0xE91F28, 0xE91E10, 0xE91F00, 0xE91E88, 0xE91F00, 0xE91E38, 0xE91D48, 0xE91E10, 0xE91EB0, 0xE91D70, 0xE91DE8, 0xE91D20, 0xE91E60] +IntroCryOffset=0x12FCB4 +IntroSpriteOffset=0x13111C +IntroOtherOffset=0x1310C8 +TradeTableOffset=0x268754 +RunIndoorsTweakOffset=0xBD648 +TextSpeedValuesOffset=0x41A090 +CatchingTutorialOpponentMonOffset=0x7F8C4 +PCPotionOffset=0x3FCB9C +TypeEffectivenessOffset=0x24A7BC +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C50A, 0x16C50D, 0x16C54D, 0x16C581], Level=[0x16C50F]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16ECA4, 0x16ECAB], Level=[0x16ED1E]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16ECEA, 0x16ECF1], Level=[0x16ED1E]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x1638D2, 0x1638D9], Level=[0x1638D4]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x163930, 0x163937], Level=[0x163932]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16385E, 0x163865, 0x1638B9], Level=[0x163860]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x163252, 0x163259, 0x1632AD, 0x170138, 0x170144], Level=[0x163254]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163BD9, 0x163BE0, 0x163C34], Level=[0x163BDB]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x16259C, 0x1625B0, 0x1625F6], Level=[0x1625B2]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x1680DD, 0x1680E3, 0x160D2E, 0x160D34], Level=[0x1680DF]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x1681E9, 0x1681EF], Level=[0x1681EB]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163D4E, 0x163D54], Level=[0x163D56]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x16537A, 0x16538A, 0x1653D4, 0x1653DF], Level=[0x16538F]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x1650D0, 0x1650FF, 0x16514E, 0x165159], Level=[0x165104]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x16521E, 0x165229, 0x165273, 0x16527E], Level=[0x16522E]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E87D, 0x16E881, 0x16E88C, 0x16E77E], Level=[0x16E88E]} // Old Amber +StaticPokemon{}={Species=[0x16E7F3, 0x16E7F7, 0x16E802, 0x16E702], Level=[0x16E804]} // Helix Fossil +StaticPokemon{}={Species=[0x16E838, 0x16E83C, 0x16E847, 0x16E740], Level=[0x16E849]} // Dome Fossil +StaticPokemon{}={Species=[0x161B6A, 0x161B6D, 0x161BAF, 0x161BE5], Level=[0x161B6F]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F85B, 0x16F85E, 0x16F920], Level=[0x16F860]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CCB0, 0x16CD2C], Level=[0x16CD6F]} // Abra +StaticPokemon{}={Species=[0x16CCC0, 0x16CD37], Level=[0x16CD84]} // Clefairy +StaticPokemon{}={Species=[0x16CCE0, 0x16CD4D], Level=[0x16CDAE]} // Scyther +StaticPokemon{}={Species=[0x16CCD0, 0x16CD42], Level=[0x16CD99]} // Dratini +StaticPokemon{}={Species=[0x16CCF0, 0x16CD58], Level=[0x16CDC3]} // Porygon +FossilLevelOffsets=[0x16E88E, 0x16E804, 0x16E849] +ShopItemOffsets=[0x164A4C, 0x167778, 0x167790, 0x1677AC, 0x1677CC, 0x16A330, 0x16A7A0, 0x16AD70, 0x16B428, 0x16B724, 0x16BBD0, 0x16BC0C, 0x16BCC8, 0x16BD1C, 0x16BD54, 0x16D5B0, 0x16EAE0, 0x16EB8C, 0x16F074, 0x170BF4, 0x171950, 0x171D70, 0x171F28] +CRC32=9F08064E + +[Leaf Green (S)] +Game=BPGS +Version=0 +Type=FRLG +CopyFrom=Fire Red (S) +PokemonMovesets=0x258F5C +EggMoves=0x25A6B4 +PokemonTMHMCompat=0x24E36C +PokemonEvolutions=0x254EFC +StarterPokemon=0x169C28 +TrainerData=0x23A210 +TrainerClassNames=0x239CA0 +MoveDescriptions=0x47E670 +TmMoves=0x4543AC +TmMovesDuplicate=0x454614 +MoveTutorData=0x453968 +ItemImages=0x3CF2C8 +TmPals=[0xE91E18, 0xE91D78, 0xE91FD0, 0xE91E40, 0xE91F58, 0xE91FA8, 0xE91E90, 0xE91F80, 0xE91F08, 0xE91F80, 0xE91EB8, 0xE91DC8, 0xE91E90, 0xE91F30, 0xE91DF0, 0xE91E68, 0xE91DA0, 0xE91EE0] +IntroCryOffset=0x12FC8C +IntroSpriteOffset=0x1310F4 +IntroOtherOffset=0x1310A0 +TradeTableOffset=0x268734 +RunIndoorsTweakOffset=0xBD61C +TextSpeedValuesOffset=0x419ECC +CatchingTutorialOpponentMonOffset=0x7F898 +PCPotionOffset=0x3FC9D8 +TypeEffectivenessOffset=0x24A798 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C4E6, 0x16C4E9, 0x16C529, 0x16C55D], Level=[0x16C4EB]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16EC80, 0x16EC87], Level=[0x16ECFA]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16ECC6, 0x16ECCD], Level=[0x16ECFA]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x1638AE, 0x1638B5], Level=[0x1638B0]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16390C, 0x163913], Level=[0x16390E]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16383A, 0x163841, 0x163895], Level=[0x16383C]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x16322E, 0x163235, 0x163289, 0x170114, 0x170120], Level=[0x163230]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163BB5, 0x163BBC, 0x163C10], Level=[0x163BB7]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x162578, 0x16258C, 0x1625D2], Level=[0x16258E]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x1680B9, 0x1680BF, 0x160D0A, 0x160D10], Level=[0x1680BB]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x1681C5, 0x1681CB], Level=[0x1681C7]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163D2A, 0x163D30], Level=[0x163D32]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x165356, 0x165366, 0x1653B0, 0x1653BB], Level=[0x16536B]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x1650AC, 0x1650DB, 0x16512A, 0x165135], Level=[0x1650E0]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x1651FA, 0x165205, 0x16524F, 0x16525A], Level=[0x16520A]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E859, 0x16E85D, 0x16E868, 0x16E75A], Level=[0x16E86A]} // Old Amber +StaticPokemon{}={Species=[0x16E7CF, 0x16E7D3, 0x16E7DE, 0x16E6DE], Level=[0x16E7E0]} // Helix Fossil +StaticPokemon{}={Species=[0x16E814, 0x16E818, 0x16E823, 0x16E71C], Level=[0x16E825]} // Dome Fossil +StaticPokemon{}={Species=[0x161B46, 0x161B49, 0x161B8B, 0x161BC1], Level=[0x161B4B]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F837, 0x16F83A, 0x16F8FC], Level=[0x16F83C]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CC8C, 0x16CD08], Level=[0x16CD4B]} // Abra +StaticPokemon{}={Species=[0x16CC9C, 0x16CD13], Level=[0x16CD60]} // Clefairy +StaticPokemon{}={Species=[0x16CCAC, 0x16CD3F], Level=[0x16CDB4]} // Pinsir +StaticPokemon{}={Species=[0x16CCBC, 0x16CD1E], Level=[0x16CD75]} // Dratini +StaticPokemon{}={Species=[0x16CCCC, 0x16CD34], Level=[0x16CD9F]} // Porygon +FossilLevelOffsets=[0x16E86A, 0x16E7E0, 0x16E825] +ShopItemOffsets=[0x164A28, 0x167754, 0x16776C, 0x167788, 0x1677A8, 0x16A30C, 0x16A77C, 0x16AD4C, 0x16B404, 0x16B700, 0x16BBAC, 0x16BBE8, 0x16BCA4, 0x16BCF8, 0x16BD30, 0x16D58C, 0x16EABC, 0x16EB68, 0x16F050, 0x170BD0, 0x17192C, 0x171D4C, 0x171F04] +CRC32=2CA11D59 + +[Fire Red (I)] +Game=BPRI +Version=0 +Type=FRLG +CopyFrom=Fire Red (U) 1.0 +PokemonMovesets=0x256894 +EggMoves=0x257FEC +PokemonTMHMCompat=0x24BCA8 +PokemonEvolutions=0x252834 +StarterPokemon=0x169B60 +TrainerData=0x237B6C +TrainerClassNames=0x2375FC +MoveDescriptions=0x47C174 +TmMoves=0x451580 +TmMovesDuplicate=0x4517E8 +MoveTutorData=0x450B3C +ItemImages=0x3CCDA4 +TmPals=[0xE91DD4, 0xE91D34, 0xE91F8C, 0xE91DFC, 0xE91F14, 0xE91F64, 0xE91E4C, 0xE91F3C, 0xE91EC4, 0xE91F3C, 0xE91E74, 0xE91D84, 0xE91E4C, 0xE91EEC, 0xE91DAC, 0xE91E24, 0xE91D5C, 0xE91E9C] +IntroCryOffset=0x12FBC8 +IntroSpriteOffset=0x131030 +IntroOtherOffset=0x130FDC +TradeTableOffset=0x26606C +RunIndoorsTweakOffset=0xBD560 +TextSpeedValuesOffset=0x4169E8 +CatchingTutorialOpponentMonOffset=0x7F7DC +PCPotionOffset=0x3F99B0 +TypeEffectivenessOffset=0x2480F4 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C41E, 0x16C421, 0x16C461, 0x16C495], Level=[0x16C423]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16EBB8, 0x16EBBF], Level=[0x16EC32]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16EBFE, 0x16EC05], Level=[0x16EC32]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x1637E6, 0x1637ED], Level=[0x1637E8]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x163844, 0x16384B], Level=[0x163846]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x163772, 0x163779, 0x1637CD], Level=[0x163774]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x163166, 0x16316D, 0x1631C1, 0x17004C, 0x170058], Level=[0x163168]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163AED, 0x163AF4, 0x163B48], Level=[0x163AEF]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x1624B0, 0x1624C4, 0x16250A], Level=[0x1624C6]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x167FF1, 0x167FF7, 0x160C42, 0x160C48], Level=[0x167FF3]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x1680FD, 0x168103], Level=[0x1680FF]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163C62, 0x163C68], Level=[0x163C6A]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x16528E, 0x16529E, 0x1652E8, 0x1652F3], Level=[0x1652A3]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x164FE4, 0x165013, 0x165062, 0x16506D], Level=[0x165018]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x165132, 0x16513D, 0x165187, 0x165192], Level=[0x165142]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E791, 0x16E795, 0x16E7A0, 0x16E692], Level=[0x16E7A2]} // Old Amber +StaticPokemon{}={Species=[0x16E707, 0x16E70B, 0x16E716, 0x16E616], Level=[0x16E718]} // Helix Fossil +StaticPokemon{}={Species=[0x16E74C, 0x16E750, 0x16E75B, 0x16E654], Level=[0x16E75D]} // Dome Fossil +StaticPokemon{}={Species=[0x161A7E, 0x161A81, 0x161AC3, 0x161AF9], Level=[0x161A83]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F76F, 0x16F772, 0x16F834], Level=[0x16F774]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CBC4, 0x16CC40], Level=[0x16CC83]} // Abra +StaticPokemon{}={Species=[0x16CBD4, 0x16CC4B], Level=[0x16CC98]} // Clefairy +StaticPokemon{}={Species=[0x16CBF4, 0x16CC61], Level=[0x16CCC2]} // Scyther +StaticPokemon{}={Species=[0x16CBE4, 0x16CC56], Level=[0x16CCAD]} // Dratini +StaticPokemon{}={Species=[0x16CC04, 0x16CC6C], Level=[0x16CCD7]} // Porygon +FossilLevelOffsets=[0x16E7A2, 0x16E718, 0x16E75D] +ShopItemOffsets=[0x164960, 0x16768C, 0x1676A4, 0x1676C0, 0x1676E0, 0x16A244, 0x16A6B4, 0x16AC84, 0x16B33C, 0x16B638, 0x16BAE4, 0x16BB20, 0x16BBDC, 0x16BC30, 0x16BC68, 0x16D4C4, 0x16E9F4, 0x16EAA0, 0x16EF88, 0x170B08, 0x171864, 0x171C84, 0x171E3C] +CRC32=73A72167 + +[Leaf Green (I)] +Game=BPGI +Version=0 +Type=FRLG +CopyFrom=Fire Red (I) +PokemonMovesets=0x256874 +EggMoves=0x257FCC +PokemonTMHMCompat=0x24BC84 +PokemonEvolutions=0x252814 +StarterPokemon=0x169B3C +TrainerData=0x237B48 +TrainerClassNames=0x2375D8 +MoveDescriptions=0x47B90C +TmMoves=0x450DB0 +TmMovesDuplicate=0x451018 +MoveTutorData=0x45036C +ItemImages=0x3CCBE0 +TmPals=[0xE91E54, 0xE91DB4, 0xE9200C, 0xE91E7C, 0xE91F94, 0xE91FE4, 0xE91ECC, 0xE91FBC, 0xE91F44, 0xE91FBC, 0xE91EF4, 0xE91E04, 0xE91ECC, 0xE91F6C, 0xE91E2C, 0xE91EA4, 0xE91DDC, 0xE91F1C] +IntroCryOffset=0x12FBA0 +IntroSpriteOffset=0x131008 +IntroOtherOffset=0x130FB4 +TradeTableOffset=0x26604C +RunIndoorsTweakOffset=0xBD534 +TextSpeedValuesOffset=0x416824 +CatchingTutorialOpponentMonOffset=0x7F7B0 +PCPotionOffset=0x3F97EC +TypeEffectivenessOffset=0x2480D0 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x16C3FA, 0x16C3FD, 0x16C43D, 0x16C471], Level=[0x16C3FF]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x16EB94, 0x16EB9B], Level=[0x16EC0E]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x16EBDA, 0x16EBE1], Level=[0x16EC0E]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x1637C2, 0x1637C9], Level=[0x1637C4]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x163820, 0x163827], Level=[0x163822]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16374E, 0x163755, 0x1637A9], Level=[0x163750]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x163142, 0x163149, 0x16319D, 0x170028, 0x170034], Level=[0x163144]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x163AC9, 0x163AD0, 0x163B24], Level=[0x163ACB]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x16248C, 0x1624A0, 0x1624E6], Level=[0x1624A2]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x167FCD, 0x167FD3, 0x160C1E, 0x160C24], Level=[0x167FCF]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x1680D9, 0x1680DF], Level=[0x1680DB]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x163C3E, 0x163C44], Level=[0x163C46]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x16526A, 0x16527A, 0x1652C4, 0x1652CF], Level=[0x16527F]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x164FC0, 0x164FEF, 0x16503E, 0x165049], Level=[0x164FF4]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x16510E, 0x165119, 0x165163, 0x16516E], Level=[0x16511E]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x16E76D, 0x16E771, 0x16E77C, 0x16E66E], Level=[0x16E77E]} // Old Amber +StaticPokemon{}={Species=[0x16E6E3, 0x16E6E7, 0x16E6F2, 0x16E5F2], Level=[0x16E6F4]} // Helix Fossil +StaticPokemon{}={Species=[0x16E728, 0x16E72C, 0x16E737, 0x16E630], Level=[0x16E739]} // Dome Fossil +StaticPokemon{}={Species=[0x161A5A, 0x161A5D, 0x161A9F, 0x161AD5], Level=[0x161A5F]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x16F74B, 0x16F74E, 0x16F810], Level=[0x16F750]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x16CBA0, 0x16CC1C], Level=[0x16CC5F]} // Abra +StaticPokemon{}={Species=[0x16CBB0, 0x16CC27], Level=[0x16CC74]} // Clefairy +StaticPokemon{}={Species=[0x16CBC0, 0x16CC53], Level=[0x16CCC8]} // Pinsir +StaticPokemon{}={Species=[0x16CBD0, 0x16CC32], Level=[0x16CC89]} // Dratini +StaticPokemon{}={Species=[0x16CBE0, 0x16CC48], Level=[0x16CCB3]} // Porygon +FossilLevelOffsets=[0x16E77E, 0x16E6F4, 0x16E739] +ShopItemOffsets=[0x16493C, 0x167668, 0x167680, 0x16769C, 0x1676BC, 0x16A220, 0x16A690, 0x16AC60, 0x16B318, 0x16B614, 0x16BAC0, 0x16BAFC, 0x16BBB8, 0x16BC0C, 0x16BC44, 0x16D4A0, 0x16E9D0, 0x16EA7C, 0x16EF64, 0x170AE4, 0x171840, 0x171C60, 0x171E18] +CRC32=16974506 + +[Ruby (J)] +Game=AXVJ +Version=0 +Type=Ruby +TableFile=gba_jpn +FreeSpace=0x661000 +PokemonCount=411 +PokemonNameLength=6 +PokemonStats=0x1D09CC +PokemonMovesets=0x1D997C +EggMoves=0x1DAF78 +PokemonTMHMCompat=0x1CEEA4 +PokemonEvolutions=0x1D591C +StarterPokemon=0x3D25C8 +StarterItems=0x7F072 +TrainerData=0x1C4C94 +TrainerEntrySize=32 +TrainerCount=0x2B6 +TrainerClassNames=0x1C4A14 +TrainerClassCount=58 +TrainerClassNameLength=11 +TrainerNameLength=6 +DoublesTrainerClasses=[27, 42, 55, 56, 57] +EliteFourIndices=[261, 262, 263, 264, 335] +ItemData=0x39A648 +ItemEntrySize=40 +ItemCount=348 +MoveCount=354 +MoveData=0x1CCEE0 +MoveNameLength=8 +MoveNames=0x1CACFC +AbilityNameLength=8 +AbilityNames=0x1CBC44 +TmMoves=0x35017C +IntroCryOffset=0x7AAA +IntroSpriteOffset=0x8838 +IntroPaletteOffset=0x8844 +IntroOtherOffset=0x8806 +PokemonFrontSprites=0x1BCB60 +PokemonNormalPalettes=0x1BEDC0 +ItemBallPic=59 +TradeTableOffset=0x1E9C48 +TradeTableSize=3 +TradesUnused=[] +RunIndoorsTweakOffset=0xE0D88 +CatchingTutorialOpponentMonOffset=0x7E9D8 +CatchingTutorialPlayerMonOffset=0x10A606 +PCPotionOffset=0x3E177C +PickupTableStartLocator=16001E00170028000200320044003C +PickupItemCount=10 +TypeEffectivenessOffset=0x1CB9BC +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x162B23, 0x162B50], Level=[0x162B52]} // Lileep +StaticPokemon{}={Species=[0x162B75, 0x162BA2], Level=[0x162BA4]} // Anorith +StaticPokemon{}={Species=[0x174A01, 0x1800AD, 0x1749FA], Level=[0x1749FC]} // Groudon +StaticPokemon{}={Species=[0x171D3A, 0x171D43], Level=[0x171D45]} // Regirock +StaticPokemon{}={Species=[0x1767CC, 0x1767D5], Level=[0x1767D7]} // Regice +StaticPokemon{}={Species=[0x17687F, 0x176888], Level=[0x17688A]} // Registeel +StaticPokemon{}={Species=[0x179434, 0x17945A], Level=[0x17945C]} // Latias (Southern Island) +StaticPokemon{}={Species=[0x176B44, 0x176B4B], Level=[0x176B46]} // Rayquaza +StaticPokemon{}={Species=[0x1801EC, 0x1801F5], Level=[0x1801F7]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x153337, 0x153340], Level=[0x153342]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x175ADB, 0x175AE2], Level=[0x175ADD]} // Voltorb 1 +StaticPokemon{}={Species=[0x175AF9, 0x175B00], Level=[0x175AFB]} // Voltorb 2 +StaticPokemon{}={Species=[0x175B17, 0x175B1E], Level=[0x175B19]} // Voltorb 3 +StaticPokemon{}={Species=[0x18010A, 0x180111], Level=[0x18010C]} // Electrode 1 +StaticPokemon{}={Species=[0x180128, 0x18012F], Level=[0x18012A]} // Electrode 2 +StaticPokemon{}={Species=[0x14CF1E]} // Wynaut Egg +StaticPokemon{}={Species=[0x16C023, 0x16C033], Level=[0x16C025]} // Beldum +StaticPokemon{}={Species=[0x17E914], Level=[0x17E916]} // Castform +RoamingPokemon{}={Species=[0x10B89C, 0x12F07C], Level=[0x12EFE0, 0x12EFF0]} // Latios +StaticEggPokemonOffsets=[15] +FindMapsWithMonFunctionStartOffset=0x10B81C +CreateInitialRoamerMonFunctionStartOffset=0x12EFC4 +ShopItemOffsets=[0x146004, 0x1463E4, 0x1566AC, 0x1566C8, 0x157954, 0x158280, 0x158448, 0x15932C, 0x15CA14, 0x15CA40, 0x160330, 0x162680, 0x1642EC, 0x164314, 0x1653B4, 0x16A694, 0x16A6C8, 0x16A78C, 0x16A7B4, 0x16A900, 0x16A924, 0x16BCF0, 0x16D828, 0x16F184] +SkipShops=[1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19, 20, 21, 22, 23] +MainGameShops=[0, 4, 17, 18] +CRC32=CEE9471A + +[Sapphire (J)] +Game=AXPJ +Version=0 +Type=Sapp +CopyFrom=Ruby (J) +PokemonStats=0x1D095C +PokemonMovesets=0x1D990C +EggMoves=0x1DAF08 +PokemonTMHMCompat=0x1CEE34 +PokemonEvolutions=0x1D58AC +StarterPokemon=0x3D25AC +TrainerData=0x1C4C24 +TrainerClassNames=0x1C49A4 +ItemData=0x39A62C +MoveData=0x1CCE70 +MoveNames=0x1CAC8C +AbilityNames=0x1CBBD4 +TmMoves=0x35010C +PokemonFrontSprites=0x1BCAF0 +PokemonNormalPalettes=0x1BED50 +TradeTableOffset=0x1E9BD8 +RunIndoorsTweakOffset=0xE0D88 +PCPotionOffset=0x3E1760 +TypeEffectivenessOffset=0x1CB94C +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x162AB3, 0x162AE0], Level=[0x162AE2]} // Lileep +StaticPokemon{}={Species=[0x162B05, 0x162B32], Level=[0x162B34]} // Anorith +StaticPokemon{}={Species=[0x174991, 0x18003D, 0x17498A], Level=[0x17498C]} // Kyogre +StaticPokemon{}={Species=[0x171CCA, 0x171CD3], Level=[0x171CD5]} // Regirock +StaticPokemon{}={Species=[0x17675C, 0x176765], Level=[0x176767]} // Regice +StaticPokemon{}={Species=[0x17680F, 0x176818], Level=[0x17681A]} // Registeel +StaticPokemon{}={Species=[0x1793C4, 0x1793EA], Level=[0x1793EC]} // Latios (Southern Island) +StaticPokemon{}={Species=[0x176AD4, 0x176ADB], Level=[0x176AD6]} // Rayquaza +StaticPokemon{}={Species=[0x18017C, 0x180185], Level=[0x180187]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x1532CB, 0x1532D4], Level=[0x1532D6]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x175A6B, 0x175A72], Level=[0x175A6D]} // Voltorb 1 +StaticPokemon{}={Species=[0x175A89, 0x175A90], Level=[0x175A8B]} // Voltorb 2 +StaticPokemon{}={Species=[0x175AA7, 0x175AAE], Level=[0x175AA9]} // Voltorb 3 +StaticPokemon{}={Species=[0x18009A, 0x1800A1], Level=[0x18009C]} // Electrode 1 +StaticPokemon{}={Species=[0x1800B8, 0x1800BF], Level=[0x1800BA]} // Electrode 2 +StaticPokemon{}={Species=[0x14CEB2]} // Wynaut Egg +StaticPokemon{}={Species=[0x16BFB3, 0x16BFC3], Level=[0x16BFB5]} // Beldum +StaticPokemon{}={Species=[0x17E8A4], Level=[0x17E8A6]} // Castform +RoamingPokemon{}={Species=[0x10B898, 0x12F080], Level=[0x12EFDE, 0x12EFEE]} // Latias +ShopItemOffsets=[0x146004, 0x1463E4, 0x15663C, 0x156658, 0x1578E4, 0x158210, 0x1583D8, 0x1592BC, 0x15C9A4, 0x15C9D0, 0x1602C0, 0x162610, 0x16427C, 0x1642A4, 0x165344, 0x16A624, 0x16A658, 0x16A71C, 0x16A744, 0x16A890, 0x16A8B4, 0x16BC80, 0x16D7B8, 0x16F114] +CRC32=FD1EEB78 + +[Emerald (J)] +Game=BPEJ +Version=0 +Type=Em +TableFile=gba_jpn +FreeSpace=0xE40000 +PokemonCount=411 +PokemonNameLength=6 +PokemonMovesets=0x2F9D04 +EggMoves=0x2FB764 +PokemonTMHMCompat=0x2EF220 +PokemonEvolutions=0x2F5CA4 +StarterPokemon=0x590C08 +StarterItems=0xB0A66 +TrainerData=0x2E383C +TrainerEntrySize=32 +TrainerCount=0x357 +TrainerClassNames=0x2E3564 +TrainerClassCount=66 +TrainerClassNameLength=11 +TrainerNameLength=6 +DoublesTrainerClasses=[34, 46, 55, 56, 57] +EliteFourIndices=[261, 262, 263, 264, 335] +MossdeepStevenTeamOffset=0x5BC614 +ItemEntrySize=40 +ItemCount=376 +MoveCount=354 +MoveNameLength=8 +AbilityNameLength=8 +TmMoves=0x5E144C +TmMovesDuplicate=0x5E18F8 +MoveTutorData=0x5E08C4 +MoveTutorMoves=30 +ItemImages=0x5DFCC8 +TmPals=[0xDB613C, 0xDB609C, 0xDB62F4, 0xDB6164, 0xDB627C, 0xDB62CC, 0xDB61B4, 0xDB62A4, 0xDB622C, 0xDB62A4, 0xDB61DC, 0xDB60EC, 0xDB61B4, 0xDB6254, 0xDB6114, 0xDB618C, 0xDB60C4, 0xDB6204] +IntroCryOffset=0x3084C +IntroSpriteOffset=0x31664 +ItemBallPic=59 +TradeTableOffset=0x30D114 +TradeTableSize=4 +TradesUnused=[] +RunIndoorsTweakOffset=0x11AA38 +TextSpeedValuesOffset=0x5D7B24 +CatchingTutorialOpponentMonOffset=0xB016C +CatchingTutorialPlayerMonOffset=0x1394E2 +PCPotionOffset=0x5C0BE0 +PickupTableStartLocator=0D000E0016000300560055 +PickupItemCount=29 +TypeEffectivenessOffset=0x2EBB38 +DeoxysStatPrefix=8AAE6C78FF00 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x2015EE, 0x201613, 0x201616, 0x201698, 0x2016A6], Level=[0x201618]} // Lileep +StaticPokemon{}={Species=[0x201600, 0x2016B6, 0x2016B9, 0x20173B, 0x201749], Level=[0x2016BB]} // Anorith +StaticPokemon{}={Species=[0x21D057, 0x21D065, 0x21D0BA, 0x1E201C, 0x1E209C, 0x1E2158, 0x1E21D8], Level=[0x21D067]} // Kyogre +StaticPokemon{}={Species=[0x21D128, 0x21D136, 0x21D18B, 0x1E205C, 0x1E2198], Level=[0x21D138]} // Groudon +StaticPokemon{}={Species=[0x213E8A, 0x213E93, 0x213ED9], Level=[0x213E95]} // Regirock +StaticPokemon{}={Species=[0x21B8A1, 0x21B8AA, 0x21B8F0], Level=[0x21B8AC]} // Regice +StaticPokemon{}={Species=[0x21B9A3, 0x21B9AC, 0x21B9F2], Level=[0x21B9AE]} // Registeel +StaticPokemon{}={Species=[0x21BF95, 0x21BF9E, 0x21BFE4, 0x21C033, 0x21C051, 0x1E22FA, 0x1E2318, 0x1E23AE, 0x1E23CC], Level=[0x21BFA0]} // Rayquaza +StaticPokemon{}={Species=[0x243407, 0x243410], Level=[0x243412]} // Kecleons on OW (7) +StaticPokemon{}={Species=[0x1EDB4E, 0x1EDB57], Level=[0x1EDB59]} // Kecleon w/ Steven +StaticPokemon{}={Species=[0x21A80D, 0x21A814], Level=[0x21A80F]} // Voltorb 1 +StaticPokemon{}={Species=[0x21A85A, 0x21A861], Level=[0x21A85C]} // Voltorb 2 +StaticPokemon{}={Species=[0x21A8A7, 0x21A8AE], Level=[0x21A8A9]} // Voltorb 3 +StaticPokemon{}={Species=[0x217CF7, 0x217CFE], Level=[0x217CF9]} // Electrode 1 +StaticPokemon{}={Species=[0x217D44, 0x217D4B], Level=[0x217D46]} // Electrode 2 +StaticPokemon{}={Species=[0x222AB7, 0x222AC5], Level=[0x222AC7]} // Sudowoodo in Battle Frontier +StaticPokemon{}={Species=[0x22282F, 0x222944], Level=[0x222949]} // Latios on Southern Island +StaticPokemon{}={Species=[0x22283A, 0x222957], Level=[0x22295C]} // Latias on Southern Island +StaticPokemon{}={Species=[0x23B6A0, 0x23B6B0, 0x23B6FA, 0x23B705], Level=[0x23B6B5]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x23B4DB, 0x23B515, 0x23B56A, 0x23B575], Level=[0x23B51A]} // Mew on Faraway Island +StaticPokemon{}={Species=[0x23C1AE, 0x23C1DD, 0x23C22C, 0x23C237], Level=[0x23C1E2]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x23C2F6, 0x23C301, 0x23C34B, 0x23C356], Level=[0x23C306]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x1E5912]} // Wynaut Egg +StaticPokemon{}={Species=[0x20C8FA, 0x20C8FD, 0x20C97F, 0x20C990], Level=[0x20C8FF]} // Beldum +StaticPokemon{}={Species=[0x24161F, 0x241622, 0x2416AE], Level=[0x241624]} // Castform +RoamingPokemon{}={Species=[0x161AC4], Level=[0x161AF6, 0x161B02]} // Latios +RoamingPokemon{}={Species=[0x161ACC], Level=[0x161AF6, 0x161B02]} // Latias +StaticEggPokemonOffsets=[22] +CreateInitialRoamerMonFunctionStartOffset=0x161AA8 +StaticSouthernIslandOffsets=[16, 17] +ShopItemOffsets=[0x1DC148, 0x1DC564, 0x1F2B2C, 0x1F2B48, 0x1F41A0, 0x1F4EE4, 0x1F50B0, 0x1F6F40, 0x1FAA0C, 0x1FAA38, 0x1FE914, 0x200F68, 0x20360C, 0x203634, 0x204DD8, 0x20A954, 0x20A988, 0x20AA34, 0x20AA5C, 0x20AB84, 0x20ABA8, 0x20C5CC, 0x20F1D0, 0x211124, 0x23B264, 0x23BA34, 0x23BA60] +SkipShops=[1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19, 20, 21, 22, 23, 24, 25, 26] +MainGameShops=[0, 4, 17, 18] +CRC32=4881F3F8 + +[Emerald (T-Eng)] +Game=BPET +Version=0 +Type=Em +CopyStaticPokemon=1 +CopyFrom=Emerald (J) + +[Fire Red (J)] +Game=BPRJ +Version=0 +Type=FRLG +TableFile=gba_jpn +FreeSpace=0x800000 +PokemonCount=411 +PokemonNameLength=6 +PokemonMovesets=0x21A1BC +EggMoves=0x21B918 +PokemonTMHMCompat=0x20F5D0 +PokemonEvolutions=0x21615C +BattleTrappersBanned=[55,56,57,58,59] +StarterPokemon=0x17D1D0 +TrainerData=0x1FDFD8 +TrainerEntrySize=32 +TrainerCount=0x2E7 +TrainerClassNames=0x1FDB3C +TrainerClassCount=107 +TrainerClassNameLength=11 +TrainerNameLength=6 +DoublesTrainerClasses=[26, 40, 52, 53, 54, 92, 93, 94, 95, 96] +EliteFourIndices=[410, 411, 412, 413, 438, 439, 440] +ItemEntrySize=40 +ItemCount=374 +MoveCount=354 +MoveNameLength=8 +AbilityNameLength=8 +TmMoves=0x419D34 +TmMovesDuplicate=0x419F9C +MoveTutorData=0x4192F0 +MoveTutorMoves=15 +ItemImages=0x39C79C +TmPals=[0xD91E8C, 0xD91DEC, 0xD92044, 0xD91EB4, 0xD91FCC, 0xD9201C, 0xD91F04, 0xD91FF4, 0xD91F7C, 0xD91FF4, 0xD91F2C, 0xD91E3C, 0xD91F04, 0xD91FA4, 0xD91E64, 0xD91EDC, 0xD91E14, 0xD91F54] +IntroCryOffset=0x13034C +IntroSpriteOffset=0x1317B4 +IntroOtherOffset=0x131760 +ItemBallPic=92 +TradeTableOffset=0x22D2F8 +TradeTableSize=9 +TradesUnused=[] +RunIndoorsTweakOffset=0xBE754 +TextSpeedValuesOffset=0x3E30CC +CatchingTutorialOpponentMonOffset=0x7EFB4 +PCPotionOffset=0x3C83F8 +PickupTableStartLocator=8B000F00850019008600230087002D +PickupItemCount=16 +TypeEffectivenessOffset=0x20BF24 +DeoxysStatPrefix=7F002301FFFF +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x184BB8, 0x184BBB, 0x184BFB, 0x184C2F], Level=[0x184BBD]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x18A2E0, 0x18A2E7], Level=[0x18A35A]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x18A326, 0x18A32D], Level=[0x18A35A]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x16C18B, 0x16C192], Level=[0x16C18D]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16C1E9, 0x16C1F0], Level=[0x16C1EB]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16C117, 0x16C11E, 0x16C172], Level=[0x16C119]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x16B2B8, 0x16B2BF, 0x16B313, 0x18DE82, 0x18DE8E], Level=[0x16B2BA]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x16C846, 0x16C84D, 0x16C8A1], Level=[0x16C848]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x1693E1, 0x1693F5, 0x16943B], Level=[0x1693F7]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x1769F0, 0x1769F7, 0x16505E, 0x165064], Level=[0x1769F2]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x177A5E, 0x177A65], Level=[0x177A60]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x16C9CA, 0x16C9D0], Level=[0x16C9D2]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x16F243, 0x16F253, 0x16F29D, 0x16F2A8], Level=[0x16F258]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x16EF99, 0x16EFC8, 0x16F017, 0x16F022], Level=[0x16EFCD]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x16F0E7, 0x16F0F2, 0x16F13C, 0x16F147], Level=[0x16F0F7]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x18959C, 0x1895A0, 0x1895AB, 0x18949D], Level=[0x1895AD]} // Old Amber +StaticPokemon{}={Species=[0x189512, 0x189516, 0x189521, 0x189421], Level=[0x189523]} // Helix Fossil +StaticPokemon{}={Species=[0x189557, 0x18955B, 0x189566, 0x18945F], Level=[0x189568]} // Dome Fossil +StaticPokemon{}={Species=[0x1675D5, 0x1675D8, 0x167617, 0x16764A], Level=[0x1675DA]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x18C665, 0x18C668, 0x18C727], Level=[0x18C66A]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x185A8A, 0x185B06], Level=[0x185B49]} // Abra +StaticPokemon{}={Species=[0x185A9A, 0x185B11], Level=[0x185B5E]} // Clefairy +StaticPokemon{}={Species=[0x185ABA, 0x185B27], Level=[0x185B88]} // Scyther +StaticPokemon{}={Species=[0x185AAA, 0x185B1C], Level=[0x185B73]} // Dratini +StaticPokemon{}={Species=[0x185ACA, 0x185B32], Level=[0x185B9D]} // Porygon +FossilLevelOffsets=[0x1895AD, 0x189523, 0x189568] +ShopItemOffsets=[0x16E72C, 0x17409C, 0x1740B4, 0x1740D0, 0x1740F0, 0x17EFAC, 0x17FE28, 0x1810D0, 0x182210, 0x182D44, 0x183948, 0x183984, 0x183CF0, 0x183E08, 0x183E40, 0x186E28, 0x189AC8, 0x189BBC, 0x18B014, 0x18F494, 0x191518, 0x191DFC, 0x192354] +SkipShops=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 15, 16, 17, 18, 19, 20, 21, 22] +MainGameShops=[12, 13, 14] +CRC32=3B2056E9 + +[Fire Red (J) 1.1] +Game=BPRJ +Version=1 +Type=FRLG +CopyFrom=Fire Red (J) +PokemonMovesets=0x2159D4 +EggMoves=0x217130 +PokemonTMHMCompat=0x20ADE8 +PokemonEvolutions=0x211974 +StarterPokemon=0x17CD8E +TrainerData=0x1F97F0 +TrainerClassNames=0x1F9354 +TmMoves=0x415634 +TmMovesDuplicate=0x41589C +MoveTutorData=0x414BF0 +ItemImages=0x397F5C +IntroCryOffset=0x13029C +IntroSpriteOffset=0x131704 +IntroOtherOffset=0x1316B0 +TradeTableOffset=0x228B10 +RunIndoorsTweakOffset=0xBE6CC +TextSpeedValuesOffset=0x3DEA24 +CatchingTutorialOpponentMonOffset=0x7EF44 +PCPotionOffset=0x3C3D50 +TypeEffectivenessOffset=0x20773C +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x184774, 0x184777, 0x1847B7, 0x1847EB], Level=[0x184779]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x189E9C, 0x189EA3], Level=[0x189F16]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x189EE2, 0x189EE9], Level=[0x189F16]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x16BD20, 0x16BD27], Level=[0x16BD22]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16BD7E, 0x16BD85], Level=[0x16BD80]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16BCAC, 0x16BCB3, 0x16BD07], Level=[0x16BCAE]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x16AE48, 0x16AE4F, 0x16AEA3, 0x18DA3E, 0x18DA4A], Level=[0x16AE4A]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x16C3DB, 0x16C3E2, 0x16C436], Level=[0x16C3DD]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x168F71, 0x168F85, 0x168FCB], Level=[0x168F87]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x1765A4, 0x1765AB, 0x164BE4, 0x164BEA], Level=[0x1765A6]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x177612, 0x177619], Level=[0x177614]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x16C55F, 0x16C565], Level=[0x16C567]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x16EDDB, 0x16EDEB, 0x16EE35, 0x16EE40], Level=[0x16EDF0]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x16EB31, 0x16EB60, 0x16EBAF, 0x16EBBA], Level=[0x16EB65]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x16EC7F, 0x16EC8A, 0x16ECD4, 0x16ECDF], Level=[0x16EC8F]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x189158, 0x18915C, 0x189167, 0x189059], Level=[0x189169]} // Old Amber +StaticPokemon{}={Species=[0x1890CE, 0x1890D2, 0x1890DD, 0x188FDD], Level=[0x1890DF]} // Helix Fossil +StaticPokemon{}={Species=[0x189113, 0x189117, 0x189122, 0x18901B], Level=[0x189124]} // Dome Fossil +StaticPokemon{}={Species=[0x167160, 0x167163, 0x1671A2, 0x1671D5], Level=[0x167165]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x18C221, 0x18C224, 0x18C2E3], Level=[0x18C226]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x185646, 0x1856C2], Level=[0x185705]} // Abra +StaticPokemon{}={Species=[0x185656, 0x1856CD], Level=[0x18571A]} // Clefairy +StaticPokemon{}={Species=[0x185676, 0x1856E3], Level=[0x185744]} // Scyther +StaticPokemon{}={Species=[0x185666, 0x1856D8], Level=[0x18572F]} // Dratini +StaticPokemon{}={Species=[0x185686, 0x1856EE], Level=[0x185759]} // Porygon +FossilLevelOffsets=[0x189169, 0x1890DF, 0x189124] +ShopItemOffsets=[0x16E2C4, 0x173C3C, 0x173C54, 0x173C70, 0x173C90, 0x17EB68, 0x17F9E4, 0x180C8C, 0x181DCC, 0x182900, 0x183504, 0x183540, 0x1838AC, 0x1839C4, 0x1839FC, 0x1869E4, 0x189684, 0x189778, 0x18ABD0, 0x18F050, 0x1910D4, 0x1919B8, 0x191F10] +CRC32=BB640DF7 + +[Leaf Green (J)] +Game=BPGJ +Version=0 +Type=FRLG +CopyFrom=Fire Red (J) +PokemonMovesets=0x21A19C +EggMoves=0x21B8F8 +PokemonTMHMCompat=0x20F5AC +PokemonEvolutions=0x21613C +StarterPokemon=0x17D1AC +TrainerData=0x1FDFB4 +TrainerClassNames=0x1FDB18 +TmMoves=0x419CBC +TmMovesDuplicate=0x419F24 +MoveTutorData=0x419278 +ItemImages=0x39C60C +TmPals=[0xD91F0C, 0xD91E6C, 0xD920C4, 0xD91F34, 0xD9204C, 0xD9209C, 0xD91F84, 0xD92074, 0xD91FFC, 0xD92074, 0xD91FAC, 0xD91EBC, 0xD91F84, 0xD92024, 0xD91EE4, 0xD91F5C, 0xD91E94, 0xD91FD4] +IntroCryOffset=0x130324 +IntroSpriteOffset=0x13178C +IntroOtherOffset=0x131738 +TradeTableOffset=0x22D2D8 +RunIndoorsTweakOffset=0xBE728 +TextSpeedValuesOffset=0x3E2F3C +CatchingTutorialOpponentMonOffset=0x7EF88 +PCPotionOffset=0x3C8268 +TypeEffectivenessOffset=0x20BF00 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[0x184B94, 0x184B97, 0x184BD7, 0x184C0B], Level=[0x184B99]} // Eevee in Celadon Condominiums +StaticPokemon{}={Species=[0x18A2BC, 0x18A2C3], Level=[0x18A336]} // Hitmonlee in Fighting Dojo +StaticPokemon{}={Species=[0x18A302, 0x18A309], Level=[0x18A336]} // Hitmonchan in Fighting Dojo +StaticPokemon{}={Species=[0x16C167, 0x16C16E], Level=[0x16C169]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16C1C5, 0x16C1CC], Level=[0x16C1C7]} // Electrode in The Power Plant +StaticPokemon{}={Species=[0x16C0F3, 0x16C0FA, 0x16C14E], Level=[0x16C0F5]} // Zapdos in The Power Plant +StaticPokemon{}={Species=[0x16B294, 0x16B29B, 0x16B2EF, 0x18DE5E, 0x18DE6A], Level=[0x16B296]} // Articuno (Seafoam and Route 15 Gatehouse) +StaticPokemon{}={Species=[0x16C822, 0x16C829, 0x16C87D], Level=[0x16C824]} // Moltres in Mt.Ember +StaticPokemon{}={Species=[0x1693BD, 0x1693D1, 0x169417], Level=[0x1693D3]} // Mewtwo in Unk. Dungeon +StaticPokemon{}={Species=[0x1769CC, 0x1769D3, 0x16503A, 0x165040], Level=[0x1769CE]} // Snorlax (Route 12 and S.S. Anne) +StaticPokemon{}={Species=[0x177A3A, 0x177A41], Level=[0x177A3C]} // Snorlax (Route 16) +StaticPokemon{}={Species=[0x16C9A6, 0x16C9AC], Level=[0x16C9AE]} // Hypno in Berry Forest +StaticPokemon{}={Species=[0x16F21F, 0x16F22F, 0x16F279, 0x16F284], Level=[0x16F234]} // Deoxys on Birth Island +StaticPokemon{}={Species=[0x16EF75, 0x16EFA4, 0x16EFF3, 0x16EFFE], Level=[0x16EFA9]} // Ho-Oh on Navel Rock +StaticPokemon{}={Species=[0x16F0C3, 0x16F0CE, 0x16F118, 0x16F123], Level=[0x16F0D3]} // Lugia on Navel Rock +StaticPokemon{}={Species=[0x189578, 0x18957C, 0x189587, 0x189479], Level=[0x189589]} // Old Amber +StaticPokemon{}={Species=[0x1894EE, 0x1894F2, 0x1894FD, 0x1893FD], Level=[0x1894FF]} // Helix Fossil +StaticPokemon{}={Species=[0x189533, 0x189537, 0x189542, 0x18943B], Level=[0x189544]} // Dome Fossil +StaticPokemon{}={Species=[0x1675B1, 0x1675B4, 0x1675F3, 0x167626], Level=[0x1675B6]} // Lapras in Silph. Co +StaticPokemon{}={Species=[0x18C641, 0x18C644, 0x18C703], Level=[0x18C646]} // Magikarp in Mt.Moon Center +StaticPokemon{}={Species=[0x185A66, 0x185AE2], Level=[0x185B25]} // Abra +StaticPokemon{}={Species=[0x185A76, 0x185AED], Level=[0x185B3A]} // Clefairy +StaticPokemon{}={Species=[0x185A86, 0x185B19], Level=[0x185B8E]} // Pinsir +StaticPokemon{}={Species=[0x185A96, 0x185AF8], Level=[0x185B4F]} // Dratini +StaticPokemon{}={Species=[0x185AA6, 0x185B0E], Level=[0x185B79]} // Porygon +FossilLevelOffsets=[0x189589, 0x1894FF, 0x189544] +ShopItemOffsets=[0x16E708, 0x174078, 0x174090, 0x1740AC, 0x1740CC, 0x17EF88, 0x17FE04, 0x1810AC, 0x1821EC, 0x182D20, 0x183924, 0x183960, 0x183CCC, 0x183DE4, 0x183E1C, 0x186E04, 0x189AA4, 0x189B98, 0x18AFF0, 0x18F470, 0x1914F4, 0x191DD8, 0x192330] +CRC32=0A48556B diff --git a/src/com/pkrandom/config/gen4_offsets.ini b/src/com/pkrandom/config/gen4_offsets.ini new file mode 100755 index 0000000..8cfc7d1 --- /dev/null +++ b/src/com/pkrandom/config/gen4_offsets.ini @@ -0,0 +1,1477 @@ +[Diamond (U)] +Game=ADAE +Type=DP +Version=5 +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +EncounterOvlNumber=6 +BattleOvlNumber=11 +IntroOvlNumber=59 +StarterPokemonOvlNumber=64 +FastestTextTweak=instant_text/dp_instant_text +NewIndexToMusicTweak=musicfix/diamond_musicfix +NationalDexAtStartTweak=national_dex/dp_national_dex +StarterPokemonOffset=0x1B88 +StarterPokemonGraphicsPrefix=000222402104120C +StarterPokemonGraphicsPrefixInner=0290039002200002 +StarterPokemonScriptOffset=342 +StarterPokemonHeldItemOffset=0x2B4 +HasExtraPokemonNames=Yes +PokemonNamesTextOffset=362 +TrainerNamesTextOffset=559 +TrainerClassesTextOffset=560 +DoublesTrainerClasses=[8, 23, 31, 47, 70, 82] +EliteFourIndices=[261, 262, 263, 264, 267] +MoveDescriptionsTextOffset=587 +MoveNamesTextOffset=588 +AbilityNamesTextOffset=552 +ItemNamesTextOffset=344 +ItemDescriptionsTextOffset=343 +StarterScreenTextOffset=320 +PokedexSpeciesTextOffset=621 +StarterLocationTextOffset=270 +IngameTradesTextOffset=326 +IngameTradePersonTextOffsets=[67,89,171,584] +HiddenItemTableOffset=0xF2DB4 +HiddenItemCount=229 +ItemBallsScriptOffset=370 +ItemBallsSkip=[40, 196] +MapTableARM9Offset=0xEEDBC +MapTableNameIndexSize=2 +MapNamesTextOffset=382 +CatchingTutorialOpponentMonOffset=0x47960 +NationalDexScriptOffset=990 +StaticPokemonSupport=1 +HoneyTreeOffsets=[2, 3, 4] +FossilTableOffset=0xF450C +FossilLevelScriptNumber=63 +FossilLevelOffset=0x41A +PokedexAreaDataDungeonIndex=4 +PokedexAreaDataDungeonSpecialPreNationalIndex=1489 +PokedexAreaDataDungeonSpecialPostNationalIndex=1984 +PokedexAreaDataOverworldIndex=2479 +PokedexAreaDataOverworldSpecialPreNationalIndex=3964 +PokedexAreaDataOverworldSpecialPostNationalIndex=4459 +StaticPokemon{}={Species=[342:0x261, 342:0x2BE], Level=[342:0x2C0]} // Starly +StaticPokemon{}={Species=[230:0x4AE, 230:0xE9A, 230:0xECE, 230:0x1201, 230:0x1235], Level=[230:0xEE4, 230:0x124B]} // Dialga +StaticPokemon{}={Species=[230:0x4B4, 230:0xEA0, 230:0xED4, 230:0x1207, 230:0x123B], Level=[230:0xEE4, 230:0x124B]} // Palkia +StaticPokemon{}={Species=[352:0x39, 352:0x48], Level=[352:0x4A]} // Uxie +StaticPokemon{}={Species=[348:0x81, 348:0x90], Level=[348:0x92]} // Azelf +StaticPokemon{}={Species=[278:0x16F, 278:0x17E], Level=[278:0x180]} // Heatran +StaticPokemon{}={Species=[309:0x88, 309:0x94], Level=[309:0x96]} // Regigigas +StaticPokemon{}={Species=[283:0x50, 283:0x5F], Level=[283:0x61]} // Giratina +StaticPokemon{}={Species=[354:0x40], Level=[354:0x42]} // Darkrai +StaticPokemon{}={Species=[302:0x39, 302:0x48], Level=[302:0x4A]} // Shaymin +StaticPokemon{}={Species=[232:0x45, 232:0x53, 232:0x62], Level=[232:0x64]} // Arceus +StaticPokemon{}={Species=[112:0xB5], Level=[112:0xB7]} // Eevee +StaticPokemon{}={Species=[90:0x568]} // Happiny (egg) +StaticPokemon{}={Species=[321:0x332]} // Riolu (egg) +StaticPokemon{}={Species=[210:0x1C5, 210:0x1D6], Level=[210:0x1D8]} // Drifloon +StaticPokemon{}={Species=[329:0x74, 329:0x80], Level=[329:0x82]} // Rotom +StaticPokemon{}={Species=[406:0x153, 406:0x160], Level=[406:0x162]} // Spiritomb +RoamingPokemon{}={Species=[0x60580], Level=[0x604B6], Script=[344:0x1A, 344:0x24], Gender=[344:0x1C]} // Mesprit +RoamingPokemon{}={Species=[0x60584], Level=[0x604BE], Script=[274:0x18, 274:0x22], Gender=[274:0x1A]} // Cresselia +RoamingPokemonFunctionStartOffset=0x60490 +ShopCount=28 +SkipShops=[12,13,15,16,17,18,19,20,21,22,23] +MainGameShops=[0,1,2,3,4,5,6,7,8,9,10,11,14,24,25,26,27] +ShopDataPrefix=391104027511040285AF0302A5AF0302 +StaticEggPokemonOffsets=[11, 12] +MainGameLegendaries=[483] +TCMCopyingPrefix=111011157D7005001EFF2FE11EFF2FE1 +NewIndexToMusicPrefix=142003E00E2001E01220FFE702BC0847 +Arm9ExtensionSize=168 // 168 for music +SpecialMusicStatics=[479,480,481,482,483,484,485,486,487,488,491,492,493] +DoubleBattleFlagReturnPrefix=08B5092131F020FB +DoubleBattleWalkingPrefix1=16B00020F8BD1498 +DoubleBattleWalkingPrefix2=36FB16B00120F8BD +DoubleBattleTextBoxPrefix=F7F71AFEF7F734FE +TrainerEndFileNumber=4 +TrainerEndTextBoxOffset=0xD8 +TMText{}={42=[538:1], 48=[54:2], 56=[466:1, 466:2], 63=[135:35], 66=[453:1], 67=[90:0], 76=[60:4], 77=[437:2], 78=[419:2], 88=[518:2], 92=[449:1]} +Arm9CRC32=08E0337C +OverlayCRC32<6>=0AE6A693 +OverlayCRC32<11>=3DCCA476 +OverlayCRC32<59>=8CEA8C3C +OverlayCRC32<64>=727963E2 + +[Pearl (U)] +Game=APAE +Type=DP +Version=5 +CopyText=1 +CopyStaticPokemon=1 +CopyRoamingPokemon=1 +CopyFrom=Diamond (U) +File= +File= +File= +FastestTextTweak=instant_text/dp_instant_text +NewIndexToMusicTweak=musicfix/diamond_musicfix +NationalDexAtStartTweak=national_dex/dp_national_dex +HoneyTreeOffsets=[5, 6, 7] +MainGameLegendaries=[484] +Arm9CRC32=D80458A5 +OverlayCRC32<6>=F7C193D2 +OverlayCRC32<11>=0DD7691D +OverlayCRC32<59>=8CEA8C3C +OverlayCRC32<64>=525F49E6 + +[Platinum (U)] +Game=CPUE +Type=Plat +Version=0 +CopyFrom=Diamond (U) +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +FieldOvlNumber=5 +MoveTutorCompatOvlNumber=5 +BattleOvlNumber=16 +IntroOvlNumber=73 +StarterPokemonOvlNumber=78 +NewRoamerSubroutineTweak=hardcoded_statics/roamers/plat_roamers +FastestTextTweak=instant_text/plat_instant_text +NewIndexToMusicTweak=musicfix/plat_musicfix +NationalDexAtStartTweak=national_dex/plat_national_dex +StarterPokemonOffset=0x1BC0 +StarterPokemonGraphicsPrefix=000222402104120C +StarterPokemonGraphicsPrefixInner=0290039002200002 +StarterPokemonScriptOffset=427 +StarterPokemonHeldItemOffset=0x460 +MoveTutorMovesOffset=0x2FF64 +MoveTutorCount=38 +MoveTutorBytesCount=12 +MoveTutorCompatOffset=0x3012C +MoveTutorCompatBytesCount=5 +PokemonNamesTextOffset=412 +TrainerNamesTextOffset=618 +TrainerClassesTextOffset=619 +MoveDescriptionsTextOffset=646 +MoveNamesTextOffset=647 +AbilityNamesTextOffset=610 +ItemDescriptionsTextOffset=391 +ItemNamesTextOffset=392 +StarterScreenTextOffset=360 +PokedexSpeciesTextOffset=711 +StarterLocationTextOffset=466 +IngameTradesTextOffset=370 +IngameTradePersonTextOffsets=[74,97,180,643] +HiddenItemTableOffset=0xEA378 +HiddenItemCount=257 +ItemBallsScriptOffset=404 +ItemBallsSkip=[25, 238, 321, 325, 326] +MapTableARM9Offset=0xE601C +MapTableNameIndexSize=1 +MapNamesTextOffset=433 +CatchingTutorialOpponentMonOffset=0x520A0 +HoneyTreeOffsets=[2, 3, 4] +FossilTableOffset=0xEBFFC +FossilLevelScriptNumber=65 +FossilLevelOffset=0x426 +NationalDexScriptOffset=1064 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[291:0x43, 291:0x52, 389:0xCC, 389:0xDD], Level=[291:0x54, 389:0xDF]} // Giratina +StaticPokemon{}={Species=[361:0x39, 361:0x48], Level=[361:0x4A]} // Uxie +StaticPokemon{}={Species=[357:0x81, 357:0x90], Level=[357:0x92]} // Azelf +StaticPokemon{}={Species=[239:0xAB, 239:0xB8], Level=[239:0xBA]} // Dialga +StaticPokemon{}={Species=[240:0xAB, 240:0xB8], Level=[240:0xBA]} // Palkia +StaticPokemon{}={Species=[286:0x103, 286:0x112], Level=[286:0x114]} // Heatran +StaticPokemon{}={Species=[317:0x88, 317:0x94], Level=[317:0x96]} // Regigigas +StaticPokemon{}={Species=[392:0xB0, 392:0xBD], Level=[392:0xBF]} // Registeel +StaticPokemon{}={Species=[394:0xB0, 394:0xBD], Level=[394:0xBF]} // Regice +StaticPokemon{}={Species=[396:0xB0, 396:0xBD], Level=[396:0xBF]} // Regirock +StaticPokemon{}={Species=[363:0x8E], Level=[363:0x90]} // Darkrai +StaticPokemon{}={Species=[310:0x87, 310:0x96], Level=[310:0x98]} // Shaymin +StaticPokemon{}={Species=[238:0x6C, 238:0x7A, 238:0x89], Level=[238:0x8B]} // Arceus +StaticPokemon{}={Species=[71:0xE5B]} // Togepi (egg) +StaticPokemon{}={Species=[117:0x79], Level=[117:0x7B]} // Eevee +StaticPokemon{}={Species=[153:0x7D], Level=[153:0x7F]} // Porygon +StaticPokemon{}={Species=[329:0x338]} // Riolu (egg) +StaticPokemon{}={Species=[216:0x1C9, 216:0x1DA], Level=[216:0x1DC]} // Drifloon +StaticPokemon{}={Species=[337:0x51, 337:0x5D], Level=[337:0x5F]} // Rotom +StaticPokemon{}={Species=[441:0x153, 441:0x160], Level=[441:0x162]} // Spiritomb +RoamingPokemon{}={Species=[0x1024B0, 0x102514], Level=[0x10248A], Script=[353:0x1A, 353:0x24], Gender=[353:0x1C]} // Mesprit +RoamingPokemon{}={Species=[0x1024B4, 0x102518], Level=[0x102490], Script=[282:0x16, 282:0x20], Gender=[282:0x18]} // Cresselia +RoamingPokemon{}={Species=[0x1024C4, 0x102524], Level=[0x1024A8]} // Articuno +RoamingPokemon{}={Species=[0x1024C0, 0x102520], Level=[0x1024A2]} // Zapdos +RoamingPokemon{}={Species=[0x1024BC, 0x10251C], Level=[0x10249C]} // Moltres +ShopCount=29 +SkipShops=[13,15,16,17,18,19,20,21,22,23,24] +MainGameShops=[0,1,2,3,4,5,6,7,8,9,10,11,12,14,25,26,27,28] +ShopDataPrefix=ED7F0402258004026180040281800402 +StaticEggPokemonOffsets=[13, 16] +MainGameLegendaries=[487] +TCMCopyingPrefix=111011157D7005001EFF2FE11EFF2FE1 +NewIndexToMusicPrefix=142003E00E2001E01220FFE702BC0847 +Arm9ExtensionSize=424 // 240 for music, 184 for roamers +SpecialMusicStatics=[144,145,146,377,378,379,479,480,481,482,483,484,485,486,487,488,491,492,493] +DoubleBattleFlagReturnPrefix=08B5092139F0C6FF +DoubleBattleWalkingPrefix1=16B00020F8BD1498 +DoubleBattleWalkingPrefix2=C4FE16B00120F8BD +DoubleBattleTextBoxPrefix=F6F792FCF6F7ACFC +TMText{}={27=[561:10], 42=[594:1], 48=[61:1], 56=[517:0, 517:1], 63=[143:51], 66=[504:1, 504:2], 67=[98:0], 76=[67:4], 77=[488:2], 78=[470:2], 88=[574:2], 92=[500:1]} +FastDistortionWorldTweak=pt_fast_distortion_world +Arm9CRC32=4D104949 +OverlayCRC32<5>=3E286491 +OverlayCRC32<6>=E6C5F31B +OverlayCRC32<16>=25EBE8C1 +OverlayCRC32<73>=C003DED1 +OverlayCRC32<78>=091E8E97 + +[HeartGold (U)] +Game=IPKE +Type=HGSS +Version=0 +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +FieldOvlNumber=1 +BattleOvlNumber=12 +FossilTableOvlNumber=21 +StarterPokemonOvlNumber=61 +NewRoamerSubroutineTweak=hardcoded_statics/roamers/hgss_roamers +NewCatchingTutorialSubroutineTweak=hgss_catching_tutorialfix +FastestTextTweak=instant_text/hgss_instant_text +NationalDexAtStartTweak=national_dex/hgss_national_dex +MoveTutorMovesOffset=0x23AE0 +MoveTutorCount=52 +MoveTutorBytesCount=4 +MoveTutorCompatOffset=0 +MoveTutorCompatBytesCount=8 +HasExtraPokemonNames=Yes +PokemonNamesTextOffset=237 +TrainerNamesTextOffset=729 +TrainerClassesTextOffset=730 +DoublesTrainerClasses=[8, 121, 122] +EliteFourIndices=[244, 245, 246, 247, 418] +MoveDescriptionsTextOffset=749 +MoveNamesTextOffset=750 +AbilityNamesTextOffset=720 +ItemDescriptionsTextOffset=221 +ItemNamesTextOffset=222 +StarterScreenTextOffset=190 +IngameTradesTextOffset=200 +IngameTradePersonTextOffsets=[562,596,608,634,0,0,344,463,535,47,537] +HiddenItemTableOffset=0xFA558 +HiddenItemCount=231 +ItemBallsScriptOffset=141 +ItemBallsSkip=[58] +MapTableARM9Offset=0xF6BE0 +MapTableNameIndexSize=1 +MapNamesTextOffset=279 +FossilTableOffset=0x130 +FossilLevelScriptNumber=755 +FossilLevelOffset=0x58D +NationalDexScriptOffset=229 +PokedexAreaDataDungeonIndex=2 +PokedexAreaDataOverworldIndex=1487 +PokedexAreaDataDungeonSpecialIndex=2972 +PokedexAreaDataOverworldSpecialIndex=3467 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[104:0x108], Level=[104:0x138, 104:0x12C]} // Lugia +StaticPokemon{}={Species=[21:0xD1], Level=[21:0xF5, 21:0x101]} // Ho-oh +StaticPokemon{}={Species=[216:0x58F, 216:0x6E8, 216:0x708, 24:0x67, 24:0xB4, 24:0x314, 24:0x320, 24:0xD4], Level=[216:0x70A, 24:0x322]} // Suicune +StaticPokemon{}={Species=[14:0x2F, 14:0x3B], Level=[14:0x3D]} // Articuno +StaticPokemon{}={Species=[191:0x26B, 191:0x277], Level=[191:0x279]} // Zapdos +StaticPokemon{}={Species=[106:0x2F, 106:0x3B], Level=[106:0x3D]} // Moltres +StaticPokemon{}={Species=[11:0x2F, 11:0x3B], Level=[11:0x3D]} // Mewtwo +StaticPokemon{}={Species=[134:0xA3, 134:0xB4], Level=[134:0xB6]} // Kyogre +StaticPokemon{}={Species=[133:0xA3, 133:0xB4], Level=[133:0xB6]} // Groudon +StaticPokemon{}={Species=[135:0xDA, 135:0xEB, 135:0x62, 135:0x98], Level=[135:0xED]} // Rayquaza +StaticPokemon{}={Species=[131:0x43A, 131:0x67C, 131:0x872, 131:0x8E4, 131:0x958, 131:0x963], Level=[131:0x965]} // Dialga +StaticPokemon{}={Species=[131:0x4A2, 131:0x695, 131:0x88D, 131:0x8FA, 131:0x97F, 131:0x98A], Level=[131:0x98C]} // Palkia +StaticPokemon{}={Species=[131:0x50A, 131:0x9A4], Level=[131:0x9A6], Forme=[131:0x9AA]} // Giratina-O +StaticPokemon{}={Species=[750:0x4CC], Level=[750:0x4E3]} // Latias +StaticPokemon{}={Species=[750:0x4B7], Level=[750:0x4E3]} // Latios +StaticPokemon{}={Species=[243:0x2FD, 243:0x14B], Level=[243:0x2FF, 243:0x14D]} // Sudowoodo +StaticPokemon{}={Species=[58:0x61, 58:0x6D], Level=[58:0x6F]} // Lapras +StaticPokemon{}={Species=[938:0x3CD, 938:0x3DE], Level=[938:0x3E0]} // Red Gyarados +StaticPokemon{}={Species=[197:0x6C, 197:0x7D, 199:0x26A, 199:0x27B], Level=[197:0x7F, 199:0x27D]} // Snorlax +StaticPokemon{}={Species=[89:0xF3D, 89:0x1078, 89:0x10A5, 89:0x112C, 89:0x11B3], Level=[89:0xF3F, 89:0x107A, 89:0x10A7, 89:0x112E, 89:0x11B5]} // Koffing @ Rocket Base +StaticPokemon{}={Species=[89:0xF6A, 89:0xFC4, 89:0x101E, 89:0x104B, 89:0x1159, 89:0x1186], Level=[89:0xF6C, 89:0xFC6, 89:0x1020, 89:0x104D, 89:0x115B, 89:0x1188]} // Voltorb @ Rocket Base +StaticPokemon{}={Species=[89:0xF97, 89:0xFF1, 89:0x10D2, 89:0x10FF, 89:0x11E0], Level=[89:0xF99, 89:0xFF3, 89:0x10D4, 89:0x1101, 89:0x11E2]} // Geodude @ Rocket Base +StaticPokemon{}={Species=[90:0x784], Level=[90:0x786]} // Electrode @ Rocket Base (1) +StaticPokemon{}={Species=[90:0x7E8], Level=[90:0x7EA]} // Electrode @ Rocket Base (2) +StaticPokemon{}={Species=[90:0x84C], Level=[90:0x84E]} // Electrode @ Rocket Base (3) +StaticPokemon{}={Species=[892:0x61], Level=[892:0x63]} // Eevee +StaticPokemon{}={Species=[98:0x71], Level=[98:0x73]} // Tyrogue +StaticPokemon{}={Species=[112:0x4D1], Level=[112:0x4D3]} // Dratini +StaticPokemon{}={Species=[740:0x66F, 740:0x675, 740:0x695, 740:0x818, 740:0x8BC], Level=[740:0x86D]} // Bulbasaur +StaticPokemon{}={Species=[740:0x71D, 740:0x723, 740:0x743, 740:0x833, 740:0x8D7], Level=[740:0x86D]} // Squirtle +StaticPokemon{}={Species=[740:0x7CB, 740:0x7D1, 740:0x7F1], Level=[740:0x86D]} // Charmander +StaticPokemon{}={Species=[837:0x28F], Level=[837:0x2D1]} // Treecko +StaticPokemon{}={Species=[837:0x2A8], Level=[837:0x2D1]} // Torchic +StaticPokemon{}={Species=[837:0x2B4], Level=[837:0x2D1]} // Mudkip +StaticPokemon{}={Species=[860:0x146, 860:0x14D]]} // Primo's Mareep Egg +StaticPokemon{}={Species=[860:0x180, 860:0x187]]} // Primo's Wooper Egg +StaticPokemon{}={Species=[860:0x1BA, 860:0x1C1]]} // Primo's Slugma Egg +StaticPokemon{}={Species=[878:0x90], Level=[878:0x92]} // Secret Tentacool +StaticPokemonGameCorner{}={Species=[910:0x9A9, 910:0xA38, 910:0xAD8], Level=[910:0xABC], Text=[603:0x18]} // Abra +StaticPokemonGameCorner{}={Species=[910:0x9B5, 910:0xA5C, 910:0xAEF], Level=[910:0xABC], Text=[603:0x19]} // Ekans +StaticPokemonGameCorner{}={Species=[910:0x9CD, 910:0xA80, 910:0xB06], Level=[910:0xABC], Text=[603:0x1B]} // Sandshrew +StaticPokemonGameCorner{}={Species=[910:0x9C1], Level=[910:0xABC], Text=[603:0x1A]} // Dratini +StaticPokemonGameCorner{}={Species=[804:0x875, 804:0x8DB, 804:0x957], Level=[804:0x93B], Text=[509:0x21]} // Mr. Mime +StaticPokemonGameCorner{}={Species=[804:0x881, 804:0x8FF, 804:0x96E], Level=[804:0x93B], Text=[509:0x22]} // Eevee +StaticPokemonGameCorner{}={Species=[804:0x88D], Level=[804:0x93B], Text=[509:0x23]} // Porygon +RoamingPokemon{}={Species=[0x111F08, 0x111F4C], Level=[0x111EEE], Script=[24:0x2F]} // Raikou +RoamingPokemon{}={Species=[0x111F0C, 0x111F50], Level=[0x111EF4], Script=[24:0x4B]} // Entei +RoamingPokemon{}={Species=[0x111F10, 0x111F54], Level=[0x111EFA], Script=[776:0xD5], Gender=[776:0xD7]} // Latias +RoamingPokemon{}={Species=[0x111F14, 0x111F58], Level=[0x111F00], Script=[776:0x10B], Gender=[776:0x10D]} // Latios +StaticPokemonTrades=[6,7] // Shuckie & Kenya +StaticPokemonTradeScripts=[880,241] +StaticPokemonTradeLevelOffsets=[0x80,0xA7] +KenyaTextOffset=388 +MysteryEggOffset=0x1C80E // Togepi Mystery Egg +ShopCount=40 +SkipShops=[17,18,22,23,24,25,26,27,28,29,30,37,39] +MainGameShops=[0,2,5,6,7,12,14,16,19,31,33,34,36] +ShopDataPrefix=298E0402618E0402998E0402B98E0402 +StaticEggPokemonOffsets=[34, 35, 36] +MainGameLegendaries=[250] +IndexToMusicPrefix=905F010084030000A0860100E803000000010203040000000001020304000000 +SpecialMusicStatics=[243,244,245,249,250,383,382,384,150,381,380] +MarillCryScripts=[93:0x66, 225:0x1B8, 842:0x16C6, 849:0x1AE] +MarillTextFiles=[115, 379, 542, 545, 549] +TCMCopyingPrefix=111011157D7005001EFF2FE11EFF2FE1 +Arm9ExtensionSize=220 // 92 for catching tutorial, 128 for roamers +CatchingTutorialMonTablePrefix=4EFA04B070BD +DoubleBattleFlagReturnPrefix=08B5092132F0B4FF +DoubleBattleWalkingPrefix1=16B00020F8BD1498 +DoubleBattleWalkingPrefix2=9CFE16B00120F8BD +DoubleBattleTextBoxPrefix=F6F794FEF6F7AEFE +TrainerEndFileNumber=4 +TrainerEndTextBoxOffset=0xD8 +TMTextGameCorner{}={90=[603:14], 75=[603:15], 44=[603:16], 35=[603:17], 13=[603:18], 24=[603:19]} // Goldenrod +TMTextGameCorner{}={58=[509:23], 32=[509:24], 10=[509:25], 29=[509:26], 74=[509:27], 68=[509:28]} // Celadon +TMText{}={01=[574:6], 07=[622:4], 23=[606:5], 30=[614:5], 45=[582:7], 51=[558:4], 59=[129:3, 631:7], 89=[567:5]} // Johto Gym Leaders +TMText{}={03=[469:12], 19=[492:3], 34=[485:4], 48=[531:4], 50=[53:3], 80=[462:3, 462:4], 84=[514:3], 92=[454:4]} // Kanto Gym Leaders +TMText{}={05=[380:10, 380:12], 10=[627:2], 11=[67:7], 12=[386:2], 29=[534:1], 36=[403:6], 37=[370:2], 44=[378:8], 47=[372:2], 57=[345:17], 70=[56:4, 56:5], 83=[397:10], 85=[452:6]} // Everything else +FrontierScriptNumber=76 +FrontierScriptTMOffsets{}={40=0xC5C, 31=0xC7A, 89=0xC98, 81=0xCB6, 71=0xCD4, 26=0xCF2, 30=0xD88, 53=0xDA6, 36=0xDC4, 59=0xDE2, 06=0xEA2, 73=0xEC0, 61=0xEDE, 45=0xEFC, 08=0xF1A, 04=0xF38} +MiscUITextOffset=191 +FrontierTMText{}={40=380, 31=381, 89=382, 81=383, 71=384, 26=385, 30=386, 53=387, 36=388, 59=389, 06=390, 73=391, 61=392, 45=393, 08=394, 04=395} +Arm9CRC32=99A30D93 +OverlayCRC32<1>=21F7A855 +OverlayCRC32<12>=90D2AF3E +OverlayCRC32<21>=A6363D04 +OverlayCRC32<61>=EE849CB4 + +[SoulSilver (U)] +Game=IPGE +Type=HGSS +Version=0 +CopyText=1 +CopyStaticPokemon=1 +CopyRoamingPokemon=1 +CopyFrom=HeartGold (U) +File= +File= +File= +FastestTextTweak=instant_text/hgss_instant_text +NationalDexAtStartTweak=national_dex/hgss_national_dex +NewCatchingTutorialSubroutineTweak=hgss_catching_tutorialfix +NewRoamerSubroutineTweak=hardcoded_statics/roamers/hgss_roamers +MainGameLegendaries=[249] +Arm9CRC32=8711C90D +OverlayCRC32<1>=172E4E62 +OverlayCRC32<12>=7AFCE42A +OverlayCRC32<21>=A6363D04 +OverlayCRC32<61>=EE849CB4 + +[Diamond (E)] +Game=ADAE +Type=DP +Version=13 +CopyText=1 +CopyStaticPokemon=1 +CopyRoamingPokemon=1 +CopyFrom=Diamond (U) +File= +FastestTextTweak=instant_text/dp_instant_text +NewIndexToMusicTweak=musicfix/diamond_musicfix +NationalDexAtStartTweak=national_dex/dp_national_dex +Arm9CRC32=08E0337C +OverlayCRC32<6>=0AE6A693 +OverlayCRC32<11>=3DCCA476 +OverlayCRC32<59>=8CEA8C3C +OverlayCRC32<64>=727963E2 + +[Pearl (E)] +Game=APAE +Type=DP +Version=13 +CopyText=1 +CopyStaticPokemon=1 +CopyRoamingPokemon=1 +CopyFrom=Pearl (U) +File= +FastestTextTweak=instant_text/dp_instant_text +NewIndexToMusicTweak=musicfix/diamond_musicfix +NationalDexAtStartTweak=national_dex/dp_national_dex +Arm9CRC32=D80458A5 +OverlayCRC32<6>=F7C193D2 +OverlayCRC32<11>=0DD7691D +OverlayCRC32<59>=8CEA8C3C +OverlayCRC32<64>=525F49E6 + +[Platinum (U Rev 1)] +Game=CPUE +Type=Plat +Version=1 +CopyText=1 +CopyStaticPokemon=1 +CopyRoamingPokemon=1 +CopyFrom=Platinum (U) +NewRoamerSubroutineTweak=hardcoded_statics/roamers/plat_roamers +FastestTextTweak=instant_text/plat_instant_text +NewIndexToMusicTweak=musicfix/plat_musicfix +NationalDexAtStartTweak=national_dex/plat_national_dex +FastDistortionWorldTweak=pt_fast_distortion_world +Arm9CRC32=4D104949 +OverlayCRC32<5>=3E286491 +OverlayCRC32<6>=E6C5F31B +OverlayCRC32<16>=25EBE8C1 +OverlayCRC32<73>=C003DED1 +OverlayCRC32<78>=091E8E97 + +[Platinum (E)] +Game=CPUE +Type=Plat +Version=10 +CopyText=1 +CopyStaticPokemon=1 +CopyRoamingPokemon=1 +CopyFrom=Platinum (U) +File= +File= +NewRoamerSubroutineTweak=hardcoded_statics/roamers/plat_roamers +FastestTextTweak=instant_text/plat_instant_text +NewIndexToMusicTweak=musicfix/plat_musicfix +NationalDexAtStartTweak=national_dex/plat_national_dex +FastDistortionWorldTweak=pt_fast_distortion_world +Arm9CRC32=4D104949 +OverlayCRC32<5>=3E286491 +OverlayCRC32<6>=E6C5F31B +OverlayCRC32<16>=25EBE8C1 +OverlayCRC32<73>=C003DED1 +OverlayCRC32<78>=091E8E97 + +[HeartGold (E)] +Game=IPKE +Type=HGSS +Version=10 +CopyText=1 +CopyStaticPokemon=1 +CopyRoamingPokemon=1 +CopyFrom=HeartGold (U) +NewRoamerSubroutineTweak=hardcoded_statics/roamers/hgss_roamers +NewCatchingTutorialSubroutineTweak=hgss_catching_tutorialfix +FastestTextTweak=instant_text/hgss_instant_text +NationalDexAtStartTweak=national_dex/hgss_national_dex +Arm9CRC32=99A30D93 +OverlayCRC32<1>=21F7A855 +OverlayCRC32<12>=90D2AF3E +OverlayCRC32<21>=A6363D04 +OverlayCRC32<61>=EE849CB4 + +[SoulSilver (E)] +Game=IPGE +Type=HGSS +Version=10 +CopyText=1 +CopyStaticPokemon=1 +CopyRoamingPokemon=1 +CopyFrom=SoulSilver (U) +FastestTextTweak=instant_text/hgss_instant_text +NationalDexAtStartTweak=national_dex/hgss_national_dex +NewCatchingTutorialSubroutineTweak=hgss_catching_tutorialfix +NewRoamerSubroutineTweak=hardcoded_statics/roamers/hgss_roamers +Arm9CRC32=8711C90D +OverlayCRC32<1>=172E4E62 +OverlayCRC32<12>=7AFCE42A +OverlayCRC32<21>=A6363D04 +OverlayCRC32<61>=EE849CB4 + +[Pearl (J)] +Game=APAJ +Type=DP +Version=0 +CopyStaticPokemon=1 +CopyFrom=Pearl (U) +File= +File= +File= +File= +StarterPokemonOffset=0x30 +HiddenItemTableOffset=0xF4C14 +HasExtraPokemonNames=No +PokemonNamesTextOffset=356 +TrainerNamesTextOffset=550 +TrainerClassesTextOffset=551 +MoveDescriptionsTextOffset=574 +MoveNamesTextOffset=575 +AbilityNamesTextOffset=544 +ItemDescriptionsTextOffset=340 +ItemNamesTextOffset=341 +StarterScreenTextOffset=318 +PokedexSpeciesTextOffset=607 +StarterLocationTextOffset=269 +IngameTradesTextOffset=324 +IngameTradePersonTextOffsets=[66,88,170,571] +MapTableARM9Offset=0xF0C2C +MapNamesTextOffset=374 +CatchingTutorialOpponentMonOffset=0x4AB34 +FossilTableOffset=0xF6334 +ShopDataPrefix=F11A040249BD030219BD0302FDBC0302 +Arm9CRC32=B1A9B403 +OverlayCRC32<6>=AD2BA4AF +OverlayCRC32<11>=4F5D2535 +OverlayCRC32<59>=52AAB459 +OverlayCRC32<64>=6CC01D0F + +[Diamond (J)] +Game=ADAJ +Type=DP +Version=0 +CopyStaticPokemon=1 +CopyFrom=Diamond (U) +File= +File= +File= +File= +StarterPokemonOffset=0x30 +HiddenItemTableOffset=0xF4C10 +HasExtraPokemonNames=No +PokemonNamesTextOffset=356 +TrainerNamesTextOffset=550 +TrainerClassesTextOffset=551 +MoveDescriptionsTextOffset=574 +MoveNamesTextOffset=575 +AbilityNamesTextOffset=544 +ItemDescriptionsTextOffset=340 +ItemNamesTextOffset=341 +StarterScreenTextOffset=318 +PokedexSpeciesTextOffset=607 +StarterLocationTextOffset=269 +IngameTradesTextOffset=324 +IngameTradePersonTextOffsets=[66,88,170,571] +MapTableARM9Offset=0xF0C28 +MapNamesTextOffset=374 +CatchingTutorialOpponentMonOffset=0x4AB34 +FossilTableOffset=0xF6330 +ShopDataPrefix=F11A040249BD030219BD0302FDBC0302 +Arm9CRC32=2624AED0 +OverlayCRC32<6>=90564DAF +OverlayCRC32<11>=87D3D888 +OverlayCRC32<59>=189D13CA +OverlayCRC32<64>=6F2480C9 + +[Pearl (J Rev 5)] +Game=APAJ +Type=DP +Version=5 +CopyText=1 +CopyStaticPokemon=1 +CopyFrom=Pearl (J) +Arm9CRC32=B1A9B403 +OverlayCRC32<6>=AD2BA4AF +OverlayCRC32<11>=4F5D2535 +OverlayCRC32<59>=52AAB459 +OverlayCRC32<64>=6CC01D0F + +[Pearl (G)] +Game=APAD +Type=DP +Version=5 +CopyStaticPokemon=1 +CopyFrom=Pearl (U) +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/dp_national_dex +HiddenItemTableOffset=0xF2DC4 +MapTableARM9Offset=0xEEDCC +CatchingTutorialOpponentMonOffset=0x479D0 +FossilTableOffset=0xF4520 +DoubleBattleFlagReturnPrefix=08B5092131F02AFB +DoubleBattleWalkingPrefix2=22FB16B00120F8BD +DoubleBattleTextBoxPrefix=F7F706FEF7F720FE +ShopDataPrefix=BD110402CDAF0302EDAF0302DD110402 +Arm9CRC32=672E6E4B +OverlayCRC32<6>=6E45EC08 +OverlayCRC32<11>=A1F171CB +OverlayCRC32<59>=96DC349C +OverlayCRC32<64>=CD34E846 + +[Diamond (G)] +Game=ADAD +Type=DP +Version=5 +CopyStaticPokemon=1 +CopyFrom=Diamond (U) +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/dp_national_dex +HiddenItemTableOffset=0xF2DC4 +MapTableARM9Offset=0xEEDCC +CatchingTutorialOpponentMonOffset=0x479D0 +FossilTableOffset=0xF4520 +DoubleBattleFlagReturnPrefix=08B5092131F02AFB +DoubleBattleWalkingPrefix2=22FB16B00120F8BD +DoubleBattleTextBoxPrefix=F7F706FEF7F720FE +ShopDataPrefix=BD110402CDAF0302EDAF0302DD110402 +Arm9CRC32=6534A1F1 +OverlayCRC32<6>=93FB792F +OverlayCRC32<11>=0C8681F0 +OverlayCRC32<59>=96DC349C +OverlayCRC32<64>=65244084 + +[Pearl (S)] +Game=APAS +Type=DP +Version=5 +CopyStaticPokemon=1 +CopyFrom=Pearl (U) +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/dp_national_dex +HiddenItemTableOffset=0xF2E00 +MapTableARM9Offset=0xEEE08 +CatchingTutorialOpponentMonOffset=0x479D0 +FossilTableOffset=0xF455C +DoubleBattleFlagReturnPrefix=08B5092131F02AFB +DoubleBattleWalkingPrefix2=22FB16B00120F8BD +DoubleBattleTextBoxPrefix=F7F706FEF7F720FE +ShopDataPrefix=BD110402CDAF0302EDAF0302DD110402 +Arm9CRC32=F84C375A +OverlayCRC32<6>=E2BF78A0 +OverlayCRC32<11>=22C6C9CA +OverlayCRC32<59>=B77DAB96 +OverlayCRC32<64>=A92907D8 + +[Diamond (S)] +Game=ADAS +Type=DP +Version=5 +CopyStaticPokemon=1 +CopyFrom=Diamond (U) +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/dp_national_dex +HiddenItemTableOffset=0xF2E00 +MapTableARM9Offset=0xEEE08 +CatchingTutorialOpponentMonOffset=0x479D0 +FossilTableOffset=0xF455C +DoubleBattleFlagReturnPrefix=08B5092131F02AFB +DoubleBattleWalkingPrefix2=22FB16B00120F8BD +DoubleBattleTextBoxPrefix=F7F706FEF7F720FE +ShopDataPrefix=BD110402CDAF0302EDAF0302DD110402 +Arm9CRC32=6A024910 +OverlayCRC32<6>=34FF651E +OverlayCRC32<11>=17D68A91 +OverlayCRC32<59>=B77DAB96 +OverlayCRC32<64>=5CE1EE3C + +[Pearl (I)] +Game=APAI +Type=DP +Version=5 +CopyStaticPokemon=1 +CopyFrom=Pearl (U) +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/dp_national_dex +HiddenItemTableOffset=0xF2D68 +MapTableARM9Offset=0xEED70 +CatchingTutorialOpponentMonOffset=0x479D0 +FossilTableOffset=0xF44C4 +DoubleBattleFlagReturnPrefix=08B5092131F01AFB +DoubleBattleWalkingPrefix2=22FB16B00120F8BD +DoubleBattleTextBoxPrefix=F7F706FEF7F720FE +ShopDataPrefix=BD110402CDAF0302EDAF0302DD110402 +Arm9CRC32=6C854C5F +OverlayCRC32<6>=45958341 +OverlayCRC32<11>=2E88B408 +OverlayCRC32<59>=65BC0057 +OverlayCRC32<64>=AEC1D5A4 + +[Diamond (I)] +Game=ADAI +Type=DP +Version=5 +CopyStaticPokemon=1 +CopyFrom=Diamond (U) +HiddenItemTableOffset=0xF2D68 +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/dp_national_dex +MapTableARM9Offset=0xEED70 +CatchingTutorialOpponentMonOffset=0x479D0 +FossilTableOffset=0xF44C4 +DoubleBattleFlagReturnPrefix=08B5092131F01AFB +DoubleBattleWalkingPrefix2=22FB16B00120F8BD +DoubleBattleTextBoxPrefix=F7F706FEF7F720FE +ShopDataPrefix=BD110402CDAF0302EDAF0302DD110402 +Arm9CRC32=569504C4 +OverlayCRC32<6>=DEAC5AEB +OverlayCRC32<11>=8C0E7676 +OverlayCRC32<59>=65BC0057 +OverlayCRC32<64>=5B093C40 + +[Pearl (F)] +Game=APAF +Type=DP +Version=5 +CopyStaticPokemon=1 +CopyFrom=Pearl (U) +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/dp_national_dex +HiddenItemTableOffset=0xF2DF4 +MapTableARM9Offset=0xEEDFC +CatchingTutorialOpponentMonOffset=0x479D0 +FossilTableOffset=0xF4550 +DoubleBattleFlagReturnPrefix=08B5092131F02AFB +DoubleBattleWalkingPrefix2=22FB16B00120F8BD +DoubleBattleTextBoxPrefix=F7F706FEF7F720FE +ShopDataPrefix=BD110402CDAF0302EDAF0302DD110402 +Arm9CRC32=AD992311 +OverlayCRC32<6>=8934EF2F +OverlayCRC32<11>=53F5A4B0 +OverlayCRC32<59>=95817DEA +OverlayCRC32<64>=143FB16B + +[Diamond (F)] +Game=ADAF +Type=DP +Version=5 +CopyStaticPokemon=1 +CopyFrom=Diamond (U) +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/dp_national_dex +HiddenItemTableOffset=0xF2DF4 +MapTableARM9Offset=0xEEDFC +CatchingTutorialOpponentMonOffset=0x479D0 +FossilTableOffset=0xF4550 +DoubleBattleFlagReturnPrefix=08B5092131F02AFB +DoubleBattleWalkingPrefix2=22FB16B00120F8BD +DoubleBattleTextBoxPrefix=F7F706FEF7F720FE +ShopDataPrefix=BD110402CDAF0302EDAF0302DD110402 +Arm9CRC32=B8EE141C +OverlayCRC32<6>=A5AD136A +OverlayCRC32<11>=A0CA9F36 +OverlayCRC32<59>=95817DEA +OverlayCRC32<64>=34199B6F + +[Pearl (K)] +Game=APAK +Type=DP +Version=0 +CopyStaticPokemon=1 +CopyFrom=Pearl (U) +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/dp_national_dex +HiddenItemTableOffset=0xEE400 +HasExtraPokemonNames=No +PokemonNamesTextOffset=357 +TrainerNamesTextOffset=552 +TrainerClassesTextOffset=553 +MoveDescriptionsTextOffset=576 +MoveNamesTextOffset=577 +AbilityNamesTextOffset=546 +ItemDescriptionsTextOffset=341 +ItemNamesTextOffset=342 +StarterScreenTextOffset=319 +PokedexSpeciesTextOffset=609 +StarterLocationTextOffset=269 +IngameTradesTextOffset=325 +IngameTradePersonTextOffsets=[66,88,170,573] +MapTableARM9Offset=0xEA408 +MapNamesTextOffset=376 +CatchingTutorialOpponentMonOffset=0x47E2C +FossilTableOffset=0xEFB5C +DoubleBattleFlagReturnPrefix=08B5092131F04AFB +DoubleBattleWalkingPrefix2=02FB16B00120F8BD +DoubleBattleTextBoxPrefix=F7F708FEF7F722FE +ShopDataPrefix=E1150402F1B3030211B4030201160402 +Arm9CRC32=E317C09B +OverlayCRC32<6>=37ECE0C0 +OverlayCRC32<11>=A41BD6FC +OverlayCRC32<59>=E3B4A7FF +OverlayCRC32<64>=4431AA3B + +[Diamond (K)] +Game=ADAK +Type=DP +Version=0 +CopyStaticPokemon=1 +CopyFrom=Diamond (U) +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/dp_national_dex +HiddenItemTableOffset=0xEE400 +HasExtraPokemonNames=No +PokemonNamesTextOffset=357 +TrainerNamesTextOffset=552 +TrainerClassesTextOffset=553 +MoveDescriptionsTextOffset=576 +MoveNamesTextOffset=577 +AbilityNamesTextOffset=546 +ItemDescriptionsTextOffset=341 +ItemNamesTextOffset=342 +StarterScreenTextOffset=319 +PokedexSpeciesTextOffset=609 +StarterLocationTextOffset=269 +IngameTradesTextOffset=325 +IngameTradePersonTextOffsets=[66,88,170,573] +MapTableARM9Offset=0xEA408 +MapNamesTextOffset=376 +CatchingTutorialOpponentMonOffset=0x47E2C +FossilTableOffset=0xEFB5C +DoubleBattleFlagReturnPrefix=08B5092131F04AFB +DoubleBattleWalkingPrefix2=02FB16B00120F8BD +DoubleBattleTextBoxPrefix=F7F708FEF7F722FE +ShopDataPrefix=E1150402F1B3030211B4030201160402 +Arm9CRC32=E50BF4B5 +OverlayCRC32<6>=4681ECC3 +OverlayCRC32<11>=3D2D9752 +OverlayCRC32<59>=E3B4A7FF +OverlayCRC32<64>=6417803F + +[Platinum (J)] +Game=CPUJ +Type=Plat +Version=0 +CopyStaticPokemon=1 +CopyFrom=Platinum (U) +File= +File= +File= +File= +HiddenItemTableOffset=0xE9A4C +MoveTutorMovesOffset=0x2FD54 +MoveTutorCompatOffset=0x2FF1C +StarterPokemonOffset=0x1BAC +HasExtraPokemonNames=No +PokemonNamesTextOffset=408 +TrainerNamesTextOffset=611 +TrainerClassesTextOffset=612 +MoveDescriptionsTextOffset=635 +MoveNamesTextOffset=636 +AbilityNamesTextOffset=604 +ItemDescriptionsTextOffset=389 +ItemNamesTextOffset=390 +StarterScreenTextOffset=359 +PokedexSpeciesTextOffset=698 +StarterLocationTextOffset=460 +IngameTradesTextOffset=369 +IngameTradePersonTextOffsets=[73,96,179,632] +MapTableARM9Offset=0xE56F0 +MapNamesTextOffset=427 +CatchingTutorialOpponentMonOffset=0x51980 +FossilTableOffset=0xEB68C +DoubleBattleFlagReturnPrefix=08B5092139F054FE +DoubleBattleWalkingPrefix2=3CF816B00120F8BD +DoubleBattleTextBoxPrefix=F6F7FEFDF6F718FE +ShopDataPrefix=E5691F0271450402A5450402A1200402 +FastDistortionWorldTweak=pt_fast_distortion_world +Arm9CRC32=9370B1BD +OverlayCRC32<5>=D045CE6A +OverlayCRC32<6>=D5C60661 +OverlayCRC32<16>=13CDEC92 +OverlayCRC32<73>=8FB18796 +OverlayCRC32<78>=ABCE5F9F + +[Platinum (G)] +Game=CPUD +Type=Plat +Version=0 +CopyStaticPokemon=1 +CopyFrom=Platinum (U) +File= +File= +File= +NationalDexAtStartTweak=national_dex/plat_national_dex +HiddenItemTableOffset=0xEA3D0 +MoveTutorMovesOffset=0x2FF80 +MoveTutorCompatOffset=0x30148 +MapTableARM9Offset=0xE6074 +CatchingTutorialOpponentMonOffset=0x52144 +FossilTableOffset=0xEC054 +DoubleBattleFlagReturnPrefix=08B5092139F0C4FF +ShopDataPrefix=91800402C98004020581040225810402 +FastDistortionWorldTweak=pt_fast_distortion_world +Arm9CRC32=14AC281F +OverlayCRC32<5>=9ABB1B3D +OverlayCRC32<6>=531E0103 +OverlayCRC32<16>=81FBB5A9 +OverlayCRC32<73>=78321B58 +OverlayCRC32<78>=BB90F646 + +[Platinum (F)] +Game=CPUF +Type=Plat +Version=0 +CopyStaticPokemon=1 +CopyFrom=Platinum (U) +File= +File= +File= +NationalDexAtStartTweak=national_dex/plat_national_dex +HiddenItemTableOffset=0xEA400 +MoveTutorMovesOffset=0x2FF6C +MoveTutorCompatOffset=0x30134 +MapTableARM9Offset=0xE60A4 +CatchingTutorialOpponentMonOffset=0x52144 +FossilTableOffset=0xEC084 +DoubleBattleFlagReturnPrefix=08B5092139F0C4FF +ShopDataPrefix=91800402C98004020581040225810402 +FastDistortionWorldTweak=pt_fast_distortion_world +Arm9CRC32=C0B29D1E +OverlayCRC32<5>=7157FFE7 +OverlayCRC32<6>=0FCDB778 +OverlayCRC32<16>=495B8746 +OverlayCRC32<73>=CD2E3918 +OverlayCRC32<78>=5181F86B + +[Platinum (S)] +Game=CPUS +Type=Plat +Version=0 +CopyStaticPokemon=1 +CopyFrom=Platinum (U) +File= +File= +File= +NationalDexAtStartTweak=national_dex/plat_national_dex +HiddenItemTableOffset=0xEA40C +MoveTutorMovesOffset=0x2FF6C +MoveTutorCompatOffset=0x30134 +MapTableARM9Offset=0xE60B0 +CatchingTutorialOpponentMonOffset=0x52144 +FossilTableOffset=0xEC090 +DoubleBattleFlagReturnPrefix=08B5092139F0C4FF +ShopDataPrefix=91800402C98004020581040225810402 +FastDistortionWorldTweak=pt_fast_distortion_world +Arm9CRC32=D3F8273F +OverlayCRC32<5>=C4A31B48 +OverlayCRC32<6>=0E93E266 +OverlayCRC32<16>=1BBB41F1 +OverlayCRC32<73>=0F84AAEE +OverlayCRC32<78>=07F2C593 + +[Platinum (I)] +Game=CPUI +Type=Plat +Version=0 +CopyStaticPokemon=1 +CopyFrom=Platinum (U) +File= +File= +File= +NationalDexAtStartTweak=national_dex/plat_national_dex +HiddenItemTableOffset=0xEA394 +MoveTutorMovesOffset=0x2FF74 +MoveTutorCompatOffset=0x3013C +MapTableARM9Offset=0xE6038 +CatchingTutorialOpponentMonOffset=0x52144 +FossilTableOffset=0xEC018 +DoubleBattleFlagReturnPrefix=08B5092139F0C4FF +ShopDataPrefix=91800402C98004020581040225810402 +FastDistortionWorldTweak=pt_fast_distortion_world +Arm9CRC32=EDD15660 +OverlayCRC32<5>=DAD8DD1C +OverlayCRC32<6>=5664CD24 +OverlayCRC32<16>=3528E1D6 +OverlayCRC32<73>=03562E3A +OverlayCRC32<78>=A99B6322 + +[Platinum (K)] +Game=CPUK +Type=Plat +Version=0 +CopyStaticPokemon=1 +CopyFrom=Platinum (U) +File= +File= +File= +NationalDexAtStartTweak=national_dex/plat_national_dex +HiddenItemTableOffset=0xEAE00 +MoveTutorMovesOffset=0x2FF5C +MoveTutorCompatOffset=0x30124 +HasExtraPokemonNames=No +PokemonNamesTextOffset=408 +TrainerNamesTextOffset=612 +TrainerClassesTextOffset=613 +MoveDescriptionsTextOffset=636 +MoveNamesTextOffset=637 +AbilityNamesTextOffset=605 +ItemDescriptionsTextOffset=389 +ItemNamesTextOffset=390 +StarterScreenTextOffset=359 +PokedexSpeciesTextOffset=701 +StarterLocationTextOffset=461 +IngameTradesTextOffset=369 +IngameTradePersonTextOffsets=[73,96,179,633] +MapTableARM9Offset=0xE6AA4 +MapNamesTextOffset=428 +CatchingTutorialOpponentMonOffset=0x52594 +FossilTableOffset=0xECA84 +DoubleBattleFlagReturnPrefix=08B5092139F0C4FF +DoubleBattleWalkingPrefix2=C0FE16B00120F8BD +DoubleBattleTextBoxPrefix=F6F790FCF6F7AAFC +ShopDataPrefix=E1840402198504025585040275850402 +FastDistortionWorldTweak=pt_fast_distortion_world +Arm9CRC32=BAE2AD4B +OverlayCRC32<5>=CADD3A64 +OverlayCRC32<6>=CFF5136D +OverlayCRC32<16>=14CC7DEA +OverlayCRC32<73>=D891EA37 +OverlayCRC32<78>=923ACAED + +[HeartGold (J)] +Game=IPKJ +Type=HGSS +Version=0 +CopyFrom=HeartGold (U) +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +HiddenItemTableOffset=0xF9D08 +MoveTutorMovesOffset=0x23954 +HasExtraPokemonNames=No +PokemonNamesTextOffset=232 +TrainerNamesTextOffset=719 +TrainerClassesTextOffset=720 +MoveDescriptionsTextOffset=738 +MoveNamesTextOffset=739 +AbilityNamesTextOffset=711 +ItemDescriptionsTextOffset=218 +ItemNamesTextOffset=219 +StarterScreenTextOffset=188 +IngameTradesTextOffset=198 +IngameTradePersonTextOffsets=[554,588,599,625,0,0,337,456,527,45,529] +MapTableARM9Offset=0xF6390 +MapNamesTextOffset=272 +CatchingTutorialPlayerMonOffset=0x51610 +CatchingTutorialPlayerLevelOffset=0x51612 +CatchingTutorialOpponentMonOffset=0x51632 +FossilLevelScriptNumber=753 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[104:0x108], Level=[104:0x138, 104:0x12C]} // Lugia +StaticPokemon{}={Species=[21:0xD1], Level=[21:0xF5, 21:0x101]} // Ho-oh +StaticPokemon{}={Species=[216:0x58F, 216:0x6E8, 216:0x708, 24:0x67, 24:0xB4, 24:0x314, 24:0x320, 24:0xD4], Level=[216:0x70A, 24:0x322]} // Suicune +StaticPokemon{}={Species=[14:0x2F, 14:0x3B], Level=[14:0x3D]} // Articuno +StaticPokemon{}={Species=[191:0x26B, 191:0x277], Level=[191:0x279]} // Zapdos +StaticPokemon{}={Species=[106:0x2F, 106:0x3B], Level=[106:0x3D]} // Moltres +StaticPokemon{}={Species=[11:0x2F, 11:0x3B], Level=[11:0x3D]} // Mewtwo +StaticPokemon{}={Species=[134:0xA3, 134:0xB4], Level=[134:0xB6]} // Kyogre +StaticPokemon{}={Species=[133:0xA3, 133:0xB4], Level=[133:0xB6]} // Groudon +StaticPokemon{}={Species=[135:0xDA, 135:0xEB, 135:0x62, 135:0x98], Level=[135:0xED]} // Rayquaza +StaticPokemon{}={Species=[131:0x43A, 131:0x67C, 131:0x872, 131:0x8E4, 131:0x958, 131:0x963], Level=[131:0x965]} // Dialga +StaticPokemon{}={Species=[131:0x4A2, 131:0x695, 131:0x88D, 131:0x8FA, 131:0x97F, 131:0x98A], Level=[131:0x98C]} // Palkia +StaticPokemon{}={Species=[131:0x50A, 131:0x9A4], Level=[131:0x9A6], Forme=[131:0x9AA]} // Giratina-O +StaticPokemon{}={Species=[748:0x4CC], Level=[748:0x4E3]} // Latias +StaticPokemon{}={Species=[748:0x4B7], Level=[748:0x4E3]} // Latios +StaticPokemon{}={Species=[243:0x310, 243:0x14B], Level=[243:0x312, 243:0x14D]} // Sudowoodo +StaticPokemon{}={Species=[58:0x61, 58:0x6D], Level=[58:0x6F]} // Lapras +StaticPokemon{}={Species=[934:0x3CD, 934:0x3DE], Level=[934:0x3E0]} // Red Gyarados +StaticPokemon{}={Species=[197:0x6C, 197:0x7D, 199:0x26A, 199:0x27B], Level=[197:0x7F, 199:0x27D]} // Snorlax +StaticPokemon{}={Species=[89:0xF3D, 89:0x1078, 89:0x10A5, 89:0x112C, 89:0x11B3], Level=[89:0xF3F, 89:0x107A, 89:0x10A7, 89:0x112E, 89:0x11B5]} // Koffing @ Rocket Base +StaticPokemon{}={Species=[89:0xF6A, 89:0xFC4, 89:0x101E, 89:0x104B, 89:0x1159, 89:0x1186], Level=[89:0xF6C, 89:0xFC6, 89:0x1020, 89:0x104D, 89:0x115B, 89:0x1188]} // Voltorb @ Rocket Base +StaticPokemon{}={Species=[89:0xF97, 89:0xFF1, 89:0x10D2, 89:0x10FF, 89:0x11E0], Level=[89:0xF99, 89:0xFF3, 89:0x10D4, 89:0x1101, 89:0x11E2]} // Geodude @ Rocket Base +StaticPokemon{}={Species=[90:0x770], Level=[90:0x772]} // Electrode @ Rocket Base (1) +StaticPokemon{}={Species=[90:0x7D4], Level=[90:0x7D6]} // Electrode @ Rocket Base (2) +StaticPokemon{}={Species=[90:0x838], Level=[90:0x83A]} // Electrode @ Rocket Base (3) +StaticPokemon{}={Species=[889:0x61], Level=[889:0x63]} // Eevee +StaticPokemon{}={Species=[98:0x71], Level=[98:0x73]} // Tyrogue +StaticPokemon{}={Species=[112:0x4D1], Level=[112:0x4D3]} // Dratini +StaticPokemon{}={Species=[738:0x66F, 738:0x675, 738:0x695, 738:0x818, 738:0x8BC], Level=[738:0x86D]} // Bulbasaur +StaticPokemon{}={Species=[738:0x71D, 738:0x723, 738:0x743, 738:0x833, 738:0x8D7], Level=[738:0x86D]} // Squirtle +StaticPokemon{}={Species=[738:0x7CB, 738:0x7D1, 738:0x7F1], Level=[738:0x86D]} // Charmander +StaticPokemon{}={Species=[834:0x272], Level=[834:0x2B4]} // Treecko +StaticPokemon{}={Species=[834:0x28B], Level=[834:0x2B4]} // Torchic +StaticPokemon{}={Species=[834:0x297], Level=[834:0x2B4]} // Mudkip +StaticPokemon{}={Species=[857:0x146, 857:0x14D]]} // Primo's Mareep Egg +StaticPokemon{}={Species=[857:0x180, 857:0x187]]} // Primo's Wooper Egg +StaticPokemon{}={Species=[857:0x1BA, 857:0x1C1]]} // Primo's Slugma Egg +StaticPokemon{}={Species=[875:0x90], Level=[875:0x92]} // Secret Tentacool +StaticPokemonGameCorner{}={Species=[903:0xF8B, 903:0x101A, 903:0x10BA], Level=[903:0x109E], Text=[591:0x31]} // Abra +StaticPokemonGameCorner{}={Species=[903:0xF97, 903:0x103E, 903:0x10D1], Level=[903:0x109E], Text=[591:0x32]} // Ekans +StaticPokemonGameCorner{}={Species=[903:0xFAF, 903:0x1062, 903:0x10E8], Level=[903:0x109E], Text=[591:0x34]} // Sandshrew +StaticPokemonGameCorner{}={Species=[903:0xFA3], Level=[903:0x109E], Text=[591:0x33]} // Dratini +StaticPokemonGameCorner{}={Species=[802:0x875, 802:0x8DB, 802:0x957], Level=[802:0x93B], Text=[502:0x21]} // Mr. Mime +StaticPokemonGameCorner{}={Species=[802:0x881, 802:0x8FF, 802:0x96E], Level=[802:0x93B], Text=[502:0x22]} // Eevee +StaticPokemonGameCorner{}={Species=[802:0x88D], Level=[802:0x93B], Text=[502:0x23]} // Porygon +StaticPokemonTrades=[6,7] // Shuckie & Kenya +StaticPokemonTradeScripts=[877,241] +StaticPokemonTradeLevelOffsets=[0x80,0xA7] +KenyaTextOffset=381 +MysteryEggOffset=0x1C692 // Togepi Mystery Egg +StaticEggPokemonOffsets=[34, 35, 36] +MarillCryScripts=[93:0x66, 225:0x1B8, 839:0x16C6, 846:0x1AE] +MarillTextFiles=[] +DoubleBattleFlagReturnPrefix=08B5092132F058FE +DoubleBattleWalkingPrefix2=14F816B00120F8BD +DoubleBattleTextBoxPrefix=F6F7F0FFF7F70AF8 +ShopDataPrefix=E57C040235770402CD1C200251320402 +TMTextGameCorner{}={90=[591:39], 75=[591:40], 44=[591:41], 35=[591:42], 13=[591:43], 24=[591:44]} // Goldenrod +TMTextGameCorner{}={58=[502:23], 32=[502:24], 10=[502:25], 29=[502:26], 74=[502:27], 68=[502:28]} // Celadon +Arm9CRC32=BA386530 +OverlayCRC32<1>=513BF822 +OverlayCRC32<12>=C95025DF +OverlayCRC32<21>=7874DA2E +OverlayCRC32<61>=F45FB204 + +[SoulSilver (J)] +Game=IPGJ +Type=HGSS +Version=0 +CopyStaticPokemon=1 +CopyFrom=HeartGold (J) +File= +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +Arm9CRC32=C537A4E3 +OverlayCRC32<1>=00255396 +OverlayCRC32<12>=AA71062F +OverlayCRC32<21>=7874DA2E +OverlayCRC32<61>=F45FB204 + +[HeartGold (K)] +Game=IPKK +Type=HGSS +Version=0 +IgnoreGameCornerStatics=1 +CopyStaticPokemon=1 +CopyFrom=HeartGold (U) +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +HasExtraPokemonNames=No +HiddenItemTableOffset=0xFAC04 +PokemonNamesTextOffset=233 +TrainerNamesTextOffset=723 +TrainerClassesTextOffset=724 +MoveDescriptionsTextOffset=742 +MoveNamesTextOffset=743 +AbilityNamesTextOffset=715 +ItemDescriptionsTextOffset=219 +ItemNamesTextOffset=220 +StarterScreenTextOffset=189 +IngameTradesTextOffset=199 +IngameTradePersonTextOffsets=[557,591,603,629,0,0,339,458,530,46,532] +MapTableARM9Offset=0xF728C +MapNamesTextOffset=274 +CatchingTutorialPlayerMonOffset=0x51C74 +CatchingTutorialPlayerLevelOffset=0x51C76 +CatchingTutorialOpponentMonOffset=0x51C96 +MarillTextFiles=[] +DoubleBattleFlagReturnPrefix=08B5092132F0E6FE +DoubleBattleWalkingPrefix2=6AFF16B00120F8BD +DoubleBattleTextBoxPrefix=F6F762FFF6F77CFF +ShopDataPrefix=858F04028D8F0402958F0402B58F0402 +Arm9CRC32=DD15025F +OverlayCRC32<1>=485A49F3 +OverlayCRC32<12>=926F3029 +OverlayCRC32<21>=A2FA11EE +OverlayCRC32<61>=53119D58 + +[SoulSilver (K)] +Game=IPGK +Type=HGSS +Version=0 +IgnoreGameCornerStatics=1 +CopyStaticPokemon=1 +CopyFrom=SoulSilver (U) +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +HasExtraPokemonNames=No +HiddenItemTableOffset=0xFABFC +PokemonNamesTextOffset=233 +TrainerNamesTextOffset=723 +TrainerClassesTextOffset=724 +MoveDescriptionsTextOffset=742 +MoveNamesTextOffset=743 +AbilityNamesTextOffset=715 +ItemDescriptionsTextOffset=219 +ItemNamesTextOffset=220 +StarterScreenTextOffset=189 +IngameTradesTextOffset=199 +IngameTradePersonTextOffsets=[557,591,603,629,0,0,339,458,530,46,532] +MapTableARM9Offset=0xF7284 +MapNamesTextOffset=274 +CatchingTutorialPlayerMonOffset=0x51C6C +CatchingTutorialPlayerLevelOffset=0x51C6E +CatchingTutorialOpponentMonOffset=0x51C8E +MarillTextFiles=[] +DoubleBattleFlagReturnPrefix=08B5092132F0E6FE +DoubleBattleWalkingPrefix2=6AFF16B00120F8BD +DoubleBattleTextBoxPrefix=F6F762FFF6F77CFF +ShopDataPrefix=7D8F0402858F04028D8F0402AD8F0402 +Arm9CRC32=F1C4716F +OverlayCRC32<1>=AAC2EFA7 +OverlayCRC32<12>=EE7F9555 +OverlayCRC32<21>=D2DAA298 +OverlayCRC32<61>=F1C15D1F + +[HeartGold (F)] +Game=IPKF +Type=HGSS +Version=0 +CopyText=1 +CopyStaticPokemon=1 +CopyFrom=HeartGold (U) +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +HiddenItemTableOffset=0xFA53C +MapTableARM9Offset=0xF6BC4 +CatchingTutorialPlayerMonOffset=0x51B78 +CatchingTutorialPlayerLevelOffset=0x51B7A +CatchingTutorialOpponentMonOffset=0x51B9A +ShopDataPrefix=298E0402618E0402998E0402B98E0402 +Arm9CRC32=590080BF +OverlayCRC32<1>=6DD56808 +OverlayCRC32<12>=43574EA6 +OverlayCRC32<21>=5045E946 +OverlayCRC32<61>=62BC379B + +[SoulSilver (F)] +Game=IPGF +Type=HGSS +Version=0 +CopyText=1 +CopyStaticPokemon=1 +CopyFrom=SoulSilver (U) +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +HiddenItemTableOffset=0xFA53C +MapTableARM9Offset=0xF6BC4 +CatchingTutorialPlayerMonOffset=0x51B78 +CatchingTutorialPlayerLevelOffset=0x51B7A +CatchingTutorialOpponentMonOffset=0x51B9A +ShopDataPrefix=298E0402618E0402998E0402B98E0402 +Arm9CRC32=A55C566F +OverlayCRC32<1>=5B0C8E3F +OverlayCRC32<12>=BDF17AFF +OverlayCRC32<21>=5045E946 +OverlayCRC32<61>=62BC379B + +[HeartGold (G)] +Game=IPKD +Type=HGSS +Version=0 +CopyText=1 +CopyStaticPokemon=1 +CopyFrom=HeartGold (U) +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +HiddenItemTableOffset=0xFA50C +MapTableARM9Offset=0xF6B94 +CatchingTutorialPlayerMonOffset=0x51B78 +CatchingTutorialPlayerLevelOffset=0x51B7A +CatchingTutorialOpponentMonOffset=0x51B9A +ShopDataPrefix=298E0402618E0402998E0402B98E0402 +Arm9CRC32=010DE166 +OverlayCRC32<1>=03A114D2 +OverlayCRC32<12>=8F59BA1A +OverlayCRC32<21>=8B953722 +OverlayCRC32<61>=09E99828 + +[SoulSilver (G)] +Game=IPGD +Type=HGSS +Version=0 +CopyText=1 +CopyStaticPokemon=1 +CopyFrom=SoulSilver (U) +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +HiddenItemTableOffset=0xFA50C +MapTableARM9Offset=0xF6B94 +CatchingTutorialPlayerMonOffset=0x51B78 +CatchingTutorialPlayerLevelOffset=0x51B7A +CatchingTutorialOpponentMonOffset=0x51B9A +ShopDataPrefix=298E0402618E0402998E0402B98E0402 +Arm9CRC32=7133E536 +OverlayCRC32<1>=3578F2E5 +OverlayCRC32<12>=71FF8E43 +OverlayCRC32<21>=8B953722 +OverlayCRC32<61>=09E99828 + +[HeartGold (S)] +Game=IPKS +Type=HGSS +Version=0 +CopyText=1 +CopyStaticPokemon=1 +CopyFrom=HeartGold (U) +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +HiddenItemTableOffset=0xFA540 +MapTableARM9Offset=0xF6BC8 +CatchingTutorialPlayerMonOffset=0x51B70 +CatchingTutorialPlayerLevelOffset=0x51B72 +CatchingTutorialOpponentMonOffset=0x51B92 +ShopDataPrefix=218E0402598E0402918E0402B18E0402 +Arm9CRC32=E44F2901 +OverlayCRC32<1>=76637802 +OverlayCRC32<12>=1BE62592 +OverlayCRC32<21>=0788415E +OverlayCRC32<61>=27EDB088 + +[SoulSilver (S)] +Game=IPGS +Type=HGSS +Version=0 +CopyText=1 +CopyStaticPokemon=1 +CopyFrom=SoulSilver (U) +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +HiddenItemTableOffset=0xFA548 +MapTableARM9Offset=0xF6BD0 +CatchingTutorialPlayerMonOffset=0x51B78 +CatchingTutorialPlayerLevelOffset=0x51B7A +CatchingTutorialOpponentMonOffset=0x51B9A +ShopDataPrefix=298E0402618E0402998E0402B98E0402 +Arm9CRC32=E705FE48 +OverlayCRC32<1>=05057EF4 +OverlayCRC32<12>=DD0D85AD +OverlayCRC32<21>=7FD40F84 +OverlayCRC32<61>=88DA5446 + +[HeartGold (I)] +Game=IPKI +Type=HGSS +Version=0 +CopyText=1 +CopyStaticPokemon=1 +CopyFrom=HeartGold (U) +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +HiddenItemTableOffset=0xFA4D0 +MapTableARM9Offset=0xF6B58 +CatchingTutorialPlayerMonOffset=0x51B78 +CatchingTutorialPlayerLevelOffset=0x51B7A +CatchingTutorialOpponentMonOffset=0x51B9A +ShopDataPrefix=298E0402618E0402998E0402B98E0402 +Arm9CRC32=A200E7D3 +OverlayCRC32<1>=51DB5337 +OverlayCRC32<12>=B91C4DD4 +OverlayCRC32<21>=F71A0EFA +OverlayCRC32<61>=C4A4AED5 + +[SoulSilver (I)] +Game=IPGI +Type=HGSS +Version=0 +CopyText=1 +CopyStaticPokemon=1 +CopyFrom=SoulSilver (U) +File= +File= +NationalDexAtStartTweak=national_dex/hgss_national_dex +HiddenItemTableOffset=0xFA4D0 +MapTableARM9Offset=0xF6B58 +CatchingTutorialPlayerMonOffset=0x51B78 +CatchingTutorialPlayerLevelOffset=0x51B7A +CatchingTutorialOpponentMonOffset=0x51B9A +ShopDataPrefix=298E0402618E0402998E0402B98E0402 +Arm9CRC32=9A50D8E0 +OverlayCRC32<1>=6702B500 +OverlayCRC32<12>=47BA798D +OverlayCRC32<21>=F71A0EFA +OverlayCRC32<61>=C4A4AED5 diff --git a/src/com/pkrandom/config/gen5_offsets.ini b/src/com/pkrandom/config/gen5_offsets.ini new file mode 100755 index 0000000..b92c16f --- /dev/null +++ b/src/com/pkrandom/config/gen5_offsets.ini @@ -0,0 +1,957 @@ +[Black (U)] +Game=IRBO +Type=BW1 +Version=0 +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +RoamerOvlNumber=10 +FieldOvlNumber=21 +ShopItemOvlNumber=21 +IntroCryOvlNumber=88 +PickupOvlNumber=92 +BattleOvlNumber=93 +LowHealthMusicOvlNumber=94 +EvolutionOvlNumber=195 +IntroGraphicOvlNumber=204 +StarterCryOvlNumber=223 +FastestTextTweak=instant_text/b1_instant_text +NewIndexToMusicTweak=musicfix/black_musicfix +NewIndexToMusicOvlTweak=musicfix/black_ovl21_musicfix +ShedinjaEvolutionTweak=shedinja/black_shedinja +ShedinjaEvolutionOvlTweak=shedinja/black_ovl195_shedinja +NationalDexAtStartTweak=national_dex/bw1_national_dex +TradesUnused=[1,3,7,8,9,10,11,12] +StarterOffsets1=[782:639, 782:644, 782:0x361, 782:0x5FD, 304:0xF9, 304:0x19C] +StarterOffsets2=[782:687, 782:692, 782:0x356, 782:0x5F2, 304:0x11C, 304:0x1C4] +StarterOffsets3=[782:716, 782:721, 782:0x338, 782:0x5D4, 304:0x12C, 304:0x1D9] +StarterLocationTextOffset=430 +StarterCryTablePrefix=080A0700080000 +PokedexGivenFileOffset=792 +PokemonNamesTextOffset=70 +TrainerNamesTextOffset=190 +TrainerClassesTextOffset=191 +DoublesTrainerClasses=[16, 63, 68, 69] +EliteFourIndices=[228, 229, 230, 231, 232, 587] +TrainerMugshotsTextOffset=176 +MoveDescriptionsTextOffset=202 +MoveNamesTextOffset=203 +AbilityNamesTextOffset=182 +ItemDescriptionsTextOffset=53 +ItemNamesTextOffset=54 +IngameTradesTextOffset=35 +LuckyEggScriptOffset=390 +ItemBallsScriptOffset=864 +HiddenItemsScriptOffset=865 +MapNamesTextOffset=89 +ShopItemOffsets=[0x51538,0x5153C,0x51546,0x5154C,0x51564,0x51590,0x515E4,0x515F2,0x51600,0x51610,0x51620,0x51630,0x51640,0x51650,0x51662,0x51674,0x51686,0x5169C,0x516B2,0x516C8,0x516F8,0x51714,0x51734,0x51774,0x51796,0x517BA] +ShopItemSizes=[2,2,3,4,4,6,7,7,8,8,8,8,8,9,9,9,11,11,11,11,14,16,16,17,18,19] +ShopCount=26 +TMShops=[1,2,4,7] +RegularShops=[0,16,20,23,24,25] +ItemBallsSkip=[] +HiddenItemsSkip=[] +NationalDexScriptOffset=792 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[304:0x121, 304:0x1C9, 304:0x299], Level=[304:0x29D]} // Pansage +StaticPokemon{}={Species=[304:0x131, 304:0x1DE, 304:0x2B7], Level=[304:0x2BB]} // Pansear +StaticPokemon{}={Species=[304:0xFE, 304:0x1A1, 304:0x268], Level=[304:0x26C]} // Panpour +StaticPokemon{}={Species=[526:0x758], Level=[526:0x75C]} // Magikarp +StaticPokemon{}={Species=[94:0x810, 94:0x64, 94:0xB4, 94:0x44B, 94:0x7AB, 94:0x7D0, 94:0x7DC], Level=[94:0x44F]} // Zorua +StaticPokemon{}={Species=[776:0x85, 776:0xB2]} // Larvesta (egg) +StaticPokemon{}={Species=[316:0x369], Level=[316:0x36B]} // Darmanitan 1 +StaticPokemon{}={Species=[316:0x437], Level=[316:0x439]} // Darmanitan 2 +StaticPokemon{}={Species=[316:0x505], Level=[316:0x507]} // Darmanitan 3 +StaticPokemon{}={Species=[316:0x5D3], Level=[316:0x5D5]} // Darmanitan 4 +StaticPokemon{}={Species=[316:0x6A1], Level=[316:0x6A3]} // Darmanitan 5 +StaticPokemon{}={Species=[306:0x65, 306:0x8F], Level=[306:0x91]} // Musharna +StaticPokemon{}={Species=[770:0x2F8, 770:0x353], Level=[770:0x355]} // Zoroark +StaticPokemon{}={Species=[364:0xE, 364:0x1F], Level=[364:0x21]} // Volcarona +StaticPokemon{}={Species=[474:0x1CE, 474:0x20A], Level=[474:0x20C]} // Victini +StaticPokemon{}={Species=[426:0x133, 426:0x15B, 556:0x1841, 556:0xCFC, 556:0x1878, 556:0x18EA], Level=[426:0x15D, 556:0xCFE]} // Reshiram +StaticPokemon{}={Species=[426:0x127, 426:0x174, 556:0x184D, 556:0x186C, 556:0xD15, 556:0x18DE], Level=[426:0x176, 556:0xD17]} // Zekrom +StaticPokemon{}={Species=[670:0x415, 670:0x426, 692:0x1E2], Level=[670:0x428]} // Cobalion +StaticPokemon{}={Species=[458:0x10, 458:0x21, 692:0x203], Level=[458:0x23]} // Terrakion +StaticPokemon{}={Species=[312:0x10, 312:0x21, 692:0x224], Level=[312:0x23]} // Virizion +StaticPokemon{}={Species=[752:0x66D, 752:0x6CC, 752:0x6DD], Level=[752:0x6DF]} // Landorus +StaticPokemon{}={Species=[464:0x10, 468:0x4F, 468:0x60], Level=[468:0x62]} // Kyurem +StaticPokemon{}={Species=[877:0x601], Level=[877:0x3F7]} // Cranidos +StaticPokemon{}={Species=[877:0x620], Level=[877:0x3F7]} // Shieldon +StaticPokemon{}={Species=[877:0x63F], Level=[877:0x3F7]} // Omanyte +StaticPokemon{}={Species=[877:0x65E], Level=[877:0x3F7]} // Kabuto +StaticPokemon{}={Species=[877:0x67D], Level=[877:0x3F7]} // Aerodactyl +StaticPokemon{}={Species=[877:0x69C], Level=[877:0x3F7]} // Anorith +StaticPokemon{}={Species=[877:0x6BB], Level=[877:0x3F7]} // Lileep +StaticPokemon{}={Species=[877:0x6DA], Level=[877:0x3F7]} // Tirtouga +StaticPokemon{}={Species=[877:0x6F9], Level=[877:0x3F7]} // Archen +StaticPokemonFakeBall{}={Species=[897:0x45], Level=[331:0x2AA, 331:0x2CE, 355:0x196, 355:0x2DA]} // Foongus +StaticPokemonFakeBall{}={Species=[897:0xC1], Level=[355:0x24A, 355:0x26E]} // Amoonguss +RoamingPokemon{}={Species=[0x95D8, 0x940C], Level=[0x930E], Script=[674:0x57E, 674:0x5F1]} // Thundurus +RoamingPokemon{}={Species=[0x95DC, 0x9410], Level=[0x930E], Script=[674:0x572, 674:0x5DC]} // Tornadus +GetRoamerFlagOffsetStartOffset=0x95C4 +BoxLegendaryOffset=15 +IsBlack=1 +TradeScript[]=[46:0x81:0x86, 46:0x97:0x9C] // Cottonee/Petilil +TradeScript[]=[202:0x224:0x21F] // Minccino/Basculin +TradeScript[]=[686:0x76:0x71] // Boldore/Emolga +TradeScript[]=[830:0xB3:0xAE, 830:0xEA:0xE5, 830:0x114:0x10F] // Cinccino/Munchlax +TradeScript[]=[764:0x43:0x3E] // Ditto/Rotom +StaticEggPokemonOffsets=[5] +MainGameLegendaries=[643,644] +Arm9ExtensionSize=300 // 252 for music, 48 for Shedinja +TCMCopyingPrefix=1030A0E3013053E2FDFFFF1AF8FFFFEA +NewIndexToMusicPrefix=208020202860FFE7012002BC08470000 +SpecialMusicStatics=[494,571,637,638,639,640,641,643,645,646] +ShedinjaSpeciesOffset=0xA67C8 +TrainerOverworldTextBoxPrefix=0004000C03D10320 +DoubleBattleLimitPrefix=321C26E0012E17D1 +DoubleBattleGetPointerPrefix=0A9906980904090C +BeqToSingleTrainerNumber=0xD0D1 +TextBoxChoicePrefix=6088B0420AD1A088 +Arm9CRC32=69BC03D6 +OverlayCRC32<10>=F8D7B6CA +OverlayCRC32<21>=F2B9DC42 +OverlayCRC32<88>=26A8C2E1 +OverlayCRC32<92>=D4CF0B58 +OverlayCRC32<93>=A36EECA9 +OverlayCRC32<94>=58394A4F +OverlayCRC32<195>=E24CC7A8 +OverlayCRC32<204>=798061E6 +OverlayCRC32<223>=4FFD3D7F + +[White (U)] +Game=IRAO +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyRoamingPokemon=1 +CopyTradeScripts=1 +CopyFrom=IRBO +File= +File= +FastestTextTweak=instant_text/w1_instant_text +NewIndexToMusicTweak=musicfix/white_musicfix +NewIndexToMusicOvlTweak=musicfix/white_ovl21_musicfix +ShedinjaEvolutionTweak=shedinja/white_shedinja +ShedinjaEvolutionOvlTweak=shedinja/white_ovl195_shedinja +NationalDexAtStartTweak=national_dex/bw1_national_dex +TradesUnused=[0,2,7,8,9,10,11,12] +ShopItemOffsets=[0x51530,0x51534,0x5153E,0x51544,0x5155C,0x51588,0x515DC,0x515EA,0x515F8,0x51608,0x51618,0x51628,0x51638,0x51648,0x5165A,0x5166C,0x5167E,0x51694,0x516AA,0x516C0,0x516F0,0x5170C,0x5172C,0x5176C,0x5178E,0x517B2] +BoxLegendaryOffset=16 +EliteFourIndices=[228, 229, 230, 231, 232, 586] +IsBlack=0 +SpecialMusicStatics=[494,571,637,638,639,640,642,644,645,646] +ShedinjaSpeciesOffset=0xA67E8 +Arm9CRC32=A6BA89D8 +OverlayCRC32<10>=40E9CEEE +OverlayCRC32<21>=EAC5BFE0 +OverlayCRC32<88>=6728568B +OverlayCRC32<92>=F597B809 +OverlayCRC32<93>=167CBE37 +OverlayCRC32<94>=AC9CECAC +OverlayCRC32<195>=6DF520CE +OverlayCRC32<204>=A9A7377A +OverlayCRC32<223>=09541F48 + +[Black 2 (U)] +Game=IREO +Type=BW2 +Version=0 +CopyFrom=IRBO +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +FieldOvlNumber=36 +MoveTutorOvlNumber=36 +IntroCryOvlNumber=162 +PickupOvlNumber=166 +BattleOvlNumber=167 +LowHealthMusicOvlNumber=168 +EvolutionOvlNumber=284 +IntroGraphicOvlNumber=294 +StarterCryOvlNumber=316 +FastestTextTweak=instant_text/b2_instant_text +NewIndexToMusicTweak=musicfix/black2_musicfix +NewIndexToMusicOvlTweak=musicfix/black2_ovl36_musicfix +ShedinjaEvolutionTweak=shedinja/black2_shedinja +ShedinjaEvolutionOvlTweak=shedinja/black2_ovl284_shedinja +NationalDexAtStartTweak=national_dex/bw2_national_dex +HiddenHollowIndex=1 +ShopCount=32 +TMShops=[7,10,12,17,24] +RegularShops=[0,1,2,3,4,5] +TradesUnused=[25] +StarterOffsets1=[854:0x58B, 854:0x590, 854:0x595] +StarterOffsets2=[854:0x5C0, 854:0x5C5, 854:0x5CA] +StarterOffsets3=[854:0x5E2, 854:0x5E7, 854:0x5EC] +StarterLocationTextOffset=169 +StarterCryTablePrefix=080A070000080000 +PokedexGivenFileOffset=854 +MoveTutorDataOffset=0x51538 +PokemonNamesTextOffset=90 +TrainerMugshotsTextOffset=368 +TrainerNamesTextOffset=382 +TrainerClassesTextOffset=383 +PWTTrainerNamesTextOffset=409 +PWTTrainerClassesTextOffset=410 +EliteFourIndices=[38, 39, 40, 41, 341] +ChallengeModeEliteFourIndices=[772,773,774,775,776] +MoveDescriptionsTextOffset=402 +MoveNamesTextOffset=403 +AbilityNamesTextOffset=374 +ItemDescriptionsTextOffset=63 +ItemNamesTextOffset=64 +IngameTradesTextOffset=37 +LuckyEggScriptOffset=676 +ItemBallsScriptOffset=1240 +HiddenItemsScriptOffset=1241 +MapNamesTextOffset=109 +ItemBallsSkip=[] +HiddenItemsSkip=[] +NationalDexScriptOffset=854 +StaticPokemonSupport=1 +StaticPokemon{}={Species=[662:0x1DE, 662:0x240, 740:0xCD, 740:0xFC, 740:0x12C, 740:0x14C], Level=[740:0x12E, 740:0x14E]} // Cobalion +StaticPokemon{}={Species=[730:0x13A, 730:0x15F, 730:0x19B, 730:0x1BB], Level=[730:0x19D, 730:0x1BD]} // Virizion +StaticPokemon{}={Species=[948:0x45D, 948:0x48D, 948:0x4AD], Level=[948:0x48F, 948:0x4AF]} // Terrakion +StaticPokemon{}={Species=[426:0x38A, 426:0x39B, 556:0x367, 556:0x568, 556:0x5E6, 556:0x6E1, 1208:0x3A4, 1208:0xA6A, 1208:0x717], Level=[426:0x39D]} // Reshiram +StaticPokemon{}={Species=[426:0x36B, 426:0x37C, 556:0x350, 556:0x551, 556:0x5C7, 556:0x6C3, 1208:0x38D, 1208:0xA53, 1208:0x706], Level=[426:0x37E]} // Zekrom +StaticPokemon{}={Species=[1112:0x133, 1122:0x2BA, 1122:0x311, 1128:0x37A, 1128:0x3D1, 1208:0x1B7, 1208:0x1F8, 1208:0x723, 1208:0xF3D, 1208:0xF4E], Level=[1208:0xF50]} // Kyurem +StaticPokemon{}={Species=[1208:0xD8B, 1208:0xD97], Level=[1208:0xD99], Forme=[1208:0xD8D, 1208:0xD9B]} // Kyurem-Black +StaticPokemon{}={Species=[1208:0xDB6, 1208:0xDC2], Level=[1208:0xDC4], Forme=[1208:0xDB8, 1208:0xDC6]} // Kyurem-White +StaticPokemon{}={Species=[304:0xCC, 304:0x14B, 304:0x1BC, 304:0x237, 304:0x327, 304:0x3E6, 304:0x4A1, 304:0x54A, 304:0x5BD, 304:0x5CE], Level=[304:0x5D0]} // Latias +StaticPokemon{}={Species=[304:0xB5, 304:0x134, 304:0x1A5, 304:0x220, 304:0x310, 304:0x3CF, 304:0x48A, 304:0x533, 304:0x59E, 304:0x5AF], Level=[304:0x5B1]} // Latios +StaticPokemon{}={Species=[32:0x247, 32:0x2B0, 32:0x2C1, 1034:0x12A], Level=[32:0x2C3]} // Uxie +StaticPokemon{}={Species=[684:0x136, 684:0x1C2, 684:0x1D3, 1034:0x169], Level=[684:0x1D5]} // Mesprit +StaticPokemon{}={Species=[950:0xA1, 950:0x10A, 950:0x11B, 1034:0x1BE], Level=[950:0x11D]} // Azelf +StaticPokemon{}={Species=[1222:0x134, 1222:0x145, 1018:0x32], Level=[1222:0x147]} // Regirock +StaticPokemon{}={Species=[1224:0x134, 1224:0x145, 1018:0x2C], Level=[1224:0x147]} // Regice +StaticPokemon{}={Species=[1226:0x134, 1226:0x145, 1018:0x38], Level=[1226:0x147]} // Registeel +StaticPokemon{}={Species=[1018:0x97, 1018:0xA8], Level=[1018:0xAA]} // Regigigas +StaticPokemon{}={Species=[526:0x48D, 526:0x512, 526:0x523], Level=[526:0x525]} // Cresselia +StaticPokemon{}={Species=[1068:0x193, 1068:0x1D6, 1068:0x1E7, 1080:0x193, 1080:0x1D6, 1080:0x1E7], Level=[1068:0x1E9, 1080:0x1E9]} // Heatran +StaticPokemon{}={Species=[652:0x5C6, 652:0x5E9], Level=[652:0x5EB]} // Mandibuzz +StaticPokemon{}={Species=[1102:0x592, 1102:0x5B5], Level=[1102:0x5B7]} // Braviary +StaticPokemon{}={Species=[364:0xE, 364:0x32, 364:0x40], Level=[364:0x34, 364:0x42]} // Volcarona +StaticPokemon{}={Species=[1030:0x290, 1030:0x2A1], Level=[1030:0x2A3]} // Crustle +StaticPokemon{}={Species=[480:0xE1, 480:0x10A, 480:0x131, 480:0x15A], Level=[480:0x10C, 480:0x15C]} // Jellicent +StaticPokemon{}={Species=[1168:0x2C, 1168:0x4F], Level=[1168:0x51]} // Shiny Haxorus +StaticPokemon{}={Species=[988:0x382], Level=[988:0x386]} // Eevee +StaticPokemon{}={Species=[664:0x3B5, 664:0x3E2, 664:0x40F, 664:0x43C], Level=[664:0x3B9, 664:0x3E6, 664:0x413, 664:0x440], Forme=[664:0x3B7, 664:0x3E4, 664:0x411, 664:0x43E]} // Deerling +StaticPokemon{}={Species=[880:0xAB4, 880:0xAC7], Level=[880:0xAB8]} // Shiny Gible +StaticPokemon{}={Species=[880:0xAD3, 880:0xAE6], Level=[880:0xAD7]} // Shiny Dratini +StaticPokemon{}={Species=[54:0xDD]} // Happiny (egg) +StaticPokemon{}={Species=[526:0x27E], Level=[526:0x282]} // Magikarp +StaticPokemon{}={Species=[1253:0x5E0], Level=[1253:0x3D6]} // Cranidos +StaticPokemon{}={Species=[1253:0x5FF], Level=[1253:0x3D6]} // Shieldon +StaticPokemon{}={Species=[1253:0x61E], Level=[1253:0x3D6]} // Omanyte +StaticPokemon{}={Species=[1253:0x63D], Level=[1253:0x3D6]} // Kabuto +StaticPokemon{}={Species=[1253:0x65C], Level=[1253:0x3D6]} // Aerodactyl +StaticPokemon{}={Species=[1253:0x67B], Level=[1253:0x3D6]} // Anorith +StaticPokemon{}={Species=[1253:0x69A], Level=[1253:0x3D6]} // Lileep +StaticPokemon{}={Species=[1253:0x6B9], Level=[1253:0x3D6]} // Tirtouga +StaticPokemon{}={Species=[1253:0x6D8], Level=[1253:0x3D6]} // Archen +StaticPokemon{}={Species=[208:0x5A6], Level=[208:0x5A8]} // Zorua +StaticPokemonFakeBall{}={Species=[1273:0x45], Level=[500:0x46E, 500:0x492, 500:0x4B6, 506:0x42A, 506:0x44E]} // Foongus +StaticPokemonFakeBall{}={Species=[1273:0xC7], Level=[534:0x2F2, 534:0x316, 562:0x3FE, 562:0x422, 563:0x742, 563:0x766, 563:0x78A]} // Amoonguss +IngameTradePersonTextOffsets=[529, 555, 193, 594, 628, 628] +StaticEggPokemonOffsets=[29] +MainGameLegendaries=[638,639,640] +Arm9ExtensionSize=496 // 448 for music, 48 for Shedinja +TCMCopyingPrefix=1030A0E3013053E2FDFFFF1AF8FFFFEA +NewIndexToMusicPrefix=2648288021203060FFE7012002BC0847 +SpecialMusicStatics=[377,378,379,381,480,481,482,485,486,488,494,571,612,637,638,639,640,644,646,669] +ShedinjaSpeciesOffset=0x9ACCC +TrainerOverworldTextBoxPrefix=0004000C03D10320 +DoubleBattleLimitPrefix=0224E3E7012817D1 +DoubleBattleGetPointerPrefix=0A9906980904090C +BeqToSingleTrainerNumber=0xD0C7 +TextBoxChoicePrefix=6088A8420AD1A088 +Arm9CRC32=FAF9D733 +OverlayCRC32<36>=4CF872D1 +OverlayCRC32<162>=8D54F1B3 +OverlayCRC32<166>=4CB7622C +OverlayCRC32<167>=26B6E3C0 +OverlayCRC32<168>=0439E88C +OverlayCRC32<284>=957DBCBA +OverlayCRC32<294>=F3F8525A +OverlayCRC32<316>=D4756D45 + +[White 2 (U)] +Game=IRDO +Type=BW2 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IREO +File= +File= +File= +FastestTextTweak=instant_text/w2_instant_text +NewIndexToMusicTweak=musicfix/white2_musicfix +NewIndexToMusicOvlTweak=musicfix/white2_ovl36_musicfix +ShedinjaEvolutionTweak=shedinja/white2_shedinja +ShedinjaEvolutionOvlTweak=shedinja/white2_ovl284_shedinja +NationalDexAtStartTweak=national_dex/bw2_national_dex +HiddenHollowIndex=0 +MoveTutorDataOffset=0x5152C +TradesUnused=[24] +IngameTradePersonTextOffsets=[537, 555, 193, 594, 628, 628] +SpecialMusicStatics=[377,378,379,380,480,481,482,485,486,488,494,571,612,637,638,639,640,643,646,668] +ShedinjaSpeciesOffset=0x9AD0C +Arm9CRC32=DB3843F0 +OverlayCRC32<36>=E3CF3D36 +OverlayCRC32<162>=CCA4CB78 +OverlayCRC32<166>=759F8A03 +OverlayCRC32<167>=3E3CA5FA +OverlayCRC32<168>=EDE00D9D +OverlayCRC32<284>=0E276134 +OverlayCRC32<294>=717E4E01 +OverlayCRC32<316>=D7EED8D1 + +[Black (F)] +Game=IRBF +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRBO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw1_national_dex +ShopItemOffsets=[0x51530,0x51534,0x5153E,0x51544,0x5155C,0x51588,0x515DC,0x515EA,0x515F8,0x51608,0x51618,0x51628,0x51638,0x51648,0x5165A,0x5166C,0x5167E,0x51694,0x516AA,0x516C0,0x516F0,0x5170C,0x5172C,0x5176C,0x5178E,0x517B2] +Arm9CRC32=A4F262BE +OverlayCRC32<10>=8FC77ABE +OverlayCRC32<21>=054897E8 +OverlayCRC32<88>=55593929 +OverlayCRC32<92>=57E1FE0E +OverlayCRC32<93>=D568F722 +OverlayCRC32<94>=3A8E21BE +OverlayCRC32<195>=6B6112D6 +OverlayCRC32<204>=3E6F6775 +OverlayCRC32<223>=F01B4C69 + +[White (F)] +Game=IRAF +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRAO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw1_national_dex +ShopItemOffsets=[0x51528,0x5152C,0x51536,0x5153C,0x51554,0x51580,0x515D4,0x515E2,0x515F0,0x51600,0x51610,0x51620,0x51630,0x51640,0x51652,0x51664,0x51676,0x5168C,0x516A2,0x516B8,0x516E8,0x51704,0x51724,0x51764,0x51786,0x517AA] +Arm9CRC32=961E0C7B +OverlayCRC32<10>=97A78C5C +OverlayCRC32<21>=14BA0CDE +OverlayCRC32<88>=F4EB70CD +OverlayCRC32<92>=5ACBE24B +OverlayCRC32<93>=B185132E +OverlayCRC32<94>=985B99B1 +OverlayCRC32<195>=F5FB3F97 +OverlayCRC32<204>=B391C71E +OverlayCRC32<223>=935717C1 + +[Black (G)] +Game=IRBD +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRBO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw1_national_dex +ShopItemOffsets=[0x51530,0x51534,0x5153E,0x51544,0x5155C,0x51588,0x515DC,0x515EA,0x515F8,0x51608,0x51618,0x51628,0x51638,0x51648,0x5165A,0x5166C,0x5167E,0x51694,0x516AA,0x516C0,0x516F0,0x5170C,0x5172C,0x5176C,0x5178E,0x517B2] +Arm9CRC32=5189F435 +OverlayCRC32<10>=A74BA01F +OverlayCRC32<21>=7E6E109F +OverlayCRC32<88>=E2E86BAF +OverlayCRC32<92>=C684D133 +OverlayCRC32<93>=5DAB79AA +OverlayCRC32<94>=5F094FDE +OverlayCRC32<195>=CACEF977 +OverlayCRC32<204>=6CBA741B +OverlayCRC32<223>=787E48BF + +[White (G)] +Game=IRAD +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRAO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw1_national_dex +ShopItemOffsets=[0x51528,0x5152C,0x51536,0x5153C,0x51554,0x51580,0x515D4,0x515E2,0x515F0,0x51600,0x51610,0x51620,0x51630,0x51640,0x51652,0x51664,0x51676,0x5168C,0x516A2,0x516B8,0x516E8,0x51704,0x51724,0x51764,0x51786,0x517AA] +Arm9CRC32=23C9DFD2 +OverlayCRC32<10>=452DD9DB +OverlayCRC32<21>=61B4BE9C +OverlayCRC32<88>=805834D4 +OverlayCRC32<92>=1BC6EC0E +OverlayCRC32<93>=FEDB5C26 +OverlayCRC32<94>=DC4FBCB9 +OverlayCRC32<195>=BD27975F +OverlayCRC32<204>=4CB70AB9 +OverlayCRC32<223>=B1CA7124 + +[Black (S)] +Game=IRBS +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRBO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw1_national_dex +ShopItemOffsets=[0x5153C,0x51540,0x5154A,0x51550,0x51568,0x51594,0x515E8,0x515F6,0x51604,0x51614,0x51624,0x51634,0x51644,0x51654,0x51666,0x51678,0x5168A,0x516A0,0x516B6,0x516CC,0x516FC,0x51718,0x51738,0x51778,0x5179A,0x517BE] +Arm9CRC32=368F8E04 +OverlayCRC32<10>=94D5A1A8 +OverlayCRC32<21>=A6539D64 +OverlayCRC32<88>=51BB12C3 +OverlayCRC32<92>=414ADA7A +OverlayCRC32<93>=F1905C6B +OverlayCRC32<94>=63537E6B +OverlayCRC32<195>=38859ADE +OverlayCRC32<204>=8ED54FBE +OverlayCRC32<223>=187C0D03 + +[White (S)] +Game=IRAS +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRAO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw1_national_dex +ShopItemOffsets=[0x51534,0x51538,0x51542,0x51548,0x51560,0x5158C,0x515E0,0x515EE,0x515FC,0x5160C,0x5161C,0x5162C,0x5163C,0x5164C,0x5165E,0x51670,0x51682,0x51698,0x516AE,0x516C4,0x516F4,0x51710,0x51730,0x51770,0x51792,0x517B6] +Arm9CRC32=D48F02E1 +OverlayCRC32<10>=5DB2D539 +OverlayCRC32<21>=3882585C +OverlayCRC32<88>=54B7C88D +OverlayCRC32<92>=8043EB67 +OverlayCRC32<93>=73422427 +OverlayCRC32<94>=325AA919 +OverlayCRC32<195>=6637307A +OverlayCRC32<204>=DD127FDF +OverlayCRC32<223>=2F3ADC12 + +[Black (I)] +Game=IRBI +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRBO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw1_national_dex +ShopItemOffsets=[0x51520,0x51524,0x51532,0x51548,0x51538,0x515AC,0x515D0,0x515DE,0x515EC,0x515FC,0x5160C,0x5161C,0x5162C,0x5163C,0x5164E,0x51660,0x516B4,0x51672,0x51688,0x5169E,0x516E4,0x51700,0x51720,0x51760,0x51782,0x517A6] +Arm9CRC32=5A9924F2 +OverlayCRC32<10>=C15D1FAD +OverlayCRC32<21>=4F9C6CB1 +OverlayCRC32<88>=02AE885C +OverlayCRC32<92>=598C95C4 +OverlayCRC32<93>=E0F9359C +OverlayCRC32<94>=2F85DA79 +OverlayCRC32<195>=70F4039C +OverlayCRC32<204>=AE28DAEC +OverlayCRC32<223>=DC1D15FC + +[White (I)] +Game=IRAI +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRAO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw1_national_dex +ShopItemOffsets=[0x51518,0x5151C,0x5152A,0x51540,0x51530,0x515A4,0x515C8,0x515D6,0x515E4,0x515F4,0x51604,0x51614,0x51624,0x51634,0x51646,0x51658,0x516AC,0x5166A,0x51680,0x51696,0x516DC,0x516F8,0x51718,0x51758,0x5177A,0x5179E] +Arm9CRC32=8DC1C9C0 +OverlayCRC32<10>=9921B1F7 +OverlayCRC32<21>=5999FAA1 +OverlayCRC32<88>=2B0077F8 +OverlayCRC32<92>=F578AD4B +OverlayCRC32<93>=1C399052 +OverlayCRC32<94>=0F25AD49 +OverlayCRC32<195>=0C9EEB6B +OverlayCRC32<204>=12FF46BB +OverlayCRC32<223>=0F78FCA2 + +[Black (J)] +Game=IRBJ +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRBO +File= +File= +File= +File= +File= +File= +File= +MoveDescriptionsTextOffset=203 +MoveNamesTextOffset=204 +ShopItemOffsets=[0x51244,0x51248,0x51252,0x51268,0x51270,0x512B4,0x512F0,0x512FE,0x5131C,0x5134C,0x5132C,0x5133C,0x5130C,0x5135C,0x5136E,0x51380,0x51392,0x513A8,0x513BE,0x513D4,0x51404,0x51420,0x51440,0x51480,0x514A2,0x514C6] +Arm9CRC32=6E7444E9 +OverlayCRC32<10>=E91DA62C +OverlayCRC32<21>=2FEEC460 +OverlayCRC32<88>=DC1300B9 +OverlayCRC32<92>=0EC1245E +OverlayCRC32<93>=65349A4A +OverlayCRC32<94>=31CB4E7A +OverlayCRC32<195>=CA203F2C +OverlayCRC32<204>=127CF38D +OverlayCRC32<223>=973F0880 + +[White (J)] +Game=IRAJ +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRAO +File= +File= +File= +File= +File= +File= +File= +MoveDescriptionsTextOffset=203 +MoveNamesTextOffset=204 +ShopItemOffsets=[0x5123C,0x51240,0x5124A,0x51260,0x51268,0x512AC,0x512E8,0x512F6,0x51314,0x51344,0x51324,0x51334,0x51304,0x51354,0x51366,0x51378,0x5138A,0x513A0,0x513B6,0x513CC,0x513FC,0x51418,0x51438,0x51478,0x5149A,0x514BE] +Arm9CRC32=1A99A22E +OverlayCRC32<10>=95D97B6C +OverlayCRC32<21>=9311D75D +OverlayCRC32<88>=2983855B +OverlayCRC32<92>=41E0F3F4 +OverlayCRC32<93>=CBF347C4 +OverlayCRC32<94>=4DD90D5E +OverlayCRC32<195>=63636362 +OverlayCRC32<204>=88CB9615 +OverlayCRC32<223>=57871763 + +[Black (K)] +Game=IRBK +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRBO +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw1_national_dex +ShopItemOffsets=[0x51524,0x51528,0x51536,0x5154C,0x5153C,0x515B0,0x515D4,0x515E2,0x515F0,0x51600,0x51610,0x51620,0x51630,0x51640,0x51652,0x51664,0x516B8,0x51676,0x5168C,0x516A2,0x516E8,0x51704,0x51724,0x51764,0x51786,0x517AA] +Arm9CRC32=37794F95 +OverlayCRC32<10>=9C041821 +OverlayCRC32<21>=E79FDDB3 +OverlayCRC32<88>=8A835ABF +OverlayCRC32<92>=27B75A9A +OverlayCRC32<93>=3F071EF5 +OverlayCRC32<94>=113D4CDF +OverlayCRC32<195>=FFEF510C +OverlayCRC32<204>=05A0D977 +OverlayCRC32<223>=90F700C2 + +[White (K)] +Game=IRAK +Type=BW1 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRAO +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw1_national_dex +ShopItemOffsets=[0x5151C,0x51520,0x5152E,0x51544,0x51534,0x515A8,0x515CC,0x515DA,0x515E8,0x515F8,0x51608,0x51618,0x51628,0x51638,0x5164A,0x5165C,0x516B0,0x5166E,0x51684,0x5169A,0x516E0,0x516FC,0x5171C,0x5175C,0x5177E,0x517A2] +Arm9CRC32=16AA3F19 +OverlayCRC32<10>=7A4C40D4 +OverlayCRC32<21>=DD795DB3 +OverlayCRC32<88>=43A68763 +OverlayCRC32<92>=4410A4AC +OverlayCRC32<93>=61307C75 +OverlayCRC32<94>=1AB592F2 +OverlayCRC32<195>=8D5E5514 +OverlayCRC32<204>=70E175AF +OverlayCRC32<223>=DF2D29AC + +[Black 2 (F)] +Game=IREF +Type=BW2 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IREO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw2_national_dex +MoveTutorDataOffset=0x5152C +Arm9CRC32=98ACB21A +OverlayCRC32<36>=7314EA5A +OverlayCRC32<162>=47279894 +OverlayCRC32<166>=DCD888DE +OverlayCRC32<167>=D23B80B4 +OverlayCRC32<168>=F6639242 +OverlayCRC32<284>=112A8880 +OverlayCRC32<294>=07A19A52 +OverlayCRC32<316>=9A87B407 + +[White 2 (F)] +Game=IRDF +Type=BW2 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRDO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw2_national_dex +MoveTutorDataOffset=0x51520 +Arm9CRC32=61194710 +OverlayCRC32<36>=2936E726 +OverlayCRC32<162>=268CD440 +OverlayCRC32<166>=4375ED33 +OverlayCRC32<167>=99AA7857 +OverlayCRC32<168>=77FD23BD +OverlayCRC32<284>=34FAD991 +OverlayCRC32<294>=8E753885 +OverlayCRC32<316>=A680DF32 + +[Black 2 (G)] +Game=IRED +Type=BW2 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IREO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw2_national_dex +MoveTutorDataOffset=0x5155C +Arm9CRC32=8DC7E4A6 +OverlayCRC32<36>=A1F1DD09 +OverlayCRC32<162>=A1A6458A +OverlayCRC32<166>=A06010C3 +OverlayCRC32<167>=40392337 +OverlayCRC32<168>=3B1F041B +OverlayCRC32<284>=946AE3C4 +OverlayCRC32<294>=4E16F546 +OverlayCRC32<316>=699BD8B0 + +[White 2 (G)] +Game=IRDD +Type=BW2 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRDO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw2_national_dex +MoveTutorDataOffset=0x51550 +Arm9CRC32=EB769B6A +OverlayCRC32<36>=400EE8B2 +OverlayCRC32<162>=A55A3E8E +OverlayCRC32<166>=B8770300 +OverlayCRC32<167>=6ECDC783 +OverlayCRC32<168>=D18B4E73 +OverlayCRC32<284>=42022F46 +OverlayCRC32<294>=AA708716 +OverlayCRC32<316>=D35EA355 + +[Black 2 (I)] +Game=IREI +Type=BW2 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IREO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw2_national_dex +MoveTutorDataOffset=0x51554 +Arm9CRC32=266EA831 +OverlayCRC32<36>=96CCDDA7 +OverlayCRC32<162>=49534761 +OverlayCRC32<166>=2003AFAA +OverlayCRC32<167>=FC14A53F +OverlayCRC32<168>=E1C34887 +OverlayCRC32<284>=122E79F7 +OverlayCRC32<294>=8E83C586 +OverlayCRC32<316>=96A98A1D + +[White 2 (I)] +Game=IRDI +Type=BW2 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRDO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw2_national_dex +MoveTutorDataOffset=0x51548 +Arm9CRC32=35A924E0 +OverlayCRC32<36>=5DF5BFD5 +OverlayCRC32<162>=5C2290A5 +OverlayCRC32<166>=C426EC98 +OverlayCRC32<167>=5C87223F +OverlayCRC32<168>=815FC8BA +OverlayCRC32<284>=D13AF02C +OverlayCRC32<294>=0839E1F1 +OverlayCRC32<316>=2D552545 + +[Black 2 (S)] +Game=IRES +Type=BW2 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IREO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw2_national_dex +MoveTutorDataOffset=0x5153C +Arm9CRC32=914ECB95 +OverlayCRC32<36>=B7A8FDD4 +OverlayCRC32<162>=8A291DF2 +OverlayCRC32<166>=56E6C955 +OverlayCRC32<167>=D87994A8 +OverlayCRC32<168>=334A52E5 +OverlayCRC32<284>=12547755 +OverlayCRC32<294>=B6F92135 +OverlayCRC32<316>=3585F11B + +[White 2 (S)] +Game=IRDS +Type=BW2 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRDO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw2_national_dex +MoveTutorDataOffset=0x51530 +Arm9CRC32=73D0095B +OverlayCRC32<36>=D8CE4272 +OverlayCRC32<162>=C3F7B172 +OverlayCRC32<166>=3328105B +OverlayCRC32<167>=56AB1872 +OverlayCRC32<168>=B0723FF9 +OverlayCRC32<284>=49386511 +OverlayCRC32<294>=4087E658 +OverlayCRC32<316>=49765281 + +[Black 2 (J Rev 1)] +Game=IREJ +Type=BW2 +Version=1 +CopyFrom=IREO +File= +File= +File= +File= +File= +File= +File= +File= +File= +MoveTutorDataOffset=0x512DC +StaticPokemonSupport=1 +StaticPokemon{}={Species=[662:0x1DE, 662:0x240, 740:0xCD, 740:0xFC, 740:0x12C, 740:0x14C], Level=[740:0x12E, 740:0x14E]} // Cobalion +StaticPokemon{}={Species=[730:0x13A, 730:0x15F, 730:0x19B, 730:0x1BB], Level=[730:0x19D, 730:0x1BD]} // Virizion +StaticPokemon{}={Species=[948:0x45D, 948:0x48D, 948:0x4AD], Level=[948:0x48F, 948:0x4AF]} // Terrakion +StaticPokemon{}={Species=[426:0x38A, 426:0x39B, 556:0x367, 556:0x568, 556:0x5E6, 556:0x6E1, 1208:0x3A4, 1208:0xA6A, 1208:0x717], Level=[426:0x39D]} // Reshiram +StaticPokemon{}={Species=[426:0x36B, 426:0x37C, 556:0x350, 556:0x551, 556:0x5C7, 556:0x6C3, 1208:0x38D, 1208:0xA53, 1208:0x706], Level=[426:0x37E]} // Zekrom +StaticPokemon{}={Species=[1112:0x133, 1122:0x2BA, 1122:0x311, 1128:0x37A, 1128:0x3D1, 1208:0x1B7, 1208:0x1F8, 1208:0x723, 1208:0xF3D, 1208:0xF4E], Level=[1208:0xF50]} // Kyurem +StaticPokemon{}={Species=[1208:0xD8B, 1208:0xD97], Level=[1208:0xD99], Forme=[1208:0xD8D, 1208:0xD9B]} // Kyurem-Black +StaticPokemon{}={Species=[1208:0xDB6, 1208:0xDC2], Level=[1208:0xDC4], Forme=[1208:0xDB8, 1208:0xDC6]} // Kyurem-White +StaticPokemon{}={Species=[304:0xCC, 304:0x14B, 304:0x1B8, 304:0x22F, 304:0x31B, 304:0x3D6, 304:0x491, 304:0x536, 304:0x5A9, 304:0x5BA], Level=[304:0x5BC]} // Latias +StaticPokemon{}={Species=[304:0xB5, 304:0x134, 304:0x1A1, 304:0x218, 304:0x304, 304:0x3BF, 304:0x47A, 304:0x51F, 304:0x58A, 304:0x59B], Level=[304:0x59D]} // Latios +StaticPokemon{}={Species=[32:0x247, 32:0x2B0, 32:0x2C1, 1034:0x12A], Level=[32:0x2C3]} // Uxie +StaticPokemon{}={Species=[684:0x136, 684:0x1C2, 684:0x1D3, 1034:0x169], Level=[684:0x1D5]} // Mesprit +StaticPokemon{}={Species=[950:0xA1, 950:0x10A, 950:0x11B, 1034:0x1BE], Level=[950:0x11D]} // Azelf +StaticPokemon{}={Species=[1222:0x134, 1222:0x145, 1018:0x32], Level=[1222:0x147]} // Regirock +StaticPokemon{}={Species=[1224:0x134, 1224:0x145, 1018:0x2C], Level=[1224:0x147]} // Regice +StaticPokemon{}={Species=[1226:0x134, 1226:0x145, 1018:0x38], Level=[1226:0x147]} // Registeel +StaticPokemon{}={Species=[1018:0x97, 1018:0xA8], Level=[1018:0xAA]} // Regigigas +StaticPokemon{}={Species=[526:0x48D, 526:0x512, 526:0x523], Level=[526:0x525]} // Cresselia +StaticPokemon{}={Species=[1068:0x171, 1068:0x1B4, 1068:0x1C5, 1080:0x171, 1080:0x1B4, 1080:0x1C5], Level=[1068:0x1C7, 1080:0x1C7]} // Heatran +StaticPokemon{}={Species=[652:0x5C6, 652:0x5E9], Level=[652:0x5EB]} // Mandibuzz +StaticPokemon{}={Species=[1102:0x592, 1102:0x5B5], Level=[1102:0x5B7]} // Braviary +StaticPokemon{}={Species=[364:0xE, 364:0x32, 364:0x40], Level=[364:0x34, 364:0x42]} // Volcarona +StaticPokemon{}={Species=[1030:0x290, 1030:0x2A1], Level=[1030:0x2A3]} // Crustle +StaticPokemon{}={Species=[480:0xE1, 480:0x10A, 480:0x131, 480:0x15A], Level=[480:0x10C, 480:0x15C]} // Jellicent +StaticPokemon{}={Species=[1168:0x2C, 1168:0x4F], Level=[1168:0x51]} // Shiny Haxorus +StaticPokemon{}={Species=[988:0x382], Level=[988:0x386]} // Eevee +StaticPokemon{}={Species=[664:0x3B5, 664:0x3E2, 664:0x40F, 664:0x43C], Level=[664:0x3B9, 664:0x3E6, 664:0x413, 664:0x440], Forme=[664:0x3B7, 664:0x3E4, 664:0x411, 664:0x43E]} // Deerling +StaticPokemon{}={Species=[880:0xAB4, 880:0xAC7], Level=[880:0xAB8]} // Shiny Gible +StaticPokemon{}={Species=[880:0xAD3, 880:0xAE6], Level=[880:0xAD7]} // Shiny Dratini +StaticPokemon{}={Species=[54:0xDD]} // Happiny (egg) +StaticPokemon{}={Species=[526:0x27E], Level=[526:0x282]} // Magikarp +StaticPokemon{}={Species=[1253:0x5E0], Level=[1253:0x3D6]} // Cranidos +StaticPokemon{}={Species=[1253:0x5FF], Level=[1253:0x3D6]} // Shieldon +StaticPokemon{}={Species=[1253:0x61E], Level=[1253:0x3D6]} // Omanyte +StaticPokemon{}={Species=[1253:0x63D], Level=[1253:0x3D6]} // Kabuto +StaticPokemon{}={Species=[1253:0x65C], Level=[1253:0x3D6]} // Aerodactyl +StaticPokemon{}={Species=[1253:0x67B], Level=[1253:0x3D6]} // Anorith +StaticPokemon{}={Species=[1253:0x69A], Level=[1253:0x3D6]} // Lileep +StaticPokemon{}={Species=[1253:0x6B9], Level=[1253:0x3D6]} // Tirtouga +StaticPokemon{}={Species=[1253:0x6D8], Level=[1253:0x3D6]} // Archen +StaticPokemon{}={Species=[208:0x5A6], Level=[208:0x5A8]} // Zorua +StaticPokemonFakeBall{}={Species=[1273:0x45], Level=[500:0x46E, 500:0x492, 500:0x4B6, 506:0x42A, 506:0x44E]} // Foongus +StaticPokemonFakeBall{}={Species=[1273:0xC7], Level=[534:0x2F2, 534:0x316, 562:0x3FE, 562:0x422, 563:0x742, 563:0x766, 563:0x78A]} // Amoonguss +StaticEggPokemonOffsets=[29] +Arm9CRC32=F53E7838 +OverlayCRC32<36>=4BF19607 +OverlayCRC32<162>=35DA9C90 +OverlayCRC32<166>=EBDBC1CB +OverlayCRC32<167>=0D1C66F1 +OverlayCRC32<168>=CDD99E97 +OverlayCRC32<284>=E1504E5D +OverlayCRC32<294>=1B490894 +OverlayCRC32<316>=074A22B1 + +[White 2 (J Rev 1)] +Game=IRDJ +Type=BW2 +Version=1 +CopyStaticPokemon=1 +CopyFrom=IREJ +File= +File= +File= +MoveTutorDataOffset=0x512D0 +Arm9CRC32=6B69DD50 +OverlayCRC32<36>=896946FC +OverlayCRC32<162>=DD3707A4 +OverlayCRC32<166>=083CFC05 +OverlayCRC32<167>=27978D30 +OverlayCRC32<168>=094F1D74 +OverlayCRC32<284>=337744D2 +OverlayCRC32<294>=FC3CEB37 +OverlayCRC32<316>=0F57993F + +[Black 2 (K)] +Game=IREK +Type=BW2 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IREO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw2_national_dex +MoveTutorDataOffset=0x5160C +Arm9CRC32=18ED174C +OverlayCRC32<36>=AC0E0C5A +OverlayCRC32<162>=195919EB +OverlayCRC32<166>=C0D5AB8C +OverlayCRC32<167>=6FAC8636 +OverlayCRC32<168>=0E04A907 +OverlayCRC32<284>=4621EF7E +OverlayCRC32<294>=A678C4C6 +OverlayCRC32<316>=04E41D23 + +[White 2 (K)] +Game=IRDK +Type=BW2 +Version=0 +CopyStaticPokemon=1 +CopyFrom=IRDO +File= +File= +File= +File= +File= +NationalDexAtStartTweak=national_dex/bw2_national_dex +MoveTutorDataOffset=0x51600 +Arm9CRC32=47B7A7F6 +OverlayCRC32<36>=EACE4819 +OverlayCRC32<162>=D9FE03A6 +OverlayCRC32<166>=A9F8ADA1 +OverlayCRC32<167>=B94A6A8C +OverlayCRC32<168>=5E4912F4 +OverlayCRC32<284>=99975230 +OverlayCRC32<294>=28B2F1D4 +OverlayCRC32<316>=08076F8F diff --git a/src/com/pkrandom/config/gen6_offsets.ini b/src/com/pkrandom/config/gen6_offsets.ini new file mode 100644 index 0000000..b97ebbe --- /dev/null +++ b/src/com/pkrandom/config/gen6_offsets.ini @@ -0,0 +1,190 @@ +[X] +Game=CTR-P-EKJA +TitleId=0004000000055D00 +Type=XY +Acronym=X +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +PokemonNamesTextOffset=80 +AbilityNamesTextOffset=34 +MoveNamesTextOffset=13 +MoveDescriptionsTextOffset=15 +ItemNamesTextOffset=96 +ItemDescriptionsTextOffset=99 +MapTableFileOffset=360 +MapNamesTextOffset=72 +StaticPokemonOffset=0xEE46C +GiftPokemonOffset=0xF805C +StarterIndices=[0,1,2,3,4,5] +StarterOffsetOffset=0xB8 +StarterExtraOffset=0x10 +StarterTextOffset=63 +SpecificStarterTextOffsets=[1,2,3] +TrainerNamesTextOffset=21 +TrainerClassesTextOffset=20 +TrainerMugshotsTextOffset=22 +DoublesTrainerClasses=[8, 26, 46, 52, 71, 72, 97, 99, 106] +EliteFourIndices=[187, 269, 270, 271, 276] +FieldItemsScriptNumber=17 +FieldItemsOffset=0xB04 +HiddenItemsScriptNumber=26 +HiddenItemsOffset=0xB18 +ShopItemSizes=[2, 11, 14, 17, 18, 19, 19, 19, 19, 1, 4, 10, 3, 9, 1, 1, 3, 3, 5, 5, 6, 7, 5, 5, 8, 3] +ShopCount=26 +TMShops=[18,19,22,23] +RegularShops=[0,1,2,3,4,5,6,7,8,14,15] +IngameTradesTextOffset=113 +IngameTradesTextExtraOffset=0 +IngameTradeCount=9 +TitleScreenTextOffset=85 +UpdateStringOffset=25 +BoxLegendaryOffsets=[2, 12] +BoxLegendaryScriptOffsets=[4658, 5430, 16798] +LinkedStaticEncounterOffsets=[1:3, 2:12] +MainGameLegendaries=[716] +RoamingLegendaryOffsets=[6, 7, 8] +FullyUpdatedVersionNumber=5232 +CodeCRC32=[3778C475, BA7B14F7] + +[Y] +Game=CTR-P-EK2A +TitleId=0004000000055E00 +Type=XY +Acronym=Y +CopyFrom=CTR-P-EKJA +File= +File= +File= +File= +BoxLegendaryOffsets=[1, 3] +BoxLegendaryScriptOffsets=[4670, 5456, 17266] +MainGameLegendaries=[717] +FullyUpdatedVersionNumber=5216 +CodeCRC32=[46BDAD57, 3E056C1E] + +[Omega Ruby] +Game=CTR-P-ECRA +TitleId=000400000011C400 +Type=ORAS +Acronym=OR +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +PokemonNamesTextOffset=98 +AbilityNamesTextOffset=37 +MoveNamesTextOffset=14 +MoveDescriptionsTextOffset=16 +ItemNamesTextOffset=114 +ItemDescriptionsTextOffset=117 +MapTableFileOffset=536 +MapNamesTextOffset=90 +StaticPokemonOffset=0xF1B20 +GiftPokemonOffset=0xF906C +StarterIndices=[0,1,2,28,29,30,31,32,33,34,35,36] +StarterOffsetOffset=0xB8 +StarterExtraOffset=0 +StarterTextOffset=77 +SpecificStarterTextOffsets=[1,2,3] +TrainerNamesTextOffset=22 +TrainerClassesTextOffset=21 +TrainerMugshotsTextOffset=23 +DoublesTrainerClasses=[8, 26, 46, 52, 71, 72, 97, 99, 106, 133, 173, 188, 193, 206, 210, 211, 223, 226, 231] +EliteFourIndices=[553, 554, 555, 556, 557] +FieldItemsScriptNumber=39 +FieldItemsOffset=0xB64 +ShopItemSizes=[3, 10, 14, 17, 18, 19, 19, 19, 19, 1, 9, 6, 4, 3, 8, 8, 3, 3, 4, 3, 6, 8, 7, 4] +ShopCount=24 +TMShops=[12,15,22,23] +RegularShops=[0,1,2,3,4,5,6,7,8,9] +IngameTradesTextOffset=132 +IngameTradesTextExtraOffset=18 +IngameTradeCount=3 +TitleScreenTextOffset=38 +UpdateStringOffset=22 +RayquazaEncounterNumber=28 +RayquazaEncounterScriptNumber=31 +LinkedStaticEncounterOffsets=[26:49, 27:50, 29:58, 80:56, 81:57, 75:76] +MainGameLegendaries=[381,383] +MegaStoneItemScriptNumber=57 +FullyUpdatedVersionNumber=7280 +CodeCRC32=[4D9BBCE3, B0AB0BFE] + +[Alpha Sapphire] +Game=CTR-P-ECLA +TitleId=000400000011C500 +Type=ORAS +Acronym=AS +CopyFrom=CTR-P-ECRA +File= +File= +File= +File= +File= +File= +MainGameLegendaries=[380,382] +CodeCRC32=[72B62B7F, DDC6E452] diff --git a/src/com/pkrandom/config/gen7_offsets.ini b/src/com/pkrandom/config/gen7_offsets.ini new file mode 100644 index 0000000..85f6a29 --- /dev/null +++ b/src/com/pkrandom/config/gen7_offsets.ini @@ -0,0 +1,148 @@ +[Sun] +Game=CTR-P-BNDA +TitleId=0004000000164800 +Type=SM +Acronym=Su +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +PokemonNamesTextOffset=55 +AbilityNamesTextOffset=96 +MoveNamesTextOffset=113 +MoveDescriptionsTextOffset=112 +ItemNamesTextOffset=36 +ItemDescriptionsTextOffset=35 +MapNamesTextOffset=67 +TrainerNamesTextOffset=105 +TrainerClassesTextOffset=106 +StarterTextOffset=41 +IngameTradesTextOffset=12 +TitleScreenTextOffset=94 +UpdateStringOffset=20 +TotemPokemonIndices=[1,4,9,14,19,24,33,39,45] +TotemPokemonUnusedIndices=[] +AllyPokemonIndices=[8,13,17,18,23,31,32,37,38,43,44,48,49] +ShopItemSizes=[9, 11, 13, 15, 17, 19, 20, 21, 9, 4, 8, 12, 5, 4, 11, 3, 10, 6, 10, 6, 4, 5, 7, 1] +ShopCount=24 +TMShops=[12,13,16,18,19] +RegularShops=[0,1,2,3,4,5,6,7] +DoublesTrainerClasses=[172, 173, 174, 174, 175, 176, 177, 178, 179, 181, 182] +EliteFourIndices=[149, 152, 153, 156, 129, 413, 414] +CosmoemEvolutionNumber=791 +LinkedStaticEncounterOffsets=[112:113, 120:131, 124:130] // UBs probably need to be added to this too +MainGameLegendaries=[791] +ZygardeScriptLevelOffsets=[0x19D6, 0x19F8] +FullyUpdatedVersionNumber=2112 +CodeCRC32=[DE47EF73, 2A28CFAD] + +[Moon] +Game=CTR-P-BNEA +TitleId=0004000000175E00 +Type=SM +Acronym=Mo +CopyFrom=CTR-P-BNDA +File= +CosmoemEvolutionNumber=792 +MainGameLegendaries=[792] +CodeCRC32=[02948BA5, 8E4AE24C] + +[Ultra Sun] +Game=CTR-P-A2AA +TitleId=00040000001B5000 +Type=USUM +Acronym=US +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +File= +PokemonNamesTextOffset=60 +AbilityNamesTextOffset=101 +MoveNamesTextOffset=118 +MoveDescriptionsTextOffset=117 +ItemNamesTextOffset=40 +ItemDescriptionsTextOffset=39 +MapNamesTextOffset=72 +TrainerNamesTextOffset=110 +TrainerClassesTextOffset=111 +StarterTextOffset=39 +IngameTradesTextOffset=14 +TitleScreenTextOffset=99 +UpdateStringOffset=20 +TotemPokemonIndices=[1,4,9,14,19,24,33,39,45,137,146,158,159,160,162,229,231,249] +TotemPokemonUnusedIndices=[14,19,33] +AllyPokemonIndices=[8,13,17,18,23,31,32,37,38,43,44,48,49,140,141,147,148,163,164,230,232,250] +ShopItemSizes=[9, 12, 14, 16, 18, 20, 21, 22, 9, 4, 8, 12, 5, 4, 11, 3, 5, 6, 10, 5, 4, 5, 7, 5, 8, 8, 8, 8] +ShopCount=28 +TMShops=[12,13,16,18,19] +RegularShops=[0,1,2,3,4,5,6,7] +DoublesTrainerClasses=[172, 173, 174, 174, 175, 176, 177, 178, 179, 181, 182, 211, 212, 213, 214, 215] +EliteFourIndices=[149, 153, 156, 489, 494, 495, 496] +CosmoemEvolutionNumber=791 +LinkedStaticEncounterOffsets=[127:128, 135:146, 139:145] // Unused SM UBs need to be added to this, probably other stuff too +MainGameLegendaries=[800] +ZygardeScriptLevelOffsets=[0x1B3E, 0x1B60] +FullyUpdatedVersionNumber=2080 +CodeCRC32=[6DBB9B4D, CE281B2B] + +[Ultra Moon] +Game=CTR-P-A2BA +TitleId=00040000001B5100 +Type=USUM +Acronym=UM +CopyFrom=CTR-P-A2AA +File= +CosmoemEvolutionNumber=792 +CodeCRC32=[1C3F1290, 061A17D2] diff --git a/src/com/pkrandom/config/green_translation.tbl b/src/com/pkrandom/config/green_translation.tbl new file mode 100755 index 0000000..e01baf0 --- /dev/null +++ b/src/com/pkrandom/config/green_translation.tbl @@ -0,0 +1,66 @@ +7F= +87=' +8D=- +90=0 +91=1 +92=2 +93=3 +94=4 +95=5 +96=6 +97=7 +98=8 +99=9 +9F=? +A1=A +A2=B +A3=C +A4=D +A5=E +A6=F +A7=G +A8=H +A9=I +AA=J +AB=K +AC=L +AD=M +AE=N +AF=O +B0=P +B1=Q +B2=R +B3=S +B4=T +B5=U +B6=V +B7=W +B8=X +B9=Y +BA=Z +C1=a +C2=b +C3=c +C4=d +C5=e +C6=f +C7=g +C8=h +C9=i +CA=j +CB=k +CC=l +CD=m +CE=n +CF=o +D0=p +D1=q +D2=r +D3=s +D4=t +D5=u +D6=v +D7=w +D8=x +D9=y +DA=z \ No newline at end of file diff --git a/src/com/pkrandom/config/gsc_english.tbl b/src/com/pkrandom/config/gsc_english.tbl new file mode 100755 index 0000000..e5fe32a --- /dev/null +++ b/src/com/pkrandom/config/gsc_english.tbl @@ -0,0 +1,95 @@ +4A=[pk] +54=[POKé] +74=№ +75=… +7F= +79=┌ +7A=─ +7B=┐ +7C=│ +7D=└ +7E=┘ +80=A +81=B +82=C +83=D +84=E +85=F +86=G +87=H +88=I +89=J +8A=K +8B=L +8C=M +8D=N +8E=O +8F=P +90=Q +91=R +92=S +93=T +94=U +95=V +96=W +97=X +98=Y +99=Z +9A=( +9B=) +9C=: +9D=; +9E=[ +9F=] +A0=a +A1=b +A2=c +A3=d +A4=e +A5=f +A6=g +A7=h +A8=i +A9=j +AA=k +AB=l +AC=m +AD=n +AE=o +AF=p +B0=q +B1=r +B2=s +B3=t +B4=u +B5=v +B6=w +B7=x +B8=y +B9=z +C0=Ä +C1=Ö +C2=Ü +C3=ä +C4=ö +C5=ü +D0='d +D1='l +D2='m +D3='r +D4='s +D5='t +D6='v +E0=' +E1=[PK] +E2=[MN] +E3=- +E6=? +E7=! +E8=. +E9=& +EA=é +EB=→ +F0=$ +F2=[.] +F4=, \ No newline at end of file diff --git a/src/com/pkrandom/config/gsc_espita.tbl b/src/com/pkrandom/config/gsc_espita.tbl new file mode 100755 index 0000000..d9f603c --- /dev/null +++ b/src/com/pkrandom/config/gsc_espita.tbl @@ -0,0 +1,121 @@ +4A=[pk] +54=[POKé] +74=№ +75=… +7F= +79=┌ +7A=─ +7B=┐ +7C=│ +7D=└ +7E=┘ +80=A +81=B +82=C +83=D +84=E +85=F +86=G +87=H +88=I +89=J +8A=K +8B=L +8C=M +8D=N +8E=O +8F=P +90=Q +91=R +92=S +93=T +94=U +95=V +96=W +97=X +98=Y +99=Z +9A=( +9B=) +9C=: +9D=; +9E=[ +9F=] +A0=a +A1=b +A2=c +A3=d +A4=e +A5=f +A6=g +A7=h +A8=i +A9=j +AA=k +AB=l +AC=m +AD=n +AE=o +AF=p +B0=q +B1=r +B2=s +B3=t +B4=u +B5=v +B6=w +B7=x +B8=y +B9=z +BA=à +BB=è +BC=é +BD=ù +BE=À +BF=Á +C0=Ä +C1=Ö +C2=Ü +C3=ä +C4=ö +C5=ü +C6=È +C7=É +C8=Ì +C9=Í +CA=Ñ +CB=Ò +CC=Ó +CD=Ù +CE=Ú +CF=á +D0=ì +D1=í +D2=ñ +D3=ò +D4=ó +D5=ú +D6=° +D7=& +D8='d +D9='l +DA='m +DB='r +DC='s +DD='t +DE='v +E0=' +E1=[PK] +E2=[MN] +E3=- +E4=¿ +E5=¡ +E6=? +E7=! +E8=. +E9=& +EA=é +EB=→ +F0=$ +F2=[.] +F4=, \ No newline at end of file diff --git a/src/com/pkrandom/config/gsc_freger.tbl b/src/com/pkrandom/config/gsc_freger.tbl new file mode 100755 index 0000000..b896459 --- /dev/null +++ b/src/com/pkrandom/config/gsc_freger.tbl @@ -0,0 +1,114 @@ +4A=[pk] +54=[POKé] +74=№ +75=… +7F= +79=┌ +7A=─ +7B=┐ +7C=│ +7D=└ +7E=┘ +80=A +81=B +82=C +83=D +84=E +85=F +86=G +87=H +88=I +89=J +8A=K +8B=L +8C=M +8D=N +8E=O +8F=P +90=Q +91=R +92=S +93=T +94=U +95=V +96=W +97=X +98=Y +99=Z +9A=( +9B=) +9C=: +9D=; +9E=[ +9F=] +A0=a +A1=b +A2=c +A3=d +A4=e +A5=f +A6=g +A7=h +A8=i +A9=j +AA=k +AB=l +AC=m +AD=n +AE=o +AF=p +B0=q +B1=r +B2=s +B3=t +B4=u +B5=v +B6=w +B7=x +B8=y +B9=z +BA=à +BB=è +BC=é +BD=ù +BE=ß +BF=ç +C0=Ä +C1=Ö +C2=Ü +C3=ä +C4=ö +C5=ü +C6=ë +C7=ï +C8=â +C9=ô +CA=û +CB=ê +CC=î +D4=c' +D5=d' +D6=j' +D7=l' +D8=m' +D9=n' +DA=p' +DB=s' +DC='s +DD=t' +DE=u' +DF=y' +E0=' +E1=[PK] +E2=[MN] +E3=- +E4=+ +E6=? +E7=! +E8=. +E9=& +EA=é +EB=→ +F0=$ +F2=[.] +F4=, \ No newline at end of file diff --git a/src/com/pkrandom/config/rby_english.tbl b/src/com/pkrandom/config/rby_english.tbl new file mode 100755 index 0000000..88c21d6 --- /dev/null +++ b/src/com/pkrandom/config/rby_english.tbl @@ -0,0 +1,87 @@ +4A=[pk] +54=[POKé] +74=№ +75=… +7F= +79=┌ +7A=─ +7B=┐ +7C=│ +7D=└ +7E=┘ +80=A +81=B +82=C +83=D +84=E +85=F +86=G +87=H +88=I +89=J +8A=K +8B=L +8C=M +8D=N +8E=O +8F=P +90=Q +91=R +92=S +93=T +94=U +95=V +96=W +97=X +98=Y +99=Z +9A=( +9B=) +9C=: +9D=; +9E=[ +9F=] +A0=a +A1=b +A2=c +A3=d +A4=e +A5=f +A6=g +A7=h +A8=i +A9=j +AA=k +AB=l +AC=m +AD=n +AE=o +AF=p +B0=q +B1=r +B2=s +B3=t +B4=u +B5=v +B6=w +B7=x +B8=y +B9=z +BA=é +BB='d +BC='l +BD='s +BE='t +BF='v +E0=' +E1=[PK] +E2=[MN] +E3=- +E4='r +E5='m +E6=? +E7=! +E8=. +F0=$ +F2=[.] +F4=, \ No newline at end of file diff --git a/src/com/pkrandom/config/rby_espita.tbl b/src/com/pkrandom/config/rby_espita.tbl new file mode 100755 index 0000000..3ca057c --- /dev/null +++ b/src/com/pkrandom/config/rby_espita.tbl @@ -0,0 +1,118 @@ +4A=[pk] +54=[POKé] +74=№ +75=… +7F= +79=┌ +7A=─ +7B=┐ +7C=│ +7D=└ +7E=┘ +80=A +81=B +82=C +83=D +84=E +85=F +86=G +87=H +88=I +89=J +8A=K +8B=L +8C=M +8D=N +8E=O +8F=P +90=Q +91=R +92=S +93=T +94=U +95=V +96=W +97=X +98=Y +99=Z +9A=( +9B=) +9C=: +9D=; +9E=[ +9F=] +A0=a +A1=b +A2=c +A3=d +A4=e +A5=f +A6=g +A7=h +A8=i +A9=j +AA=k +AB=l +AC=m +AD=n +AE=o +AF=p +B0=q +B1=r +B2=s +B3=t +B4=u +B5=v +B6=w +B7=x +B8=y +B9=z +BA=à +BB=è +BC=é +BD=ù +BE=À +BF=Á +C0=Ä +C1=Ö +C2=Ü +C3=ä +C4=ö +C5=ü +C6=È +C7=É +C8=Ì +C9=Í +CA=Ñ +CB=Ò +CC=Ó +CD=Ù +CE=Ú +CF=á +D0=ì +D1=í +D2=ñ +D3=ò +D4=ó +D5=ú +D6=° +D7=& +D8='d +D9='l +DA='m +DB='r +DC='s +DD='t +DE='v +E0=' +E1=[PK] +E2=[MN] +E3=- +E4=¿ +E5=¡ +E6=? +E7=! +E8=. +F0=$ +F2=[.] +F4=, \ No newline at end of file diff --git a/src/com/pkrandom/config/rby_freger.tbl b/src/com/pkrandom/config/rby_freger.tbl new file mode 100755 index 0000000..93ad462 --- /dev/null +++ b/src/com/pkrandom/config/rby_freger.tbl @@ -0,0 +1,111 @@ +4A=[pk] +54=[POKé] +74=№ +75=… +7F= +79=┌ +7A=─ +7B=┐ +7C=│ +7D=└ +7E=┘ +80=A +81=B +82=C +83=D +84=E +85=F +86=G +87=H +88=I +89=J +8A=K +8B=L +8C=M +8D=N +8E=O +8F=P +90=Q +91=R +92=S +93=T +94=U +95=V +96=W +97=X +98=Y +99=Z +9A=( +9B=) +9C=: +9D=; +9E=[ +9F=] +A0=a +A1=b +A2=c +A3=d +A4=e +A5=f +A6=g +A7=h +A8=i +A9=j +AA=k +AB=l +AC=m +AD=n +AE=o +AF=p +B0=q +B1=r +B2=s +B3=t +B4=u +B5=v +B6=w +B7=x +B8=y +B9=z +BA=à +BB=è +BC=é +BD=ù +BE=ß +BF=ç +C0=Ä +C1=Ö +C2=Ü +C3=ä +C4=ö +C5=ü +C6=ë +C7=ï +C8=â +C9=ô +CA=û +CB=ê +CC=î +D4=c' +D5=d' +D6=j' +D7=l' +D8=m' +D9=n' +DA=p' +DB=s' +DC='s +DD=t' +DE=u' +DF=y' +E0=' +E1=[PK] +E2=[MN] +E3=- +E4=+ +E6=? +E7=! +E8=. +F0=$ +F2=[.] +F4=, \ No newline at end of file diff --git a/src/com/pkrandom/config/realistic_gen1_english.tbl b/src/com/pkrandom/config/realistic_gen1_english.tbl new file mode 100644 index 0000000..403e1d0 --- /dev/null +++ b/src/com/pkrandom/config/realistic_gen1_english.tbl @@ -0,0 +1,254 @@ +01=□ +02=□ +03=□ +04=□ +05=□ +06=□ +07=□ +08=□ +09=□ +0A=□ +0B=□ +0C=□ +0D=□ +0E=□ +0F=□ +10=□ +11=□ +12=□ +13=□ +14=□ +15=□ +16=□ +17=□ +18=□ +19=□ +1A=□ +1B=□ +1C=□ +1D=□ +1E=□ +1F=□ +20=□ +21=□ +22=□ +23=□ +24=□ +25=□ +26=□ +27=□ +28=□ +29=□ +2A=□ +2B=□ +2C=□ +2D=□ +2E=□ +2F=□ +30=□ +31=□ +32=□ +33=□ +34=□ +35=□ +36=□ +37=□ +38=□ +39=□ +3A=□ +3B=□ +3C=□ +3D=□ +3E=□ +3F=□ +40=□ +41=□ +42=□ +43=□ +44=□ +45=□ +46=□ +47=□ +48=□ +49=[49] +4A=[PKMN] +4B=[4B] +4C=[4C] +4D=□ +4E=[4E] +4F=\n +51=\p +52=[Player] +53=[Rival] +54=POKé +55=\l +56=…… +57=\e +58=\x +59=[OtherMon] +5A=[CurrentMon] +5B=PC +5C=TM +5D=TRAINER +5E=ROCKET +5F=. +60=ᴀ +61=ʙ +62=ᴄ +63=ᴅ +64=ᴇ +65=ғ +66=ɢ +67=ʜ +68=ɪ +69=ᴠ +6A=s +6B=ʟ +6C=ᴍ +6D=: +6E=ぃ +6F=ぅ +70=‘ +71=’ +72=“ +73=” +74=• +75=… +76=ぁ +77=ぇ +78=ぉ +79=┌ +7A=─ +7B=┐ +7C=│ +7D=└ +7E=┘ +7F= +80=A +81=B +82=C +83=D +84=E +85=F +86=G +87=H +88=I +89=J +8A=K +8B=L +8C=M +8D=N +8E=O +8F=P +90=Q +91=R +92=S +93=T +94=U +95=V +96=W +97=X +98=Y +99=Z +9A=( +9B=) +9C=: +9D=; +9E=[ +9F=] +A0=a +A1=b +A2=c +A3=d +A4=e +A5=f +A6=g +A7=h +A8=i +A9=j +AA=k +AB=l +AC=m +AD=n +AE=o +AF=p +B0=q +B1=r +B2=s +B3=t +B4=u +B5=v +B6=w +B7=x +B8=y +B9=z +BA=é +BB='d +BC='l +BD='s +BE='t +BF='v +C0= +C1= +C2= +C3= +C4= +C5= +C6= +C7= +C8= +C9= +CA= +CB= +CC= +CD= +CE= +CF= +D0= +D1= +D2= +D3= +D4= +D5= +D6= +D7= +D8= +D9= +DA= +DB= +DC= +DD= +DE= +DF= +E0=' +E1=PK +E2=MN +E3=- +E4='r +E5='m +E6=? +E7=! +E8=. +E9=ァ +EA=ゥ +EB=ェ +EC=˃ +ED=˃ +EE=˅ +EF=♂ +F0=$ +F1=× +F2=. +F3=/ +F4=, +F5=♀ +F6=0 +F7=1 +F8=2 +F9=3 +FA=4 +FB=5 +FC=6 +FD=7 +FE=8 +FF=9 \ No newline at end of file diff --git a/src/com/pkrandom/config/vietcrystal.tbl b/src/com/pkrandom/config/vietcrystal.tbl new file mode 100755 index 0000000..71a06ce --- /dev/null +++ b/src/com/pkrandom/config/vietcrystal.tbl @@ -0,0 +1,54 @@ +7F= +80=! +81=" +82=% +83=& +84=' +85=( +86=) +87=+ +88=, +89=- +8A=. +8B=/ +8C=0 +8D=1 +8E=2 +8F=3 +90=4 +91=5 +92=6 +93=7 +94=8 +95=9 +96=: +97=; +98== +99=> +9A=? +9B=A +9C=B +9D=C +9E=D +9F=E +A0=F +A1=G +A2=H +A3=I +A4=J +A5=K +A6=L +A7=M +A8=N +A9=O +AA=P +AB=Q +AC=R +AD=S +AE=T +AF=U +B0=V +B1=W +B2=X +B3=Y +B4=Z diff --git a/src/com/pkrandom/constants/Abilities.java b/src/com/pkrandom/constants/Abilities.java new file mode 100644 index 0000000..ee4819d --- /dev/null +++ b/src/com/pkrandom/constants/Abilities.java @@ -0,0 +1,294 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Abilities.java - defines an index number constant for every Ability. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class Abilities { + public static final int stench = 1; + public static final int drizzle = 2; + public static final int speedBoost = 3; + public static final int battleArmor = 4; + public static final int sturdy = 5; + public static final int damp = 6; + public static final int limber = 7; + public static final int sandVeil = 8; + public static final int staticTheAbilityNotTheKeyword = 9; // lol + public static final int voltAbsorb = 10; + public static final int waterAbsorb = 11; + public static final int oblivious = 12; + public static final int cloudNine = 13; + public static final int compoundEyes = 14; + public static final int insomnia = 15; + public static final int colorChange = 16; + public static final int immunity = 17; + public static final int flashFire = 18; + public static final int shieldDust = 19; + public static final int ownTempo = 20; + public static final int suctionCups = 21; + public static final int intimidate = 22; + public static final int shadowTag = 23; + public static final int roughSkin = 24; + public static final int wonderGuard = 25; + public static final int levitate = 26; + public static final int effectSpore = 27; + public static final int synchronize = 28; + public static final int clearBody = 29; + public static final int naturalCure = 30; + public static final int lightningRod = 31; + public static final int sereneGrace = 32; + public static final int swiftSwim = 33; + public static final int chlorophyll = 34; + public static final int illuminate = 35; + public static final int trace = 36; + public static final int hugePower = 37; + public static final int poisonPoint = 38; + public static final int innerFocus = 39; + public static final int magmaArmor = 40; + public static final int waterVeil = 41; + public static final int magnetPull = 42; + public static final int soundproof = 43; + public static final int rainDish = 44; + public static final int sandStream = 45; + public static final int pressure = 46; + public static final int thickFat = 47; + public static final int earlyBird = 48; + public static final int flameBody = 49; + public static final int runAway = 50; + public static final int keenEye = 51; + public static final int hyperCutter = 52; + public static final int pickup = 53; + public static final int truant = 54; + public static final int hustle = 55; + public static final int cuteCharm = 56; + public static final int plus = 57; + public static final int minus = 58; + public static final int forecast = 59; + public static final int stickyHold = 60; + public static final int shedSkin = 61; + public static final int guts = 62; + public static final int marvelScale = 63; + public static final int liquidOoze = 64; + public static final int overgrow = 65; + public static final int blaze = 66; + public static final int torrent = 67; + public static final int swarm = 68; + public static final int rockHead = 69; + public static final int drought = 70; + public static final int arenaTrap = 71; + public static final int vitalSpirit = 72; + public static final int whiteSmoke = 73; + public static final int purePower = 74; + public static final int shellArmor = 75; + public static final int airLock = 76; // Was 77 in Gen 3, since 76 was Cacophony + public static final int tangledFeet = 77; + public static final int motorDrive = 78; + public static final int rivalry = 79; + public static final int steadfast = 80; + public static final int snowCloak = 81; + public static final int gluttony = 82; + public static final int angerPoint = 83; + public static final int unburden = 84; + public static final int heatproof = 85; + public static final int simple = 86; + public static final int drySkin = 87; + public static final int download = 88; + public static final int ironFist = 89; + public static final int poisonHeal = 90; + public static final int adaptability = 91; + public static final int skillLink = 92; + public static final int hydration = 93; + public static final int solarPower = 94; + public static final int quickFeet = 95; + public static final int normalize = 96; + public static final int sniper = 97; + public static final int magicGuard = 98; + public static final int noGuard = 99; + public static final int stall = 100; + public static final int technician = 101; + public static final int leafGuard = 102; + public static final int klutz = 103; + public static final int moldBreaker = 104; + public static final int superLuck = 105; + public static final int aftermath = 106; + public static final int anticipation = 107; + public static final int forewarn = 108; + public static final int unaware = 109; + public static final int tintedLens = 110; + public static final int filter = 111; + public static final int slowStart = 112; + public static final int scrappy = 113; + public static final int stormDrain = 114; + public static final int iceBody = 115; + public static final int solidRock = 116; + public static final int snowWarning = 117; + public static final int honeyGather = 118; + public static final int frisk = 119; + public static final int reckless = 120; + public static final int multitype = 121; + public static final int flowerGift = 122; + public static final int badDreams = 123; + public static final int pickpocket = 124; + public static final int sheerForce = 125; + public static final int contrary = 126; + public static final int unnerve = 127; + public static final int defiant = 128; + public static final int defeatist = 129; + public static final int cursedBody = 130; + public static final int healer = 131; + public static final int friendGuard = 132; + public static final int weakArmor = 133; + public static final int heavyMetal = 134; + public static final int lightMetal = 135; + public static final int multiscale = 136; + public static final int toxicBoost = 137; + public static final int flareBoost = 138; + public static final int harvest = 139; + public static final int telepathy = 140; + public static final int moody = 141; + public static final int overcoat = 142; + public static final int poisonTouch = 143; + public static final int regenerator = 144; + public static final int bigPecks = 145; + public static final int sandRush = 146; + public static final int wonderSkin = 147; + public static final int analytic = 148; + public static final int illusion = 149; + public static final int imposter = 150; + public static final int infiltrator = 151; + public static final int mummy = 152; + public static final int moxie = 153; + public static final int justified = 154; + public static final int rattled = 155; + public static final int magicBounce = 156; + public static final int sapSipper = 157; + public static final int prankster = 158; + public static final int sandForce = 159; + public static final int ironBarbs = 160; + public static final int zenMode = 161; + public static final int victoryStar = 162; + public static final int turboblaze = 163; + public static final int teravolt = 164; + public static final int aromaVeil = 165; + public static final int flowerVeil = 166; + public static final int cheekPouch = 167; + public static final int protean = 168; + public static final int furCoat = 169; + public static final int magician = 170; + public static final int bulletproof = 171; + public static final int competitive = 172; + public static final int strongJaw = 173; + public static final int refrigerate = 174; + public static final int sweetVeil = 175; + public static final int stanceChange = 176; + public static final int galeWings = 177; + public static final int megaLauncher = 178; + public static final int grassPelt = 179; + public static final int symbiosis = 180; + public static final int toughClaws = 181; + public static final int pixilate = 182; + public static final int gooey = 183; + public static final int aerilate = 184; + public static final int parentalBond = 185; + public static final int darkAura = 186; + public static final int fairyAura = 187; + public static final int auraBreak = 188; + public static final int primordialSea = 189; + public static final int desolateLand = 190; + public static final int deltaStream = 191; + public static final int stamina = 192; + public static final int wimpOut = 193; + public static final int emergencyExit = 194; + public static final int waterCompaction = 195; + public static final int merciless = 196; + public static final int shieldsDown = 197; + public static final int stakeout = 198; + public static final int waterBubble = 199; + public static final int steelworker = 200; + public static final int berserk = 201; + public static final int slushRush = 202; + public static final int longReach = 203; + public static final int liquidVoice = 204; + public static final int triage = 205; + public static final int galvanize = 206; + public static final int surgeSurfer = 207; + public static final int schooling = 208; + public static final int disguise = 209; + public static final int battleBond = 210; + public static final int powerConstruct = 211; + public static final int corrosion = 212; + public static final int comatose = 213; + public static final int queenlyMajesty = 214; + public static final int innardsOut = 215; + public static final int dancer = 216; + public static final int battery = 217; + public static final int fluffy = 218; + public static final int dazzling = 219; + public static final int soulHeart = 220; + public static final int tanglingHair = 221; + public static final int receiver = 222; + public static final int powerOfAlchemy = 223; + public static final int beastBoost = 224; + public static final int rksSystem = 225; + public static final int electricSurge = 226; + public static final int psychicSurge = 227; + public static final int mistySurge = 228; + public static final int grassySurge = 229; + public static final int fullMetalBody = 230; + public static final int shadowShield = 231; + public static final int prismArmor = 232; + public static final int neuroforce = 233; + public static final int intrepidSword = 234; + public static final int dauntlessShield = 235; + public static final int libero = 236; + public static final int ballFetch = 237; + public static final int cottonDown = 238; + public static final int propellerTail = 239; + public static final int mirrorArmor = 240; + public static final int gulpMissile = 241; + public static final int stalwart = 242; + public static final int steamEngine = 243; + public static final int punkRock = 244; + public static final int sandSpit = 245; + public static final int iceScales = 246; + public static final int ripen = 247; + public static final int iceFace = 248; + public static final int powerSpot = 249; + public static final int mimicry = 250; + public static final int screenCleaner = 251; + public static final int steelySpirit = 252; + public static final int perishBody = 253; + public static final int wanderingSpirit = 254; + public static final int gorillaTactics = 255; + public static final int neutralizingGas = 256; + public static final int pastelVeil = 257; + public static final int hungerSwitch = 258; + public static final int quickDraw = 259; + public static final int unseenFist = 260; + public static final int curiousMedicine = 261; + public static final int transistor = 262; + public static final int dragonsMaw = 263; + public static final int chillingNeigh = 264; + public static final int grimNeigh = 265; + public static final int asOneChillingNeigh = 266; + public static final int asOneGrimNeigh = 267; +} diff --git a/src/com/pkrandom/constants/GBConstants.java b/src/com/pkrandom/constants/GBConstants.java new file mode 100644 index 0000000..bc47bb3 --- /dev/null +++ b/src/com/pkrandom/constants/GBConstants.java @@ -0,0 +1,52 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- GBConstants.java - constants that are relevant for all of the GB --*/ +/*-- games --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.pokemon.Type; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class GBConstants { + + public static final int minRomSize = 0x80000, maxRomSize = 0x200000; + + public static final int jpFlagOffset = 0x14A, versionOffset = 0x14C, crcOffset = 0x14E, romSigOffset = 0x134, + isGBCOffset = 0x143, romCodeOffset = 0x13F; + + public static final int stringTerminator = 0x50, stringPrintedTextEnd = 0x57, stringPrintedTextPromptEnd = 0x58; + + public static final int bankSize = 0x4000; + + public static final byte gbZ80Jump = (byte) 0xC3, gbZ80Nop = 0x00, gbZ80XorA = (byte) 0xAF, gbZ80LdA = 0x3E, + gbZ80LdAToFar = (byte) 0xEA, gbZ80Ret = (byte) 0xC9, gbZ80JumpRelative = (byte) 0x18; + + public static final Set physicalTypes = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + Type.NORMAL, Type.FIGHTING, Type.POISON, Type.GROUND, Type.FLYING, Type.BUG, + Type.ROCK, Type.GHOST, Type.STEEL))); + +} diff --git a/src/com/pkrandom/constants/Gen1Constants.java b/src/com/pkrandom/constants/Gen1Constants.java new file mode 100644 index 0000000..5b2b725 --- /dev/null +++ b/src/com/pkrandom/constants/Gen1Constants.java @@ -0,0 +1,352 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Gen1Constants.java - Constants for Red/Green/Blue/Yellow --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.pkrandom.pokemon.ItemList; +import com.pkrandom.pokemon.Trainer; +import com.pkrandom.pokemon.Type; + +public class Gen1Constants { + + public static final int baseStatsEntrySize = 0x1C; + + public static final int bsHPOffset = 1, bsAttackOffset = 2, bsDefenseOffset = 3, bsSpeedOffset = 4, + bsSpecialOffset = 5, bsPrimaryTypeOffset = 6, bsSecondaryTypeOffset = 7, bsCatchRateOffset = 8, + bsExpYieldOffset = 9, bsFrontSpriteOffset = 11, bsLevel1MovesOffset = 15, bsGrowthCurveOffset = 19, + bsTMHMCompatOffset = 20; + + public static final int encounterTableEnd = 0xFFFF, encounterTableSize = 10, yellowSuperRodTableSize = 4; + + public static final int trainerClassCount = 47; + + public static final int champRivalOffsetFromGymLeaderMoves = 0x44; + + public static final int tmCount = 50, hmCount = 5; + + public static final int[] gymLeaderTMs = new int[] { 34, 11, 24, 21, 6, 46, 38, 27 }; + + public static final int[] tclassesCounts = new int[] { 21, 47 }; + + public static final List singularTrainers = Arrays.asList(28, 32, 33, 34, 35, 36, 37, 38, 39, 43, 45, 46); + + public static final List bannedMovesWithXAccBanned = Arrays.asList( + Moves.sonicBoom, Moves.dragonRage, Moves.spore); + + public static final List bannedMovesWithoutXAccBanned = Arrays.asList( + Moves.sonicBoom, Moves.dragonRage, Moves.spore, Moves.hornDrill, Moves.fissure, Moves.guillotine); + + // ban transform because of Transform assumption glitch + public static final List bannedLevelupMoves = Collections.singletonList(Moves.transform); + + public static final List fieldMoves = Arrays.asList( + Moves.cut, Moves.fly, Moves.surf, Moves.strength, Moves.flash, Moves.dig, Moves.teleport); + + public static final int damagePoison20PercentEffect = 2, damageAbsorbEffect = 3, damageBurn10PercentEffect = 4, + damageFreeze10PercentEffect = 5, damageParalyze10PercentEffect = 6, dreamEaterEffect = 8, + noDamageAtkPlusOneEffect = 10, noDamageDefPlusOneEffect = 11, noDamageSpecialPlusOneEffect = 13, + noDamageEvasionPlusOneEffect = 15, noDamageAtkMinusOneEffect = 18, noDamageDefMinusOneEffect = 19, + noDamageSpeMinusOneEffect = 20, noDamageAccuracyMinusOneEffect = 22, flinch10PercentEffect = 31, + noDamageSleepEffect = 32, damagePoison40PercentEffect = 33, damageBurn30PercentEffect = 34, + damageFreeze30PercentEffect = 35, damageParalyze30PercentEffect = 36, flinch30PercentEffect = 37, + chargeEffect = 39, flyEffect = 43, damageRecoilEffect = 48, noDamageConfusionEffect = 49, + noDamageAtkPlusTwoEffect = 50, noDamageDefPlusTwoEffect = 51, noDamageSpePlusTwoEffect = 52, + noDamageSpecialPlusTwoEffect = 53, noDamageDefMinusTwoEffect = 59, noDamagePoisonEffect = 66, + noDamageParalyzeEffect = 67, damageAtkMinusOneEffect = 68, damageDefMinusOneEffect = 69, + damageSpeMinusOneEffect = 70, damageSpecialMinusOneEffect = 71, damageConfusionEffect = 76, + twineedleEffect = 77, hyperBeamEffect = 80; + + // Taken from critical_hit_moves.asm; we could read this from the ROM, but it's easier to hardcode it. + public static final List increasedCritMoves = Arrays.asList(Moves.karateChop, Moves.razorLeaf, Moves.crabhammer, Moves.slash); + + public static final List earlyRequiredHMs = Collections.singletonList(Moves.cut); + + public static final int hmsStartIndex = Gen1Items.hm01, tmsStartIndex = Gen1Items.tm01; + + public static final List requiredFieldTMs = Arrays.asList(3, 4, 8, 10, 12, 14, 16, 19, 20, + 22, 25, 26, 30, 40, 43, 44, 45, 47); + + public static final int towerMapsStartIndex = 0x90, towerMapsEndIndex = 0x94; + + public static final String guaranteedCatchPrefix = "CF7EFE01"; + + public static final Type[] typeTable = constructTypeTable(); + + private static Type[] constructTypeTable() { + Type[] table = new Type[0x20]; + table[0x00] = Type.NORMAL; + table[0x01] = Type.FIGHTING; + table[0x02] = Type.FLYING; + table[0x03] = Type.POISON; + table[0x04] = Type.GROUND; + table[0x05] = Type.ROCK; + table[0x07] = Type.BUG; + table[0x08] = Type.GHOST; + table[0x14] = Type.FIRE; + table[0x15] = Type.WATER; + table[0x16] = Type.GRASS; + table[0x17] = Type.ELECTRIC; + table[0x18] = Type.PSYCHIC; + table[0x19] = Type.ICE; + table[0x1A] = Type.DRAGON; + return table; + } + + public static byte typeToByte(Type type) { + for (int i = 0; i < typeTable.length; i++) { + if (typeTable[i] == type) { + return (byte) i; + } + } + return (byte) 0; + } + + public static final ItemList allowedItems = setupAllowedItems(); + + private static ItemList setupAllowedItems() { + ItemList allowedItems = new ItemList(Gen1Items.tm50); // 251-255 are junk TMs + // Assorted key items & junk + // 23/01/2014: ban fake PP Up + allowedItems.banSingles(Gen1Items.townMap, Gen1Items.bicycle, Gen1Items.questionMark7, + Gen1Items.safariBall, Gen1Items.pokedex, Gen1Items.oldAmber, Gen1Items.cardKey, Gen1Items.ppUpGlitch, + Gen1Items.coin, Gen1Items.ssTicket, Gen1Items.goldTeeth); + allowedItems.banRange(Gen1Items.boulderBadge, 8); + allowedItems.banRange(Gen1Items.domeFossil, 5); + allowedItems.banRange(Gen1Items.coinCase, 10); + // Unused + allowedItems.banRange(Gen1Items.unused84, 112); + // HMs + allowedItems.banRange(hmsStartIndex, hmCount); + // Real TMs + allowedItems.tmRange(tmsStartIndex, tmCount); + return allowedItems; + } + + public static void tagTrainersUniversal(List trs) { + // Gym Leaders + tbc(trs, 34, 0, "GYM1"); + tbc(trs, 35, 0, "GYM2"); + tbc(trs, 36, 0, "GYM3"); + tbc(trs, 37, 0, "GYM4"); + tbc(trs, 38, 0, "GYM5"); + tbc(trs, 40, 0, "GYM6"); + tbc(trs, 39, 0, "GYM7"); + tbc(trs, 29, 2, "GYM8"); + + // Other giovanni teams + tbc(trs, 29, 0, "GIO1"); + tbc(trs, 29, 1, "GIO2"); + + // Elite 4 + tbc(trs, 44, 0, "ELITE1"); + tbc(trs, 33, 0, "ELITE2"); + tbc(trs, 46, 0, "ELITE3"); + tbc(trs, 47, 0, "ELITE4"); + } + + public static void tagTrainersRB(List trs) { + // Gary Battles + tbc(trs, 25, 0, "RIVAL1-0"); + tbc(trs, 25, 1, "RIVAL1-1"); + tbc(trs, 25, 2, "RIVAL1-2"); + + tbc(trs, 25, 3, "RIVAL2-0"); + tbc(trs, 25, 4, "RIVAL2-1"); + tbc(trs, 25, 5, "RIVAL2-2"); + + tbc(trs, 25, 6, "RIVAL3-0"); + tbc(trs, 25, 7, "RIVAL3-1"); + tbc(trs, 25, 8, "RIVAL3-2"); + + tbc(trs, 42, 0, "RIVAL4-0"); + tbc(trs, 42, 1, "RIVAL4-1"); + tbc(trs, 42, 2, "RIVAL4-2"); + + tbc(trs, 42, 3, "RIVAL5-0"); + tbc(trs, 42, 4, "RIVAL5-1"); + tbc(trs, 42, 5, "RIVAL5-2"); + + tbc(trs, 42, 6, "RIVAL6-0"); + tbc(trs, 42, 7, "RIVAL6-1"); + tbc(trs, 42, 8, "RIVAL6-2"); + + tbc(trs, 42, 9, "RIVAL7-0"); + tbc(trs, 42, 10, "RIVAL7-1"); + tbc(trs, 42, 11, "RIVAL7-2"); + + tbc(trs, 43, 0, "RIVAL8-0"); + tbc(trs, 43, 1, "RIVAL8-1"); + tbc(trs, 43, 2, "RIVAL8-2"); + + // Gym Trainers + tbc(trs, 5, 0, "GYM1"); + + tbc(trs, 15, 0, "GYM2"); + tbc(trs, 6, 0, "GYM2"); + + tbc(trs, 4, 7, "GYM3"); + tbc(trs, 20, 0, "GYM3"); + tbc(trs, 41, 2, "GYM3"); + + tbc(trs, 3, 16, "GYM4"); + tbc(trs, 3, 17, "GYM4"); + tbc(trs, 6, 10, "GYM4"); + tbc(trs, 18, 0, "GYM4"); + tbc(trs, 18, 1, "GYM4"); + tbc(trs, 18, 2, "GYM4"); + tbc(trs, 32, 0, "GYM4"); + + tbc(trs, 21, 2, "GYM5"); + tbc(trs, 21, 3, "GYM5"); + tbc(trs, 21, 6, "GYM5"); + tbc(trs, 21, 7, "GYM5"); + tbc(trs, 22, 0, "GYM5"); + tbc(trs, 22, 1, "GYM5"); + + tbc(trs, 19, 0, "GYM6"); + tbc(trs, 19, 1, "GYM6"); + tbc(trs, 19, 2, "GYM6"); + tbc(trs, 19, 3, "GYM6"); + tbc(trs, 45, 21, "GYM6"); + tbc(trs, 45, 22, "GYM6"); + tbc(trs, 45, 23, "GYM6"); + + tbc(trs, 8, 8, "GYM7"); + tbc(trs, 8, 9, "GYM7"); + tbc(trs, 8, 10, "GYM7"); + tbc(trs, 8, 11, "GYM7"); + tbc(trs, 11, 3, "GYM7"); + tbc(trs, 11, 4, "GYM7"); + tbc(trs, 11, 5, "GYM7"); + + tbc(trs, 22, 2, "GYM8"); + tbc(trs, 22, 3, "GYM8"); + tbc(trs, 24, 5, "GYM8"); + tbc(trs, 24, 6, "GYM8"); + tbc(trs, 24, 7, "GYM8"); + tbc(trs, 31, 0, "GYM8"); + tbc(trs, 31, 8, "GYM8"); + tbc(trs, 31, 9, "GYM8"); + } + + public static void tagTrainersYellow(List trs) { + // Rival Battles + tbc(trs, 25, 0, "IRIVAL"); + + tbc(trs, 25, 1, "RIVAL1-0"); + + tbc(trs, 25, 2, "RIVAL2-0"); + + tbc(trs, 42, 0, "RIVAL3-0"); + + tbc(trs, 42, 1, "RIVAL4-0"); + tbc(trs, 42, 2, "RIVAL4-1"); + tbc(trs, 42, 3, "RIVAL4-2"); + + tbc(trs, 42, 4, "RIVAL5-0"); + tbc(trs, 42, 5, "RIVAL5-1"); + tbc(trs, 42, 6, "RIVAL5-2"); + + tbc(trs, 42, 7, "RIVAL6-0"); + tbc(trs, 42, 8, "RIVAL6-1"); + tbc(trs, 42, 9, "RIVAL6-2"); + + tbc(trs, 43, 0, "RIVAL7-0"); + tbc(trs, 43, 1, "RIVAL7-1"); + tbc(trs, 43, 2, "RIVAL7-2"); + + // Rocket Jessie & James + tbc(trs, 30, 41, "THEMED:JESSIE&JAMES"); + tbc(trs, 30, 42, "THEMED:JESSIE&JAMES"); + tbc(trs, 30, 43, "THEMED:JESSIE&JAMES"); + tbc(trs, 30, 44, "THEMED:JESSIE&JAMES"); + + // Gym Trainers + tbc(trs, 5, 0, "GYM1"); + + tbc(trs, 6, 0, "GYM2"); + tbc(trs, 15, 0, "GYM2"); + + tbc(trs, 4, 7, "GYM3"); + tbc(trs, 20, 0, "GYM3"); + tbc(trs, 41, 2, "GYM3"); + + tbc(trs, 3, 16, "GYM4"); + tbc(trs, 3, 17, "GYM4"); + tbc(trs, 6, 10, "GYM4"); + tbc(trs, 18, 0, "GYM4"); + tbc(trs, 18, 1, "GYM4"); + tbc(trs, 18, 2, "GYM4"); + tbc(trs, 32, 0, "GYM4"); + + tbc(trs, 21, 2, "GYM5"); + tbc(trs, 21, 3, "GYM5"); + tbc(trs, 21, 6, "GYM5"); + tbc(trs, 21, 7, "GYM5"); + tbc(trs, 22, 0, "GYM5"); + tbc(trs, 22, 1, "GYM5"); + + tbc(trs, 19, 0, "GYM6"); + tbc(trs, 19, 1, "GYM6"); + tbc(trs, 19, 2, "GYM6"); + tbc(trs, 19, 3, "GYM6"); + tbc(trs, 45, 21, "GYM6"); + tbc(trs, 45, 22, "GYM6"); + tbc(trs, 45, 23, "GYM6"); + + tbc(trs, 8, 8, "GYM7"); + tbc(trs, 8, 9, "GYM7"); + tbc(trs, 8, 10, "GYM7"); + tbc(trs, 8, 11, "GYM7"); + tbc(trs, 11, 3, "GYM7"); + tbc(trs, 11, 4, "GYM7"); + tbc(trs, 11, 5, "GYM7"); + + tbc(trs, 22, 2, "GYM8"); + tbc(trs, 22, 3, "GYM8"); + tbc(trs, 24, 5, "GYM8"); + tbc(trs, 24, 6, "GYM8"); + tbc(trs, 24, 7, "GYM8"); + tbc(trs, 31, 0, "GYM8"); + tbc(trs, 31, 8, "GYM8"); + tbc(trs, 31, 9, "GYM8"); + } + + private static void tbc(List allTrainers, int classNum, int number, String tag) { + int currnum = -1; + for (Trainer t : allTrainers) { + if (t.trainerclass == classNum) { + currnum++; + if (currnum == number) { + t.tag = tag; + return; + } + } + } + } + +} diff --git a/src/com/pkrandom/constants/Gen1Items.java b/src/com/pkrandom/constants/Gen1Items.java new file mode 100644 index 0000000..8374ed1 --- /dev/null +++ b/src/com/pkrandom/constants/Gen1Items.java @@ -0,0 +1,285 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Gen1Items.java - defines an index number constant for every item in --*/ +/*-- the Generation 1 games. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class Gen1Items { + // https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_(Generation_I) + public static final int nothing = 0; + public static final int masterBall = 1; + public static final int ultraBall = 2; + public static final int greatBall = 3; + public static final int pokeBall = 4; + public static final int townMap = 5; + public static final int bicycle = 6; + public static final int questionMark7 = 7; + public static final int safariBall = 8; + public static final int pokedex = 9; + public static final int moonStone = 10; + public static final int antidote = 11; + public static final int burnHeal = 12; + public static final int iceHeal = 13; + public static final int awakening = 14; + public static final int parlyzHeal = 15; + public static final int fullRestore = 16; + public static final int maxPotion = 17; + public static final int hyperPotion = 18; + public static final int superPotion = 19; + public static final int potion = 20; + public static final int boulderBadge = 21; + public static final int cascadeBadge = 22; + public static final int thunderBadge = 23; + public static final int rainbowBadge = 24; + public static final int soulBadge = 25; + public static final int marshBadge = 26; + public static final int volcanoBadge = 27; + public static final int earthBadge = 28; + public static final int escapeRope = 29; + public static final int repel = 30; + public static final int oldAmber = 31; + public static final int fireStone = 32; + public static final int thunderstone = 33; + public static final int waterStone = 34; + public static final int hpUp = 35; + public static final int protein = 36; + public static final int iron = 37; + public static final int carbos = 38; + public static final int calcium = 39; + public static final int rareCandy = 40; + public static final int domeFossil = 41; + public static final int helixFossil = 42; + public static final int secretKey = 43; + public static final int questionMark44 = 44; + public static final int bikeVoucher = 45; + public static final int xAccuracy = 46; + public static final int leafStone = 47; + public static final int cardKey = 48; + public static final int nugget = 49; + public static final int ppUpGlitch = 50; + public static final int pokeDoll = 51; + public static final int fullHeal = 52; + public static final int revive = 53; + public static final int maxRevive = 54; + public static final int guardSpec = 55; + public static final int superRepel = 56; + public static final int maxRepel = 57; + public static final int direHit = 58; + public static final int coin = 59; + public static final int freshWater = 60; + public static final int sodaPop = 61; + public static final int lemonade = 62; + public static final int ssTicket = 63; + public static final int goldTeeth = 64; + public static final int xAttack = 65; + public static final int xDefend = 66; + public static final int xSpeed = 67; + public static final int xSpecial = 68; + public static final int coinCase = 69; + public static final int oaksParcel = 70; + public static final int itemfinder = 71; + public static final int silphScope = 72; + public static final int pokeFlute = 73; + public static final int liftKey = 74; + public static final int expAll = 75; + public static final int oldRod = 76; + public static final int goodRod = 77; + public static final int superRod = 78; + public static final int ppUp = 79; + public static final int ether = 80; + public static final int maxEther = 81; + public static final int elixer = 82; + public static final int maxElixer = 83; + public static final int unused84 = 84; + public static final int unused85 = 85; + public static final int unused86 = 86; + public static final int unused87 = 87; + public static final int unused88 = 88; + public static final int unused89 = 89; + public static final int unused90 = 90; + public static final int unused91 = 91; + public static final int unused92 = 92; + public static final int unused93 = 93; + public static final int unused94 = 94; + public static final int unused95 = 95; + public static final int unused96 = 96; + public static final int unused97 = 97; + public static final int unused98 = 98; + public static final int unused99 = 99; + public static final int unused100 = 100; + public static final int unused101 = 101; + public static final int unused102 = 102; + public static final int unused103 = 103; + public static final int unused104 = 104; + public static final int unused105 = 105; + public static final int unused106 = 106; + public static final int unused107 = 107; + public static final int unused108 = 108; + public static final int unused109 = 109; + public static final int unused110 = 110; + public static final int unused111 = 111; + public static final int unused112 = 112; + public static final int unused113 = 113; + public static final int unused114 = 114; + public static final int unused115 = 115; + public static final int unused116 = 116; + public static final int unused117 = 117; + public static final int unused118 = 118; + public static final int unused119 = 119; + public static final int unused120 = 120; + public static final int unused121 = 121; + public static final int unused122 = 122; + public static final int unused123 = 123; + public static final int unused124 = 124; + public static final int unused125 = 125; + public static final int unused126 = 126; + public static final int unused127 = 127; + public static final int unused128 = 128; + public static final int unused129 = 129; + public static final int unused130 = 130; + public static final int unused131 = 131; + public static final int unused132 = 132; + public static final int unused133 = 133; + public static final int unused134 = 134; + public static final int unused135 = 135; + public static final int unused136 = 136; + public static final int unused137 = 137; + public static final int unused138 = 138; + public static final int unused139 = 139; + public static final int unused140 = 140; + public static final int unused141 = 141; + public static final int unused142 = 142; + public static final int unused143 = 143; + public static final int unused144 = 144; + public static final int unused145 = 145; + public static final int unused146 = 146; + public static final int unused147 = 147; + public static final int unused148 = 148; + public static final int unused149 = 149; + public static final int unused150 = 150; + public static final int unused151 = 151; + public static final int unused152 = 152; + public static final int unused153 = 153; + public static final int unused154 = 154; + public static final int unused155 = 155; + public static final int unused156 = 156; + public static final int unused157 = 157; + public static final int unused158 = 158; + public static final int unused159 = 159; + public static final int unused160 = 160; + public static final int unused161 = 161; + public static final int unused162 = 162; + public static final int unused163 = 163; + public static final int unused164 = 164; + public static final int unused165 = 165; + public static final int unused166 = 166; + public static final int unused167 = 167; + public static final int unused168 = 168; + public static final int unused169 = 169; + public static final int unused170 = 170; + public static final int unused171 = 171; + public static final int unused172 = 172; + public static final int unused173 = 173; + public static final int unused174 = 174; + public static final int unused175 = 175; + public static final int unused176 = 176; + public static final int unused177 = 177; + public static final int unused178 = 178; + public static final int unused179 = 179; + public static final int unused180 = 180; + public static final int unused181 = 181; + public static final int unused182 = 182; + public static final int unused183 = 183; + public static final int unused184 = 184; + public static final int unused185 = 185; + public static final int unused186 = 186; + public static final int unused187 = 187; + public static final int unused188 = 188; + public static final int unused189 = 189; + public static final int unused190 = 190; + public static final int unused191 = 191; + public static final int unused192 = 192; + public static final int unused193 = 193; + public static final int unused194 = 194; + public static final int unused195 = 195; + public static final int hm01 = 196; + public static final int hm02 = 197; + public static final int hm03 = 198; + public static final int hm04 = 199; + public static final int hm05 = 200; + public static final int tm01 = 201; + public static final int tm02 = 202; + public static final int tm03 = 203; + public static final int tm04 = 204; + public static final int tm05 = 205; + public static final int tm06 = 206; + public static final int tm07 = 207; + public static final int tm08 = 208; + public static final int tm09 = 209; + public static final int tm10 = 210; + public static final int tm11 = 211; + public static final int tm12 = 212; + public static final int tm13 = 213; + public static final int tm14 = 214; + public static final int tm15 = 215; + public static final int tm16 = 216; + public static final int tm17 = 217; + public static final int tm18 = 218; + public static final int tm19 = 219; + public static final int tm20 = 220; + public static final int tm21 = 221; + public static final int tm22 = 222; + public static final int tm23 = 223; + public static final int tm24 = 224; + public static final int tm25 = 225; + public static final int tm26 = 226; + public static final int tm27 = 227; + public static final int tm28 = 228; + public static final int tm29 = 229; + public static final int tm30 = 230; + public static final int tm31 = 231; + public static final int tm32 = 232; + public static final int tm33 = 233; + public static final int tm34 = 234; + public static final int tm35 = 235; + public static final int tm36 = 236; + public static final int tm37 = 237; + public static final int tm38 = 238; + public static final int tm39 = 239; + public static final int tm40 = 240; + public static final int tm41 = 241; + public static final int tm42 = 242; + public static final int tm43 = 243; + public static final int tm44 = 244; + public static final int tm45 = 245; + public static final int tm46 = 246; + public static final int tm47 = 247; + public static final int tm48 = 248; + public static final int tm49 = 249; + public static final int tm50 = 250; + public static final int tm51 = 251; + public static final int tm52 = 252; + public static final int tm53 = 253; + public static final int tm54 = 254; + public static final int tm55 = 255; +} diff --git a/src/com/pkrandom/constants/Gen2Constants.java b/src/com/pkrandom/constants/Gen2Constants.java new file mode 100644 index 0000000..d567947 --- /dev/null +++ b/src/com/pkrandom/constants/Gen2Constants.java @@ -0,0 +1,431 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Gen2Constants.java - Constants for Gold/Silver/Crystal --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.pkrandom.pokemon.ItemList; +import com.pkrandom.pokemon.Trainer; +import com.pkrandom.pokemon.Type; + +public class Gen2Constants { + + public static final int vietCrystalCheckOffset = 0x63; + + public static final byte vietCrystalCheckValue = (byte) 0xF5; + + public static final String vietCrystalROMName = "Pokemon VietCrystal"; + + public static final int pokemonCount = 251, moveCount = 251; + + public static final int baseStatsEntrySize = 0x20; + + public static final Type[] typeTable = constructTypeTable(); + + public static final int bsHPOffset = 1, bsAttackOffset = 2, bsDefenseOffset = 3, bsSpeedOffset = 4, + bsSpAtkOffset = 5, bsSpDefOffset = 6, bsPrimaryTypeOffset = 7, bsSecondaryTypeOffset = 8, + bsCatchRateOffset = 9, bsCommonHeldItemOffset = 11, bsRareHeldItemOffset = 12, bsPicDimensionsOffset = 17, + bsGrowthCurveOffset = 22, bsTMHMCompatOffset = 24, bsMTCompatOffset = 31; + + public static final String[] starterNames = new String[] { "CYNDAQUIL", "TOTODILE", "CHIKORITA" }; + + public static final int fishingGroupCount = 12, pokesPerFishingGroup = 11, fishingGroupEntryLength = 3, + timeSpecificFishingGroupCount = 11, pokesPerTSFishingGroup = 4; + + public static final int landEncounterSlots = 7, seaEncounterSlots = 3; + + public static final int oddEggPokemonCount = 14; + + public static final int tmCount = 50, hmCount = 7; + + public static final String mtMenuCancelString = "CANCEL"; + + public static final byte mtMenuInitByte = (byte) 0x80; + + public static final int maxTrainerNameLength = 17; + + public static final int fleeingSetTwoOffset = 0xE, fleeingSetThreeOffset = 0x17; + + public static final int mapGroupCount = 26, mapsInLastGroup = 11; + + public static final int noDamageSleepEffect = 1, damagePoisonEffect = 2, damageAbsorbEffect = 3, damageBurnEffect = 4, + damageFreezeEffect = 5, damageParalyzeEffect = 6, dreamEaterEffect = 8, noDamageAtkPlusOneEffect = 10, + noDamageDefPlusOneEffect = 11, noDamageSpAtkPlusOneEffect = 13, noDamageEvasionPlusOneEffect = 16, + noDamageAtkMinusOneEffect = 18, noDamageDefMinusOneEffect = 19, noDamageSpeMinusOneEffect = 20, + noDamageAccuracyMinusOneEffect = 23, noDamageEvasionMinusOneEffect = 24, flinchEffect = 31, toxicEffect = 33, + razorWindEffect = 39, bindingEffect = 42, damageRecoilEffect = 48, noDamageConfusionEffect = 49, + noDamageAtkPlusTwoEffect = 50, noDamageDefPlusTwoEffect = 51, noDamageSpePlusTwoEffect = 52, + noDamageSpDefPlusTwoEffect = 54, noDamageAtkMinusTwoEffect = 58, noDamageDefMinusTwoEffect = 59, + noDamageSpeMinusTwoEffect = 60, noDamageSpDefMinusTwoEffect = 62, noDamagePoisonEffect = 66, + noDamageParalyzeEffect = 67, damageAtkMinusOneEffect = 68, damageDefMinusOneEffect = 69, + damageSpeMinusOneEffect = 70, damageSpDefMinusOneEffect = 72, damageAccuracyMinusOneEffect = 73, + skyAttackEffect = 75, damageConfusionEffect = 76, twineedleEffect = 77, hyperBeamEffect = 80, + snoreEffect = 92, flailAndReversalEffect = 102, trappingEffect = 106, swaggerEffect = 118, + damageBurnAndThawUserEffect = 125, damageUserDefPlusOneEffect = 138, damageUserAtkPlusOneEffect = 139, + damageUserAllPlusOneEffect = 140, skullBashEffect = 145, twisterEffect = 146, futureSightEffect = 148, + stompEffect = 150, solarbeamEffect = 151, thunderEffect = 152, semiInvulnerableEffect = 155, + defenseCurlEffect = 156; + + // Taken from critical_hit_moves.asm; we could read this from the ROM, but it's easier to hardcode it. + public static final List increasedCritMoves = Arrays.asList(Moves.karateChop, Moves.razorWind, Moves.razorLeaf, + Moves.crabhammer, Moves.slash, Moves.aeroblast, Moves.crossChop); + + public static final List requiredFieldTMs = Arrays.asList(4, 20, 22, 26, 28, 34, 35, 39, + 40, 43, 44, 46); + + public static final List fieldMoves = Arrays.asList( + Moves.cut, Moves.fly, Moves.surf, Moves.strength, Moves.flash, Moves.dig, Moves.teleport, + Moves.whirlpool, Moves.waterfall, Moves.rockSmash, Moves.headbutt, Moves.sweetScent); + + public static final List earlyRequiredHMMoves = Collections.singletonList(Moves.cut); + + // ban thief because trainers are broken with it (items are not returned). + // ban transform because of Transform assumption glitch + public static final List bannedLevelupMoves = Arrays.asList(Moves.transform, Moves.thief); + + public static final List brokenMoves = Arrays.asList( + Moves.sonicBoom, Moves.dragonRage, Moves.hornDrill, Moves.fissure, Moves.guillotine); + + public static final List illegalVietCrystalMoves = Arrays.asList( + Moves.protect, Moves.rest, Moves.spikeCannon, Moves.detect); + + public static final int tmBlockOneIndex = Gen2Items.tm01, tmBlockOneSize = 4, + tmBlockTwoIndex = Gen2Items.tm05, tmBlockTwoSize = 24, + tmBlockThreeIndex = Gen2Items.tm29, tmBlockThreeSize = 22; + + public static final int priorityHitEffectIndex = 0x67, protectEffectIndex = 0x6F, endureEffectIndex = 0x74, + forceSwitchEffectIndex = 0x1C,counterEffectIndex = 0x59, mirrorCoatEffectIndex = 0x90; + + public static final String friendshipValueForEvoLocator = "FEDCDA"; + + private static Type[] constructTypeTable() { + Type[] table = new Type[256]; + table[0x00] = Type.NORMAL; + table[0x01] = Type.FIGHTING; + table[0x02] = Type.FLYING; + table[0x03] = Type.POISON; + table[0x04] = Type.GROUND; + table[0x05] = Type.ROCK; + table[0x07] = Type.BUG; + table[0x08] = Type.GHOST; + table[0x09] = Type.STEEL; + table[0x14] = Type.FIRE; + table[0x15] = Type.WATER; + table[0x16] = Type.GRASS; + table[0x17] = Type.ELECTRIC; + table[0x18] = Type.PSYCHIC; + table[0x19] = Type.ICE; + table[0x1A] = Type.DRAGON; + table[0x1B] = Type.DARK; + return table; + } + + public static byte typeToByte(Type type) { + if (type == null) { + return 0x13; // ???-type + } + switch (type) { + case NORMAL: + return 0x00; + case FIGHTING: + return 0x01; + case FLYING: + return 0x02; + case POISON: + return 0x03; + case GROUND: + return 0x04; + case ROCK: + return 0x05; + case BUG: + return 0x07; + case GHOST: + return 0x08; + case FIRE: + return 0x14; + case WATER: + return 0x15; + case GRASS: + return 0x16; + case ELECTRIC: + return 0x17; + case PSYCHIC: + return 0x18; + case ICE: + return 0x19; + case DRAGON: + return 0x1A; + case STEEL: + return 0x09; + case DARK: + return 0x1B; + default: + return 0; // normal by default + } + } + + public static ItemList allowedItems; + + public static ItemList nonBadItems; + + static { + setupAllowedItems(); + } + + private static void setupAllowedItems() { + allowedItems = new ItemList(Gen2Items.hm07); // 250-255 are junk and cancel + // Assorted key items + allowedItems.banSingles(Gen2Items.bicycle, Gen2Items.coinCase, Gen2Items.itemfinder, Gen2Items.oldRod, + Gen2Items.goodRod, Gen2Items.superRod, Gen2Items.gsBall, Gen2Items.blueCard, Gen2Items.basementKey, + Gen2Items.pass, Gen2Items.squirtBottle, Gen2Items.rainbowWing); + allowedItems.banRange(Gen2Items.redScale, 6); + allowedItems.banRange(Gen2Items.cardKey, 4); + // HMs + allowedItems.banRange(Gen2Items.hm01, 7); + // Unused items (Teru-Samas and dummy TMs) + allowedItems.banSingles(Gen2Items.terusama6, Gen2Items.terusama25, Gen2Items.terusama45, + Gen2Items.terusama50, Gen2Items.terusama56, Gen2Items.terusama90, Gen2Items.terusama100, + Gen2Items.terusama120, Gen2Items.terusama135, Gen2Items.terusama136, Gen2Items.terusama137, + Gen2Items.terusama141, Gen2Items.terusama142, Gen2Items.terusama145, Gen2Items.terusama147, + Gen2Items.terusama148, Gen2Items.terusama149, Gen2Items.terusama153, Gen2Items.terusama154, + Gen2Items.terusama155, Gen2Items.terusama162, Gen2Items.terusama171, Gen2Items.terusama176, + Gen2Items.terusama179, Gen2Items.terusama190, Gen2Items.tm04Unused, Gen2Items.tm28Unused); + // Real TMs + allowedItems.tmRange(tmBlockOneIndex, tmBlockOneSize); + allowedItems.tmRange(tmBlockTwoIndex, tmBlockTwoSize); + allowedItems.tmRange(tmBlockThreeIndex, tmBlockThreeSize); + + // non-bad items + // ban specific pokemon hold items, berries, apricorns, mail + nonBadItems = allowedItems.copy(); + nonBadItems.banSingles(Gen2Items.luckyPunch, Gen2Items.metalPowder, Gen2Items.silverLeaf, + Gen2Items.goldLeaf, Gen2Items.redApricorn, Gen2Items.bluApricorn, Gen2Items.whtApricorn, + Gen2Items.blkApricorn, Gen2Items.pnkApricorn, Gen2Items.stick, Gen2Items.thickClub, + Gen2Items.flowerMail, Gen2Items.lightBall, Gen2Items.berry, Gen2Items.brickPiece); + nonBadItems.banRange(Gen2Items.ylwApricorn, 2); + nonBadItems.banRange(Gen2Items.normalBox, 2); + nonBadItems.banRange(Gen2Items.surfMail, 9); + } + + public static void universalTrainerTags(List allTrainers) { + // Gym Leaders + tbc(allTrainers, 1, 0, "GYM1"); + tbc(allTrainers, 3, 0, "GYM2"); + tbc(allTrainers, 2, 0, "GYM3"); + tbc(allTrainers, 4, 0, "GYM4"); + tbc(allTrainers, 7, 0, "GYM5"); + tbc(allTrainers, 6, 0, "GYM6"); + tbc(allTrainers, 5, 0, "GYM7"); + tbc(allTrainers, 8, 0, "GYM8"); + tbc(allTrainers, 17, 0, "GYM9"); + tbc(allTrainers, 18, 0, "GYM10"); + tbc(allTrainers, 19, 0, "GYM11"); + tbc(allTrainers, 21, 0, "GYM12"); + tbc(allTrainers, 26, 0, "GYM13"); + tbc(allTrainers, 35, 0, "GYM14"); + tbc(allTrainers, 46, 0, "GYM15"); + tbc(allTrainers, 64, 0, "GYM16"); + + // Elite 4 & Red + tbc(allTrainers, 11, 0, "ELITE1"); + tbc(allTrainers, 15, 0, "ELITE2"); + tbc(allTrainers, 13, 0, "ELITE3"); + tbc(allTrainers, 14, 0, "ELITE4"); + tbc(allTrainers, 16, 0, "CHAMPION"); + tbc(allTrainers, 63, 0, "UBER"); + + // Silver + // Order in rom is BAYLEEF, QUILAVA, CROCONAW teams + // Starters go CYNDA, TOTO, CHIKO + // So we want 0=CROCONAW/FERALI, 1=BAYLEEF/MEGAN, 2=QUILAVA/TYPHLO + tbc(allTrainers, 9, 0, "RIVAL1-1"); + tbc(allTrainers, 9, 1, "RIVAL1-2"); + tbc(allTrainers, 9, 2, "RIVAL1-0"); + + tbc(allTrainers, 9, 3, "RIVAL2-1"); + tbc(allTrainers, 9, 4, "RIVAL2-2"); + tbc(allTrainers, 9, 5, "RIVAL2-0"); + + tbc(allTrainers, 9, 6, "RIVAL3-1"); + tbc(allTrainers, 9, 7, "RIVAL3-2"); + tbc(allTrainers, 9, 8, "RIVAL3-0"); + + tbc(allTrainers, 9, 9, "RIVAL4-1"); + tbc(allTrainers, 9, 10, "RIVAL4-2"); + tbc(allTrainers, 9, 11, "RIVAL4-0"); + + tbc(allTrainers, 9, 12, "RIVAL5-1"); + tbc(allTrainers, 9, 13, "RIVAL5-2"); + tbc(allTrainers, 9, 14, "RIVAL5-0"); + + tbc(allTrainers, 42, 0, "RIVAL6-1"); + tbc(allTrainers, 42, 1, "RIVAL6-2"); + tbc(allTrainers, 42, 2, "RIVAL6-0"); + + tbc(allTrainers, 42, 3, "RIVAL7-1"); + tbc(allTrainers, 42, 4, "RIVAL7-2"); + tbc(allTrainers, 42, 5, "RIVAL7-0"); + + // Female Rocket Executive (Ariana) + tbc(allTrainers, 55, 0, "THEMED:ARIANA"); + tbc(allTrainers, 55, 1, "THEMED:ARIANA"); + + // others (unlabeled in this game, using HGSS names) + tbc(allTrainers, 51, 2, "THEMED:PETREL"); + tbc(allTrainers, 51, 3, "THEMED:PETREL"); + + tbc(allTrainers, 51, 1, "THEMED:PROTON"); + tbc(allTrainers, 31, 0, "THEMED:PROTON"); + + // Sprout Tower + tbc(allTrainers, 56, 0, "THEMED:SPROUTTOWER"); + tbc(allTrainers, 56, 1, "THEMED:SPROUTTOWER"); + tbc(allTrainers, 56, 2, "THEMED:SPROUTTOWER"); + tbc(allTrainers, 56, 3, "THEMED:SPROUTTOWER"); + tbc(allTrainers, 56, 6, "THEMED:SPROUTTOWER"); + tbc(allTrainers, 56, 7, "THEMED:SPROUTTOWER"); + tbc(allTrainers, 56, 8, "THEMED:SPROUTTOWER"); + } + + public static void goldSilverTags(List allTrainers) { + tbc(allTrainers, 24, 0, "GYM1"); + tbc(allTrainers, 24, 1, "GYM1"); + tbc(allTrainers, 36, 4, "GYM2"); + tbc(allTrainers, 36, 5, "GYM2"); + tbc(allTrainers, 36, 6, "GYM2"); + tbc(allTrainers, 61, 0, "GYM2"); + tbc(allTrainers, 61, 3, "GYM2"); + tbc(allTrainers, 25, 0, "GYM3"); + tbc(allTrainers, 25, 1, "GYM3"); + tbc(allTrainers, 29, 0, "GYM3"); + tbc(allTrainers, 29, 1, "GYM3"); + tbc(allTrainers, 56, 4, "GYM4"); + tbc(allTrainers, 56, 5, "GYM4"); + tbc(allTrainers, 57, 0, "GYM4"); + tbc(allTrainers, 57, 1, "GYM4"); + tbc(allTrainers, 50, 1, "GYM5"); + tbc(allTrainers, 50, 3, "GYM5"); + tbc(allTrainers, 50, 4, "GYM5"); + tbc(allTrainers, 50, 6, "GYM5"); + tbc(allTrainers, 58, 0, "GYM7"); + tbc(allTrainers, 58, 1, "GYM7"); + tbc(allTrainers, 58, 2, "GYM7"); + tbc(allTrainers, 33, 0, "GYM7"); + tbc(allTrainers, 33, 1, "GYM7"); + tbc(allTrainers, 27, 2, "GYM8"); + tbc(allTrainers, 27, 4, "GYM8"); + tbc(allTrainers, 27, 3, "GYM8"); + tbc(allTrainers, 28, 2, "GYM8"); + tbc(allTrainers, 28, 3, "GYM8"); + tbc(allTrainers, 54, 17, "GYM9"); + tbc(allTrainers, 38, 20, "GYM10"); + tbc(allTrainers, 39, 17, "GYM10"); + tbc(allTrainers, 39, 18, "GYM10"); + tbc(allTrainers, 49, 2, "GYM11"); + tbc(allTrainers, 43, 1, "GYM11"); + tbc(allTrainers, 32, 2, "GYM11"); + tbc(allTrainers, 61, 4, "GYM12"); + tbc(allTrainers, 61, 5, "GYM12"); + tbc(allTrainers, 25, 8, "GYM12"); + tbc(allTrainers, 53, 18, "GYM12"); + tbc(allTrainers, 29, 13, "GYM12"); + tbc(allTrainers, 25, 2, "GYM13"); + tbc(allTrainers, 25, 5, "GYM13"); + tbc(allTrainers, 53, 4, "GYM13"); + tbc(allTrainers, 54, 4, "GYM13"); + tbc(allTrainers, 57, 5, "GYM14"); + tbc(allTrainers, 57, 6, "GYM14"); + tbc(allTrainers, 52, 1, "GYM14"); + tbc(allTrainers, 52, 10, "GYM14"); + } + + public static void crystalTags(List allTrainers) { + tbc(allTrainers, 24, 0, "GYM1"); + tbc(allTrainers, 24, 1, "GYM1"); + tbc(allTrainers, 36, 4, "GYM2"); + tbc(allTrainers, 36, 5, "GYM2"); + tbc(allTrainers, 36, 6, "GYM2"); + tbc(allTrainers, 61, 0, "GYM2"); + tbc(allTrainers, 61, 3, "GYM2"); + tbc(allTrainers, 25, 0, "GYM3"); + tbc(allTrainers, 25, 1, "GYM3"); + tbc(allTrainers, 29, 0, "GYM3"); + tbc(allTrainers, 29, 1, "GYM3"); + tbc(allTrainers, 56, 4, "GYM4"); + tbc(allTrainers, 56, 5, "GYM4"); + tbc(allTrainers, 57, 0, "GYM4"); + tbc(allTrainers, 57, 1, "GYM4"); + tbc(allTrainers, 50, 1, "GYM5"); + tbc(allTrainers, 50, 3, "GYM5"); + tbc(allTrainers, 50, 4, "GYM5"); + tbc(allTrainers, 50, 6, "GYM5"); + tbc(allTrainers, 58, 0, "GYM7"); + tbc(allTrainers, 58, 1, "GYM7"); + tbc(allTrainers, 58, 2, "GYM7"); + tbc(allTrainers, 33, 0, "GYM7"); + tbc(allTrainers, 33, 1, "GYM7"); + tbc(allTrainers, 27, 2, "GYM8"); + tbc(allTrainers, 27, 4, "GYM8"); + tbc(allTrainers, 27, 3, "GYM8"); + tbc(allTrainers, 28, 2, "GYM8"); + tbc(allTrainers, 28, 3, "GYM8"); + tbc(allTrainers, 54, 17, "GYM9"); + tbc(allTrainers, 38, 20, "GYM10"); + tbc(allTrainers, 39, 17, "GYM10"); + tbc(allTrainers, 39, 18, "GYM10"); + tbc(allTrainers, 49, 2, "GYM11"); + tbc(allTrainers, 43, 1, "GYM11"); + tbc(allTrainers, 32, 2, "GYM11"); + tbc(allTrainers, 61, 4, "GYM12"); + tbc(allTrainers, 61, 5, "GYM12"); + tbc(allTrainers, 25, 8, "GYM12"); + tbc(allTrainers, 53, 18, "GYM12"); + tbc(allTrainers, 29, 13, "GYM12"); + tbc(allTrainers, 25, 2, "GYM13"); + tbc(allTrainers, 25, 5, "GYM13"); + tbc(allTrainers, 53, 4, "GYM13"); + tbc(allTrainers, 54, 4, "GYM13"); + tbc(allTrainers, 57, 5, "GYM14"); + tbc(allTrainers, 57, 6, "GYM14"); + tbc(allTrainers, 52, 1, "GYM14"); + tbc(allTrainers, 52, 10, "GYM14"); + } + + private static void tbc(List allTrainers, int classNum, int number, String tag) { + int currnum = -1; + for (Trainer t : allTrainers) { + // adjusted to not change the above but use 0-indexing properly + if (t.trainerclass == classNum - 1) { + currnum++; + if (currnum == number) { + t.tag = tag; + return; + } + } + } + } + +} diff --git a/src/com/pkrandom/constants/Gen2Items.java b/src/com/pkrandom/constants/Gen2Items.java new file mode 100644 index 0000000..cd45ad9 --- /dev/null +++ b/src/com/pkrandom/constants/Gen2Items.java @@ -0,0 +1,285 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Gen2Items.java - defines an index number constant for every item in --*/ +/*-- the Generation 2 games. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class Gen2Items { + // https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_(Generation_II) + public static final int nothing = 0; + public static final int masterBall = 1; + public static final int ultraBall = 2; + public static final int brightPowder = 3; + public static final int greatBall = 4; + public static final int pokeBall = 5; + public static final int terusama6 = 6; + public static final int bicycle = 7; + public static final int moonStone = 8; + public static final int antidote = 9; + public static final int burnHeal = 10; + public static final int iceHeal = 11; + public static final int awakening = 12; + public static final int parlyzHeal = 13; + public static final int fullRestore = 14; + public static final int maxPotion = 15; + public static final int hyperPotion = 16; + public static final int superPotion = 17; + public static final int potion = 18; + public static final int escapeRope = 19; + public static final int repel = 20; + public static final int maxElixer = 21; + public static final int fireStone = 22; + public static final int thunderstone = 23; + public static final int waterStone = 24; + public static final int terusama25 = 25; + public static final int hpUp = 26; + public static final int protein = 27; + public static final int iron = 28; + public static final int carbos = 29; + public static final int luckyPunch = 30; + public static final int calcium = 31; + public static final int rareCandy = 32; + public static final int xAccuracy = 33; + public static final int leafStone = 34; + public static final int metalPowder = 35; + public static final int nugget = 36; + public static final int pokeDoll = 37; + public static final int fullHeal = 38; + public static final int revive = 39; + public static final int maxRevive = 40; + public static final int guardSpec = 41; + public static final int superRepel = 42; + public static final int maxRepel = 43; + public static final int direHit = 44; + public static final int terusama45 = 45; + public static final int freshWater = 46; + public static final int sodaPop = 47; + public static final int lemonade = 48; + public static final int xAttack = 49; + public static final int terusama50 = 50; + public static final int xDefend = 51; + public static final int xSpeed = 52; + public static final int xSpecial = 53; + public static final int coinCase = 54; + public static final int itemfinder = 55; + public static final int terusama56 = 56; + public static final int expShare = 57; + public static final int oldRod = 58; + public static final int goodRod = 59; + public static final int silverLeaf = 60; + public static final int superRod = 61; + public static final int ppUp = 62; + public static final int ether = 63; + public static final int maxEther = 64; + public static final int elixer = 65; + public static final int redScale = 66; + public static final int secretPotion = 67; + public static final int ssTicket = 68; + public static final int mysteryEgg = 69; + public static final int clearBell = 70; // exclusive to Crystal + public static final int silverWing = 71; + public static final int moomooMilk = 72; + public static final int quickClaw = 73; + public static final int psnCureBerry = 74; + public static final int goldLeaf = 75; + public static final int softSand = 76; + public static final int sharpBeak = 77; + public static final int przCureBerry = 78; + public static final int burntBerry = 79; + public static final int iceBerry = 80; + public static final int poisonBarb = 81; + public static final int kingsRock = 82; + public static final int bitterBerry = 83; + public static final int mintBerry = 84; + public static final int redApricorn = 85; + public static final int tinyMushroom = 86; + public static final int bigMushroom = 87; + public static final int silverPowder = 88; + public static final int bluApricorn = 89; + public static final int terusama90 = 90; + public static final int amuletCoin = 91; + public static final int ylwApricorn = 92; + public static final int grnApricorn = 93; + public static final int cleanseTag = 94; + public static final int mysticWater = 95; + public static final int twistedSpoon = 96; + public static final int whtApricorn = 97; + public static final int blackbelt = 98; + public static final int blkApricorn = 99; + public static final int terusama100 = 100; + public static final int pnkApricorn = 101; + public static final int blackGlasses = 102; + public static final int slowpokeTail = 103; + public static final int pinkBow = 104; + public static final int stick = 105; + public static final int smokeBall = 106; + public static final int neverMeltIce = 107; + public static final int magnet = 108; + public static final int miracleBerry = 109; + public static final int pearl = 110; + public static final int bigPearl = 111; + public static final int everstone = 112; + public static final int spellTag = 113; + public static final int rageCandyBar = 114; + public static final int gsBall = 115; // exclusive to Crystal + public static final int blueCard = 116; // exclusive to Crystal + public static final int miracleSeed = 117; + public static final int thickClub = 118; + public static final int focusBand = 119; + public static final int terusama120 = 120; + public static final int energyPowder = 121; + public static final int energyRoot = 122; + public static final int healPowder = 123; + public static final int revivalHerb = 124; + public static final int hardStone = 125; + public static final int luckyEgg = 126; + public static final int cardKey = 127; + public static final int machinePart = 128; + public static final int eggTicket = 129; // exclusive to Crystal + public static final int lostItem = 130; + public static final int stardust = 131; + public static final int starPiece = 132; + public static final int basementKey = 133; + public static final int pass = 134; + public static final int terusama135 = 135; + public static final int terusama136 = 136; + public static final int terusama137 = 137; + public static final int charcoal = 138; + public static final int berryJuice = 139; + public static final int scopeLens = 140; + public static final int terusama141 = 141; + public static final int terusama142 = 142; + public static final int metalCoat = 143; + public static final int dragonFang = 144; + public static final int terusama145 = 145; + public static final int leftovers = 146; + public static final int terusama147 = 147; + public static final int terusama148 = 148; + public static final int terusama149 = 149; + public static final int mysteryBerry = 150; + public static final int dragonScale = 151; + public static final int berserkGene = 152; + public static final int terusama153 = 153; + public static final int terusama154 = 154; + public static final int terusama155 = 155; + public static final int sacredAsh = 156; + public static final int heavyBall = 157; + public static final int flowerMail = 158; + public static final int levelBall = 159; + public static final int lureBall = 160; + public static final int fastBall = 161; + public static final int terusama162 = 162; + public static final int lightBall = 163; + public static final int friendBall = 164; + public static final int moonBall = 165; + public static final int loveBall = 166; + public static final int normalBox = 167; + public static final int gorgeousBox = 168; + public static final int sunStone = 169; + public static final int polkadotBow = 170; + public static final int terusama171 = 171; + public static final int upGrade = 172; + public static final int berry = 173; + public static final int goldBerry = 174; + public static final int squirtBottle = 175; + public static final int terusama176 = 176; + public static final int parkBall = 177; + public static final int rainbowWing = 178; + public static final int terusama179 = 179; + public static final int brickPiece = 180; + public static final int surfMail = 181; + public static final int litebluemail = 182; + public static final int portraitmail = 183; + public static final int lovelyMail = 184; + public static final int eonMail = 185; + public static final int morphMail = 186; + public static final int blueskyMail = 187; + public static final int musicMail = 188; + public static final int mirageMail = 189; + public static final int terusama190 = 190; + public static final int tm01 = 191; + public static final int tm02 = 192; + public static final int tm03 = 193; + public static final int tm04 = 194; + public static final int tm04Unused = 195; + public static final int tm05 = 196; + public static final int tm06 = 197; + public static final int tm07 = 198; + public static final int tm08 = 199; + public static final int tm09 = 200; + public static final int tm10 = 201; + public static final int tm11 = 202; + public static final int tm12 = 203; + public static final int tm13 = 204; + public static final int tm14 = 205; + public static final int tm15 = 206; + public static final int tm16 = 207; + public static final int tm17 = 208; + public static final int tm18 = 209; + public static final int tm19 = 210; + public static final int tm20 = 211; + public static final int tm21 = 212; + public static final int tm22 = 213; + public static final int tm23 = 214; + public static final int tm24 = 215; + public static final int tm25 = 216; + public static final int tm26 = 217; + public static final int tm27 = 218; + public static final int tm28 = 219; + public static final int tm28Unused = 220; + public static final int tm29 = 221; + public static final int tm30 = 222; + public static final int tm31 = 223; + public static final int tm32 = 224; + public static final int tm33 = 225; + public static final int tm34 = 226; + public static final int tm35 = 227; + public static final int tm36 = 228; + public static final int tm37 = 229; + public static final int tm38 = 230; + public static final int tm39 = 231; + public static final int tm40 = 232; + public static final int tm41 = 233; + public static final int tm42 = 234; + public static final int tm43 = 235; + public static final int tm44 = 236; + public static final int tm45 = 237; + public static final int tm46 = 238; + public static final int tm47 = 239; + public static final int tm48 = 240; + public static final int tm49 = 241; + public static final int tm50 = 242; + public static final int hm01 = 243; + public static final int hm02 = 244; + public static final int hm03 = 245; + public static final int hm04 = 246; + public static final int hm05 = 247; + public static final int hm06 = 248; + public static final int hm07 = 249; + public static final int hm08 = 250; + public static final int hm09 = 251; + public static final int hm10 = 252; + public static final int hm11 = 253; + public static final int hm12 = 254; + public static final int cancel = 255; +} diff --git a/src/com/pkrandom/constants/Gen3Constants.java b/src/com/pkrandom/constants/Gen3Constants.java new file mode 100644 index 0000000..2f747d4 --- /dev/null +++ b/src/com/pkrandom/constants/Gen3Constants.java @@ -0,0 +1,1319 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Gen3Constants.java - Constants for Ruby/Sapphire/FR/LG/Emerald --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.pkrandom.pokemon.ItemList; +import com.pkrandom.pokemon.Trainer; +import com.pkrandom.pokemon.Type; + +public class Gen3Constants { + + public static final int RomType_Ruby = 0; + public static final int RomType_Sapp = 1; + public static final int RomType_Em = 2; + public static final int RomType_FRLG = 3; + + public static final int size8M = 0x800000, size16M = 0x1000000, size32M = 0x2000000; + + public static final String unofficialEmeraldROMName = "YJencrypted"; + + public static final int romNameOffset = 0xA0, romCodeOffset = 0xAC, romVersionOffset = 0xBC, + headerChecksumOffset = 0xBD; + + public static final int pokemonCount = 386; + + public static final String wildPokemonPointerPrefix = "0348048009E00000FFFF0000"; + + public static final String mapBanksPointerPrefix = "80180068890B091808687047"; + + public static final String rsPokemonNamesPointerSuffix = "30B50025084CC8F7"; + + public static final String frlgMapLabelsPointerPrefix = "AC470000AE470000B0470000"; + + public static final String rseMapLabelsPointerPrefix = "C078288030BC01BC00470000"; + + public static final String pokedexOrderPointerPrefix = "0448814208D0481C0004000C05E00000"; + + public static final String rsFrontSpritesPointerPrefix = "05E0"; + + public static final String rsFrontSpritesPointerSuffix = "1068191C"; + + public static final String rsPokemonPalettesPointerPrefix = "04D90148006817E0"; + + public static final String rsPokemonPalettesPointerSuffix = "080C064A11404840"; + + private static final String runningShoesCheckPrefixRS = "0440002C1DD08620", runningShoesCheckPrefixFRLG = "02200540002D29D0", + runningShoesCheckPrefixE = "0640002E1BD08C20"; + + public static final int efrlgPokemonNamesPointer = 0x144, efrlgMoveNamesPointer = 0x148, + efrlgAbilityNamesPointer = 0x1C0, efrlgItemDataPointer = 0x1C8, efrlgMoveDataPointer = 0x1CC, + efrlgPokemonStatsPointer = 0x1BC, efrlgFrontSpritesPointer = 0x128, efrlgPokemonPalettesPointer = 0x130; + + public static final byte[] emptyPokemonSig = new byte[] { 0x32, (byte) 0x96, 0x32, (byte) 0x96, (byte) 0x96, 0x32, + 0x00, 0x00, 0x03, 0x01, (byte) 0xAA, 0x0A, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF, 0x78, 0x00, 0x00, 0x0F, + 0x0F, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00 }; + + public static final int baseStatsEntrySize = 0x1C; + + public static final int bsHPOffset = 0, bsAttackOffset = 1, bsDefenseOffset = 2, bsSpeedOffset = 3, + bsSpAtkOffset = 4, bsSpDefOffset = 5, bsPrimaryTypeOffset = 6, bsSecondaryTypeOffset = 7, + bsCatchRateOffset = 8, bsCommonHeldItemOffset = 12, bsRareHeldItemOffset = 14, bsGenderRatioOffset = 16, + bsGrowthCurveOffset = 19, bsAbility1Offset = 22, bsAbility2Offset = 23; + + public static final int textTerminator = 0xFF, textVariable = 0xFD; + + public static final byte freeSpaceByte = (byte) 0xFF; + + public static final int rseStarter2Offset = 2, rseStarter3Offset = 4, frlgStarter2Offset = 515, + frlgStarter3Offset = 461, frlgStarterRepeatOffset = 5; + + public static final int frlgBaseStarter1 = 1, frlgBaseStarter2 = 4, frlgBaseStarter3 = 7; + + public static final int frlgStarterItemsOffset = 218; + + public static final int gbaAddRxOpcode = 0x30, gbaUnconditionalJumpOpcode = 0xE0, gbaSetRxOpcode = 0x20, + gbaCmpRxOpcode = 0x28, gbaNopOpcode = 0x46C0; + + public static final int gbaR0 = 0, gbaR1 = 1, gbaR2 = 2, gbaR3 = 3, gbaR4 = 4, gbaR5 = 5, gbaR6 = 6, gbaR7 = 7; + + public static final Type[] typeTable = constructTypeTable(); + + public static final int grassSlots = 12, surfingSlots = 5, rockSmashSlots = 5, fishingSlots = 10; + + public static final int tmCount = 50, hmCount = 8; + + public static final List hmMoves = Arrays.asList( + Moves.cut, Moves.fly, Moves.surf, Moves.strength, Moves.flash, Moves.rockSmash, Moves.waterfall, Moves.dive); + + public static final int tmItemOffset = Gen3Items.tm01; + + public static final int rseItemDescCharsPerLine = 18, frlgItemDescCharsPerLine = 24; + + public static final int regularTextboxCharsPerLine = 36; + + public static final int pointerSearchRadius = 500; + + public static final int itemDataDescriptionOffset = 0x14; + + public static final String deoxysObeyCode = "CD21490088420FD0"; + + public static final int mewObeyOffsetFromDeoxysObey = 0x16; + + public static final String levelEvoKantoDexCheckCode = "972814DD"; + + public static final String stoneEvoKantoDexCheckCode = "972808D9"; + + public static final int levelEvoKantoDexJumpAmount = 0x14, stoneEvoKantoDexJumpAmount = 0x08; + + public static final String rsPokedexScriptIdentifier = "326629010803"; + + public static final String rsNatDexScriptPart1 = "31720167"; + + public static final String rsNatDexScriptPart2 = "32662901082B00801102006B02021103016B020211DABE4E020211675A6A02022A008003"; + + public static final String frlgPokedexScriptIdentifier = "292908258101"; + + public static final String frlgNatDexScript = "292908258101256F0103"; + + public static final String frlgNatDexFlagChecker = "260D809301210D800100"; + + public static final String frlgE4FlagChecker = "2B2C0800000000000000"; + + public static final String frlgOaksLabKantoDexChecker = "257D011604800000260D80D400"; + + public static final String frlgOaksLabFix = "257D011604800100"; + + public static final String frlgOakOutsideHouseCheck = "1604800000260D80D4001908800580190980068083000880830109802109803C"; + + public static final String frlgOakOutsideHouseFix = "1604800100"; + + public static final String frlgOakAideCheckPrefix = "00B5064800880028"; + + public static final String ePokedexScriptIdentifier = "3229610825F00129E40816CD40010003"; + + public static final String eNatDexScriptPart1 = "31720167"; + + public static final String eNatDexScriptPart2 = "3229610825F00129E40825F30116CD40010003"; + + public static final String friendshipValueForEvoLocator = "DB2900D8"; + + public static final String perfectOddsBranchLocator = "FE2E2FD90020"; + + public static final int unhackedMaxPokedex = 411, unhackedRealPokedex = 386, hoennPokesStart = 252; + + public static final int evolutionMethodCount = 15; + + public static final int cacophonyIndex = 76, airLockIndex = 77, highestAbilityIndex = 77; + + public static final int emMeteorFallsStevenIndex = 804; + + public static final Map> abilityVariations = setupAbilityVariations(); + + private static Map> setupAbilityVariations() { + Map> map = new HashMap<>(); + map.put(Abilities.insomnia, Arrays.asList(Abilities.insomnia, Abilities.vitalSpirit)); + map.put(Abilities.clearBody, Arrays.asList(Abilities.clearBody, Abilities.whiteSmoke)); + map.put(Abilities.hugePower, Arrays.asList(Abilities.hugePower, Abilities.purePower)); + map.put(Abilities.battleArmor, Arrays.asList(Abilities.battleArmor, Abilities.shellArmor)); + map.put(Abilities.cloudNine, Arrays.asList(Abilities.cloudNine, Gen3Constants.airLockIndex)); + + return map; + } + + public static final List uselessAbilities = Arrays.asList(Abilities.forecast, Gen3Constants.cacophonyIndex); + + public static final int frlgMapLabelsStart = 0x58; + + public static final int noDamageSleepEffect = 1, damagePoisonEffect = 2, damageAbsorbEffect = 3, damageBurnEffect = 4, + damageFreezeEffect = 5, damageParalyzeEffect = 6, dreamEaterEffect = 8, noDamageAtkPlusOneEffect = 10, + noDamageDefPlusOneEffect = 11, noDamageSpAtkPlusOneEffect = 13, noDamageEvasionPlusOneEffect = 16, + noDamageAtkMinusOneEffect = 18, noDamageDefMinusOneEffect = 19, noDamageSpeMinusOneEffect = 20, + noDamageAccuracyMinusOneEffect = 23, noDamageEvasionMinusOneEffect = 24, flinchEffect = 31, toxicEffect = 33, + razorWindEffect = 39, bindingEffect = 42, increasedCritEffect = 43, damageRecoil25PercentEffect = 48, + noDamageConfusionEffect = 49, noDamageAtkPlusTwoEffect = 50, noDamageDefPlusTwoEffect = 51, + noDamageSpePlusTwoEffect = 52, noDamageSpAtkPlusTwoEffect = 53, noDamageSpDefPlusTwoEffect = 54, + noDamageAtkMinusTwoEffect = 58, noDamageDefMinusTwoEffect = 59, noDamageSpeMinusTwoEffect = 60, + noDamageSpDefMinusTwoEffect = 62, noDamagePoisonEffect = 66, noDamageParalyzeEffect = 67, + damageAtkMinusOneEffect = 68, damageDefMinusOneEffect = 69, damageSpeMinusOneEffect = 70, + damageSpAtkMinusOneEffect = 71, damageSpDefMinusOneEffect = 72, damageAccuracyMinusOneEffect = 73, + skyAttackEffect = 75, damageConfusionEffect = 76, twineedleEffect = 77, rechargeEffect = 80, + snoreEffect = 92, trappingEffect = 106, minimizeEffect = 108, swaggerEffect = 118, + damageBurnAndThawUserEffect = 125, damageUserDefPlusOneEffect = 138, damageUserAtkPlusOneEffect = 139, + damageUserAllPlusOneEffect = 140, skullBashEffect = 145, twisterEffect = 146, + futureSightAndDoomDesireEffect = 148, flinchWithMinimizeBonusEffect = 150, solarbeamEffect = 151, + thunderEffect = 152, semiInvulnerableEffect = 155, defenseCurlEffect = 156, fakeOutEffect = 158, + spitUpEffect = 161, flatterEffect = 166, noDamageBurnEffect = 167, chargeEffect = 174, + damageUserAtkAndDefMinusOneEffect = 182, damageRecoil33PercentEffect = 198, teeterDanceEffect = 199, + blazeKickEffect = 200, poisonFangEffect = 202, damageUserSpAtkMinusTwoEffect = 204, + noDamageAtkAndDefMinusOneEffect = 205, noDamageDefAndSpDefPlusOneEffect = 206, + noDamageAtkAndDefPlusOneEffect = 208, poisonTailEffect = 209, noDamageSpAtkAndSpDefPlusOneEffect = 211, + noDamageAtkAndSpePlusOneEffect = 212; + + public static final List soundMoves = Arrays.asList(Moves.growl, Moves.roar, Moves.sing, Moves.supersonic, + Moves.screech, Moves.snore, Moves.uproar, Moves.metalSound, Moves.grassWhistle, Moves.hyperVoice, + Moves.perishSong, Moves.healBell); + + public static final List rsRequiredFieldTMs = Arrays.asList(1, 2, 6, 7, 11, 18, 22, 23, + 26, 30, 37, 48); + + public static final List eRequiredFieldTMs = Arrays.asList(2, 6, 7, 11, 18, 22, 23, 30, + 37, 48); + + public static final List frlgRequiredFieldTMs = Arrays.asList(1, 2, 7, 8, 9, 11, 12, 14, + 17, 18, 21, 22, 25, 32, 36, 37, 40, 41, 44, 46, 47, 48, 49, 50); + + public static final List rseFieldMoves = Arrays.asList( + Moves.cut, Moves.fly, Moves.surf, Moves.strength, Moves.flash, Moves.dig, Moves.teleport, + Moves.waterfall, Moves.rockSmash, Moves.sweetScent, Moves.dive, Moves.secretPower); + + public static final List frlgFieldMoves = Arrays.asList( + Moves.cut, Moves.fly, Moves.surf, Moves.strength, Moves.flash, Moves.dig, Moves.teleport, + Moves.waterfall, Moves.rockSmash, Moves.sweetScent); + + public static final List rseEarlyRequiredHMMoves = Collections.singletonList(Moves.rockSmash); + + public static final List frlgEarlyRequiredHMMoves = Collections.singletonList(Moves.cut); + + private static List rsShopNames = Arrays.asList( + "Slateport Vitamins", + "Slateport TMs", + "Oldale Poké Mart (Before Pokédex)", + "Oldale Poké Mart (After Pokédex)", + "Lavaridge Herbs", + "Lavaridge Poké Mart", + "Fallarbor Poké Mart", + "Verdanturf Poké Mart", + "Petalburg Poké Mart (Before 4 Badges)", + "Petalburg Poké Mart (After 4 Badges)", + "Slateport Poké Mart", + "Mauville Poké Mart", + "Rustboro Poké Mart (Before Delivering Devon Goods)", + "Rustboro Poké Mart (After Delivering Devon Goods)", + "Fortree Poké Mart", + "Lilycove Department Store 2F Left", + "Lilycove Department Store 2F Right", + "Lilycove Department Store 3F Left", + "Lilycove Department Store 3F Right", + "Lilycove Department Store 4F Left (TMs)", + "Lilycove Department Store 4F Right (TMs)", + "Mossdeep Poké Mart", + "Sootopolis Poké Mart", + "Pokémon League Poké Mart" + ); + + private static List frlgShopNames = Arrays.asList( + "Trainer Tower Poké Mart", + "Two Island Market Stall (Initial)", + "Two Island Market Stall (After Saving Lostelle)", + "Two Island Market Stall (After Hall of Fame)", + "Two Island Market Stall (After Ruby/Sapphire Quest)", + "Viridian Poké Mart", + "Pewter Poké Mart", + "Cerulean Poké Mart", + "Lavender Poké Mart", + "Vermillion Poké Mart", + "Celadon Department 2F South", + "Celadon Department 2F North (TMs)", + "Celadon Department 4F", + "Celadon Department 5F South", + "Celadon Department 5F North", + "Fuchsia Poké Mart", + "Cinnabar Poké Mart", + "Indigo Plateau Poké Mart", + "Saffron Poké Mart", + "Seven Island Poké Mart", + "Three Island Poké Mart", + "Four Island Poké Mart", + "Six Island Poké Mart" + ); + + private static List emShopNames = Arrays.asList( + "Slateport Vitamins", + "Slateport TMs", + "Oldale Poké Mart (Before Pokédex)", + "Oldale Poké Mart (After Pokédex)", + "Lavaridge Herbs", + "Lavaridge Poké Mart", + "Fallarbor Poké Mart", + "Verdanturf Poké Mart", + "Petalburg Poké Mart (Before 4 Badges)", + "Petalburg Poké Mart (After 4 Badges)", + "Slateport Poké Mart", + "Mauville Poké Mart", + "Rustboro Poké Mart (Before Delivering Devon Goods)", + "Rustboro Poké Mart (After Delivering Devon Goods)", + "Fortree Poké Mart", + "Lilycove Department Store 2F Left", + "Lilycove Department Store 2F Right", + "Lilycove Department Store 3F Left", + "Lilycove Department Store 3F Right", + "Lilycove Department Store 4F Left (TMs)", + "Lilycove Department Store 4F Right (TMs)", + "Mossdeep Poké Mart", + "Sootopolis Poké Mart", + "Pokémon League Poké Mart", + "Battle Frontier Poké Mart", + "Trainer Hill Poké Mart (Before Hall of Fame)", + "Trainer Hill Poké Mart (After Hall of Fame)" + ); + + public static List getShopNames(int romType) { + if (romType == RomType_Ruby || romType == RomType_Sapp) { + return rsShopNames; + } else if (romType == RomType_FRLG) { + return frlgShopNames; + } else if (romType == RomType_Em) { + return emShopNames; + } + return null; + } + + public static final List evolutionItems = Arrays.asList(Gen3Items.sunStone, Gen3Items.moonStone, + Gen3Items.fireStone, Gen3Items.thunderstone, Gen3Items.waterStone, Gen3Items.leafStone); + + public static final List xItems = Arrays.asList(Gen3Items.guardSpec, Gen3Items.direHit, Gen3Items.xAttack, + Gen3Items.xDefend, Gen3Items.xSpeed, Gen3Items.xAccuracy, Gen3Items.xSpecial); + + public static final List consumableHeldItems = Collections.unmodifiableList(Arrays.asList( + Gen3Items.cheriBerry, Gen3Items.chestoBerry, Gen3Items.pechaBerry, Gen3Items.rawstBerry, + Gen3Items.rawstBerry, Gen3Items.leppaBerry, Gen3Items.oranBerry, Gen3Items.persimBerry, Gen3Items.lumBerry, + Gen3Items.sitrusBerry, Gen3Items.figyBerry, Gen3Items.wikiBerry, Gen3Items.magoBerry, Gen3Items.aguavBerry, + Gen3Items.iapapaBerry, Gen3Items.liechiBerry, Gen3Items.ganlonBerry, Gen3Items.salacBerry, + Gen3Items.petayaBerry, Gen3Items.apicotBerry, Gen3Items.lansatBerry, Gen3Items.starfBerry, + Gen3Items.berryJuice, Gen3Items.whiteHerb, Gen3Items.mentalHerb)); + + public static final List allHeldItems = setupAllHeldItems(); + + private static List setupAllHeldItems() { + List list = new ArrayList<>(); + list.addAll(Arrays.asList(Gen3Items.brightPowder, Gen3Items.quickClaw, Gen3Items.choiceBand, + Gen3Items.kingsRock, Gen3Items.silverPowder, Gen3Items.focusBand, Gen3Items.scopeLens, + Gen3Items.metalCoat, Gen3Items.leftovers, Gen3Items.softSand, Gen3Items.hardStone, + Gen3Items.miracleSeed, Gen3Items.blackGlasses, Gen3Items.blackBelt, Gen3Items.magnet, + Gen3Items.mysticWater, Gen3Items.sharpBeak, Gen3Items.poisonBarb, Gen3Items.neverMeltIce, + Gen3Items.spellTag, Gen3Items.twistedSpoon, Gen3Items.charcoal, Gen3Items.dragonFang, + Gen3Items.silkScarf, Gen3Items.shellBell, Gen3Items.seaIncense, Gen3Items.laxIncense)); + list.addAll(consumableHeldItems); + return Collections.unmodifiableList(list); + } + + public static final List generalPurposeConsumableItems = Collections.unmodifiableList(Arrays.asList( + Gen3Items.cheriBerry, Gen3Items.chestoBerry, Gen3Items.pechaBerry, Gen3Items.rawstBerry, + Gen3Items.aspearBerry, Gen3Items.leppaBerry, Gen3Items.oranBerry, Gen3Items.persimBerry, Gen3Items.lumBerry, + Gen3Items.sitrusBerry, Gen3Items.ganlonBerry, Gen3Items.salacBerry, + // An NPC pokemon's nature is generated randomly with IVs during gameplay. Therefore, we do not include + // the flavor berries because, prior to Gen 7, they aren't worth the risk. + Gen3Items.apicotBerry, Gen3Items.lansatBerry, Gen3Items.starfBerry, Gen3Items.berryJuice, + Gen3Items.whiteHerb, Gen3Items.mentalHerb + )); + + public static final List generalPurposeItems = Collections.unmodifiableList(Arrays.asList( + Gen3Items.brightPowder, Gen3Items.quickClaw, Gen3Items.kingsRock, Gen3Items.focusBand, Gen3Items.scopeLens, + Gen3Items.leftovers, Gen3Items.shellBell, Gen3Items.laxIncense + )); + + public static final Map> typeBoostingItems = initializeTypeBoostingItems(); + + private static Map> initializeTypeBoostingItems() { + Map> map = new HashMap<>(); + map.put(Type.BUG, Arrays.asList(Gen3Items.silverPowder)); + map.put(Type.DARK, Arrays.asList(Gen3Items.blackGlasses)); + map.put(Type.DRAGON, Arrays.asList(Gen3Items.dragonFang)); + map.put(Type.ELECTRIC, Arrays.asList(Gen3Items.magnet)); + map.put(Type.FIGHTING, Arrays.asList(Gen3Items.blackBelt)); + map.put(Type.FIRE, Arrays.asList(Gen3Items.charcoal)); + map.put(Type.FLYING, Arrays.asList(Gen3Items.sharpBeak)); + map.put(Type.GHOST, Arrays.asList(Gen3Items.spellTag)); + map.put(Type.GRASS, Arrays.asList(Gen3Items.miracleSeed)); + map.put(Type.GROUND, Arrays.asList(Gen3Items.softSand)); + map.put(Type.ICE, Arrays.asList(Gen3Items.neverMeltIce)); + map.put(Type.NORMAL, Arrays.asList(Gen3Items.silkScarf)); + map.put(Type.POISON, Arrays.asList(Gen3Items.poisonBarb)); + map.put(Type.PSYCHIC, Arrays.asList(Gen3Items.twistedSpoon)); + map.put(Type.ROCK, Arrays.asList(Gen3Items.hardStone)); + map.put(Type.STEEL, Arrays.asList(Gen3Items.metalCoat)); + map.put(Type.WATER, Arrays.asList(Gen3Items.mysticWater, Gen3Items.seaIncense)); + map.put(null, Collections.emptyList()); // ??? type + return Collections.unmodifiableMap(map); + } + + public static final Map> speciesBoostingItems = initializeSpeciesBoostingItems(); + + private static Map> initializeSpeciesBoostingItems() { + Map> map = new HashMap<>(); + map.put(Species.latias, Arrays.asList(Gen3Items.soulDew)); + map.put(Species.latios, Arrays.asList(Gen3Items.soulDew)); + map.put(Species.clamperl, Arrays.asList(Gen3Items.deepSeaTooth, Gen3Items.deepSeaScale)); + map.put(Species.pikachu, Arrays.asList(Gen3Items.lightBall)); + map.put(Species.chansey, Arrays.asList(Gen3Items.luckyPunch)); + map.put(Species.ditto, Arrays.asList(Gen3Items.metalPowder)); + map.put(Species.cubone, Arrays.asList(Gen3Items.thickClub)); + map.put(Species.marowak, Arrays.asList(Gen3Items.thickClub)); + map.put(Species.farfetchd, Arrays.asList(Gen3Items.stick)); + return Collections.unmodifiableMap(map); + } + + private static Type[] constructTypeTable() { + Type[] table = new Type[256]; + table[0x00] = Type.NORMAL; + table[0x01] = Type.FIGHTING; + table[0x02] = Type.FLYING; + table[0x03] = Type.POISON; + table[0x04] = Type.GROUND; + table[0x05] = Type.ROCK; + table[0x06] = Type.BUG; + table[0x07] = Type.GHOST; + table[0x08] = Type.STEEL; + table[0x0A] = Type.FIRE; + table[0x0B] = Type.WATER; + table[0x0C] = Type.GRASS; + table[0x0D] = Type.ELECTRIC; + table[0x0E] = Type.PSYCHIC; + table[0x0F] = Type.ICE; + table[0x10] = Type.DRAGON; + table[0x11] = Type.DARK; + return table; + } + + public static byte typeToByte(Type type) { + if (type == null) { + return 0x09; // ???-type + } + switch (type) { + case NORMAL: + return 0x00; + case FIGHTING: + return 0x01; + case FLYING: + return 0x02; + case POISON: + return 0x03; + case GROUND: + return 0x04; + case ROCK: + return 0x05; + case BUG: + return 0x06; + case GHOST: + return 0x07; + case FIRE: + return 0x0A; + case WATER: + return 0x0B; + case GRASS: + return 0x0C; + case ELECTRIC: + return 0x0D; + case PSYCHIC: + return 0x0E; + case ICE: + return 0x0F; + case DRAGON: + return 0x10; + case STEEL: + return 0x08; + case DARK: + return 0x11; + default: + return 0; // normal by default + } + } + + public static ItemList allowedItems, nonBadItemsRSE, nonBadItemsFRLG; + public static List regularShopItems, opShopItems; + + public static String getRunningShoesCheckPrefix(int romType) { + if (romType == Gen3Constants.RomType_Ruby || romType == Gen3Constants.RomType_Sapp) { + return runningShoesCheckPrefixRS; + } else if (romType == Gen3Constants.RomType_FRLG) { + return runningShoesCheckPrefixFRLG; + } else { + return runningShoesCheckPrefixE; + } + } + + static { + setupAllowedItems(); + } + + private static void setupAllowedItems() { + allowedItems = new ItemList(Gen3Items.oldSeaMap); + // Key items (+1 unknown item) + allowedItems.banRange(Gen3Items.machBike, 30); + allowedItems.banRange(Gen3Items.oaksParcel, 28); + // Unknown blank items + allowedItems.banRange(Gen3Items.unknown52, 11); + allowedItems.banRange(Gen3Items.unknown87, 6); + allowedItems.banRange(Gen3Items.unknown99, 4); + allowedItems.banRange(Gen3Items.unknown112, 9); + allowedItems.banRange(Gen3Items.unknown176, 3); + allowedItems.banRange(Gen3Items.unknown226, 28); + allowedItems.banRange(Gen3Items.unknown347, 2); + allowedItems.banSingles(Gen3Items.unknown72, Gen3Items.unknown82, Gen3Items.unknown105, Gen3Items.unknown267); + // HMs + allowedItems.banRange(Gen3Items.hm01, 8); + // TMs + allowedItems.tmRange(Gen3Items.tm01, 50); + + // non-bad items + // ban specific pokemon hold items, berries, apricorns, mail + nonBadItemsRSE = allowedItems.copy(); + nonBadItemsRSE.banSingles(Gen3Items.lightBall, Gen3Items.oranBerry, Gen3Items.soulDew); + nonBadItemsRSE.banRange(Gen3Items.orangeMail, 12); // mail + nonBadItemsRSE.banRange(Gen3Items.figyBerry, 33); // berries + nonBadItemsRSE.banRange(Gen3Items.luckyPunch, 4); // pokemon specific + nonBadItemsRSE.banRange(Gen3Items.redScarf, 5); // contest scarves + + // FRLG-exclusive bad items + // Ban Shoal items and Shards, since they don't do anything + nonBadItemsFRLG = nonBadItemsRSE.copy(); + nonBadItemsFRLG.banRange(Gen3Items.shoalSalt, 6); + + regularShopItems = new ArrayList<>(); + + regularShopItems.addAll(IntStream.rangeClosed(Gen3Items.ultraBall,Gen3Items.pokeBall).boxed().collect(Collectors.toList())); + regularShopItems.addAll(IntStream.rangeClosed(Gen3Items.potion,Gen3Items.revive).boxed().collect(Collectors.toList())); + regularShopItems.addAll(IntStream.rangeClosed(Gen3Items.superRepel,Gen3Items.repel).boxed().collect(Collectors.toList())); + + opShopItems = new ArrayList<>(); + + // "Money items" etc + opShopItems.add(Gen3Items.rareCandy); + opShopItems.addAll(IntStream.rangeClosed(Gen3Items.tinyMushroom,Gen3Items.bigMushroom).boxed().collect(Collectors.toList())); + opShopItems.addAll(IntStream.rangeClosed(Gen3Items.pearl,Gen3Items.nugget).boxed().collect(Collectors.toList())); + opShopItems.add(Gen3Items.luckyEgg); + } + + public static ItemList getNonBadItems(int romType) { + if (romType == Gen3Constants.RomType_FRLG) { + return nonBadItemsFRLG; + } else { + return nonBadItemsRSE; + } + } + + public static void trainerTagsRS(List trs, int romType) { + // Gym Trainers + tag(trs, "GYM1", 0x140, 0x141); + tag(trs, "GYM2", 0x1AA, 0x1A9, 0xB3); + tag(trs, "GYM3", 0xBF, 0x143, 0xC2, 0x289); + tag(trs, "GYM4", 0xC9, 0x288, 0xCB, 0x28A, 0xCD); + tag(trs, "GYM5", 0x47, 0x59, 0x49, 0x5A, 0x48, 0x5B, 0x4A); + tag(trs, "GYM6", 0x191, 0x28F, 0x28E, 0x194); + tag(trs, "GYM7", 0xE9, 0xEA, 0xEB, 0xF4, 0xF5, 0xF6); + tag(trs, "GYM8", 0x82, 0x266, 0x83, 0x12D, 0x81, 0x74, 0x80, 0x265); + + // Gym Leaders + tag(trs, 0x109, "GYM1-LEADER"); + tag(trs, 0x10A, "GYM2-LEADER"); + tag(trs, 0x10B, "GYM3-LEADER"); + tag(trs, 0x10C, "GYM4-LEADER"); + tag(trs, 0x10D, "GYM5-LEADER"); + tag(trs, 0x10E, "GYM6-LEADER"); + tag(trs, 0x10F, "GYM7-LEADER"); + tag(trs, 0x110, "GYM8-LEADER"); + // Elite 4 + tag(trs, 0x105, "ELITE1"); + tag(trs, 0x106, "ELITE2"); + tag(trs, 0x107, "ELITE3"); + tag(trs, 0x108, "ELITE4"); + tag(trs, 0x14F, "CHAMPION"); + // Brendan + tag(trs, 0x208, "RIVAL1-2"); + tag(trs, 0x20B, "RIVAL1-0"); + tag(trs, 0x20E, "RIVAL1-1"); + + tag(trs, 0x209, "RIVAL2-2"); + tag(trs, 0x20C, "RIVAL2-0"); + tag(trs, 0x20F, "RIVAL2-1"); + + tag(trs, 0x20A, "RIVAL3-2"); + tag(trs, 0x20D, "RIVAL3-0"); + tag(trs, 0x210, "RIVAL3-1"); + + tag(trs, 0x295, "RIVAL4-2"); + tag(trs, 0x296, "RIVAL4-0"); + tag(trs, 0x297, "RIVAL4-1"); + + // May + tag(trs, 0x211, "RIVAL1-2"); + tag(trs, 0x214, "RIVAL1-0"); + tag(trs, 0x217, "RIVAL1-1"); + + tag(trs, 0x212, "RIVAL2-2"); + tag(trs, 0x215, "RIVAL2-0"); + tag(trs, 0x218, "RIVAL2-1"); + + tag(trs, 0x213, "RIVAL3-2"); + tag(trs, 0x216, "RIVAL3-0"); + tag(trs, 0x219, "RIVAL3-1"); + + tag(trs, 0x298, "RIVAL4-2"); + tag(trs, 0x299, "RIVAL4-0"); + tag(trs, 0x29A, "RIVAL4-1"); + + // Wally + tag(trs, "THEMED:WALLY-STRONG", 0x207, 0x290, 0x291, 0x292, 0x293, 0x294); + + if (romType == RomType_Ruby) { + tag(trs, "THEMED:MAXIE-LEADER", 0x259, 0x25A); + tag(trs, "THEMED:COURTNEY-STRONG", 0x257, 0x258); + tag(trs, "THEMED:TABITHA-STRONG", 0x254, 0x255); + } else { + tag(trs, "THEMED:ARCHIE-LEADER", 0x23, 0x22); + tag(trs, "THEMED:MATT-STRONG", 0x1E, 0x1F); + tag(trs, "THEMED:SHELLY-STRONG", 0x20, 0x21); + } + + } + + public static void trainerTagsE(List trs) { + // Gym Trainers + tag(trs, "GYM1", 0x140, 0x141, 0x23B); + tag(trs, "GYM2", 0x1AA, 0x1A9, 0xB3, 0x23C, 0x23D, 0x23E); + tag(trs, "GYM3", 0xBF, 0x143, 0xC2, 0x289, 0x322); + tag(trs, "GYM4", 0x288, 0xC9, 0xCB, 0x28A, 0xCA, 0xCC, 0x1F5, 0xCD); + tag(trs, "GYM5", 0x47, 0x59, 0x49, 0x5A, 0x48, 0x5B, 0x4A); + tag(trs, "GYM6", 0x192, 0x28F, 0x191, 0x28E, 0x194, 0x323); + tag(trs, "GYM7", 0xE9, 0xEA, 0xEB, 0xF4, 0xF5, 0xF6, 0x24F, 0x248, 0x247, 0x249, 0x246, 0x23F); + tag(trs, "GYM8", 0x265, 0x80, 0x1F6, 0x73, 0x81, 0x76, 0x82, 0x12D, 0x83, 0x266); + + // Gym Leaders + Emerald Rematches! + tag(trs, "GYM1-LEADER", 0x109, 0x302, 0x303, 0x304, 0x305); + tag(trs, "GYM2-LEADER", 0x10A, 0x306, 0x307, 0x308, 0x309); + tag(trs, "GYM3-LEADER", 0x10B, 0x30A, 0x30B, 0x30C, 0x30D); + tag(trs, "GYM4-LEADER", 0x10C, 0x30E, 0x30F, 0x310, 0x311); + tag(trs, "GYM5-LEADER", 0x10D, 0x312, 0x313, 0x314, 0x315); + tag(trs, "GYM6-LEADER", 0x10E, 0x316, 0x317, 0x318, 0x319); + tag(trs, "GYM7-LEADER", 0x10F, 0x31A, 0x31B, 0x31C, 0x31D); + tag(trs, "GYM8-LEADER", 0x110, 0x31E, 0x31F, 0x320, 0x321); + + // Elite 4 + tag(trs, 0x105, "ELITE1"); + tag(trs, 0x106, "ELITE2"); + tag(trs, 0x107, "ELITE3"); + tag(trs, 0x108, "ELITE4"); + tag(trs, 0x14F, "CHAMPION"); + + // Brendan + tag(trs, 0x208, "RIVAL1-2"); + tag(trs, 0x20B, "RIVAL1-0"); + tag(trs, 0x20E, "RIVAL1-1"); + + tag(trs, 0x251, "RIVAL2-2"); + tag(trs, 0x250, "RIVAL2-0"); + tag(trs, 0x257, "RIVAL2-1"); + + tag(trs, 0x209, "RIVAL3-2"); + tag(trs, 0x20C, "RIVAL3-0"); + tag(trs, 0x20F, "RIVAL3-1"); + + tag(trs, 0x20A, "RIVAL4-2"); + tag(trs, 0x20D, "RIVAL4-0"); + tag(trs, 0x210, "RIVAL4-1"); + + tag(trs, 0x295, "RIVAL5-2"); + tag(trs, 0x296, "RIVAL5-0"); + tag(trs, 0x297, "RIVAL5-1"); + + // May + tag(trs, 0x211, "RIVAL1-2"); + tag(trs, 0x214, "RIVAL1-0"); + tag(trs, 0x217, "RIVAL1-1"); + + tag(trs, 0x258, "RIVAL2-2"); + tag(trs, 0x300, "RIVAL2-0"); + tag(trs, 0x301, "RIVAL2-1"); + + tag(trs, 0x212, "RIVAL3-2"); + tag(trs, 0x215, "RIVAL3-0"); + tag(trs, 0x218, "RIVAL3-1"); + + tag(trs, 0x213, "RIVAL4-2"); + tag(trs, 0x216, "RIVAL4-0"); + tag(trs, 0x219, "RIVAL4-1"); + + tag(trs, 0x298, "RIVAL5-2"); + tag(trs, 0x299, "RIVAL5-0"); + tag(trs, 0x29A, "RIVAL5-1"); + + // Themed + tag(trs, "THEMED:MAXIE-LEADER", 0x259, 0x25A, 0x2DE); + tag(trs, "THEMED:TABITHA-STRONG", 0x202, 0x255, 0x2DC); + tag(trs, "THEMED:ARCHIE-LEADER", 0x22); + tag(trs, "THEMED:MATT-STRONG", 0x1E); + tag(trs, "THEMED:SHELLY-STRONG", 0x20, 0x21); + tag(trs, "THEMED:WALLY-STRONG", 0x207, 0x290, 0x291, 0x292, 0x293, 0x294); + + // Steven + tag(trs, emMeteorFallsStevenIndex, "UBER"); + + } + + public static void trainerTagsFRLG(List trs) { + + // Gym Trainers + tag(trs, "GYM1", 0x8E); + tag(trs, "GYM2", 0xEA, 0x96); + tag(trs, "GYM3", 0xDC, 0x8D, 0x1A7); + tag(trs, "GYM4", 0x10A, 0x84, 0x109, 0xA0, 0x192, 0x10B, 0x85); + tag(trs, "GYM5", 0x125, 0x124, 0x120, 0x127, 0x126, 0x121); + tag(trs, "GYM6", 0x11A, 0x119, 0x1CF, 0x11B, 0x1CE, 0x1D0, 0x118); + tag(trs, "GYM7", 0xD5, 0xB1, 0xB2, 0xD6, 0xB3, 0xD7, 0xB4); + tag(trs, "GYM8", 0x129, 0x143, 0x188, 0x190, 0x142, 0x128, 0x191, 0x144); + + // Gym Leaders + tag(trs, 0x19E, "GYM1-LEADER"); + tag(trs, 0x19F, "GYM2-LEADER"); + tag(trs, 0x1A0, "GYM3-LEADER"); + tag(trs, 0x1A1, "GYM4-LEADER"); + tag(trs, 0x1A2, "GYM5-LEADER"); + tag(trs, 0x1A4, "GYM6-LEADER"); + tag(trs, 0x1A3, "GYM7-LEADER"); + tag(trs, 0x15E, "GYM8-LEADER"); + + // Giovanni + tag(trs, 0x15C, "GIO1-LEADER"); + tag(trs, 0x15D, "GIO2-LEADER"); + + // E4 Round 1 + tag(trs, 0x19A, "ELITE1-1"); + tag(trs, 0x19B, "ELITE2-1"); + tag(trs, 0x19C, "ELITE3-1"); + tag(trs, 0x19D, "ELITE4-1"); + + // E4 Round 2 + tag(trs, 0x2DF, "ELITE1-2"); + tag(trs, 0x2E0, "ELITE2-2"); + tag(trs, 0x2E1, "ELITE3-2"); + tag(trs, 0x2E2, "ELITE4-2"); + + // Rival Battles + + // Initial Rival + tag(trs, 0x148, "RIVAL1-0"); + tag(trs, 0x146, "RIVAL1-1"); + tag(trs, 0x147, "RIVAL1-2"); + + // Route 22 (weak) + tag(trs, 0x14B, "RIVAL2-0"); + tag(trs, 0x149, "RIVAL2-1"); + tag(trs, 0x14A, "RIVAL2-2"); + + // Cerulean + tag(trs, 0x14E, "RIVAL3-0"); + tag(trs, 0x14C, "RIVAL3-1"); + tag(trs, 0x14D, "RIVAL3-2"); + + // SS Anne + tag(trs, 0x1AC, "RIVAL4-0"); + tag(trs, 0x1AA, "RIVAL4-1"); + tag(trs, 0x1AB, "RIVAL4-2"); + + // Pokemon Tower + tag(trs, 0x1AF, "RIVAL5-0"); + tag(trs, 0x1AD, "RIVAL5-1"); + tag(trs, 0x1AE, "RIVAL5-2"); + + // Silph Co + tag(trs, 0x1B2, "RIVAL6-0"); + tag(trs, 0x1B0, "RIVAL6-1"); + tag(trs, 0x1B1, "RIVAL6-2"); + + // Route 22 (strong) + tag(trs, 0x1B5, "RIVAL7-0"); + tag(trs, 0x1B3, "RIVAL7-1"); + tag(trs, 0x1B4, "RIVAL7-2"); + + // E4 Round 1 + tag(trs, 0x1B8, "RIVAL8-0"); + tag(trs, 0x1B6, "RIVAL8-1"); + tag(trs, 0x1B7, "RIVAL8-2"); + + // E4 Round 2 + tag(trs, 0x2E5, "RIVAL9-0"); + tag(trs, 0x2E3, "RIVAL9-1"); + tag(trs, 0x2E4, "RIVAL9-2"); + + } + + private static void tag(List trainers, int trainerNum, String tag) { + trainers.get(trainerNum - 1).tag = tag; + } + + private static void tag(List allTrainers, String tag, int... numbers) { + for (int num : numbers) { + allTrainers.get(num - 1).tag = tag; + } + } + + public static void setMultiBattleStatusEm(List trs) { + // 25 + 569: Double Battle with Team Aqua Grunts on Mt. Pyre + // 105 + 237: Double Battle with Hex Maniac Patricia and Psychic Joshua + // 397 + 508: Double Battle with Dragon Tamer Aaron and Cooltrainer Marley + // 404 + 654: Double Battle with Bird Keeper Edwardo and Camper Flint + // 504 + 505: Double Battle with Ninja Boy Jonas and Parasol Lady Kayley + // 514 + 734: Double Battle with Tabitha and Maxie in Mossdeep Space Center + // 572 + 573: Double Battle with Sailor Brenden and Battle Girl Lilith + // 721 + 730: Double Battle with Team Magma Grunts in Team Magma Hideout + // 848 + 850: Double Battle with Psychic Mariela and Gentleman Everett + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.ALWAYS, 25, 105, 237, 397, 404, 504, 505, 508, 514, + 569, 572, 573, 654, 721, 730, 734, 848, 850 + ); + + // 1 + 124: Potential Double Battle with Hiker Sawyer and Beauty Melissa + // 3 + 192: Potential Double Battle with Team Aqua Grunts in Team Aqua Hideout + // 8 + 14: Potential Double Battle with Team Aqua Grunts in Seafloor Cavern + // 9 + 236 + 247: Potential Double Battle with Pokemon Breeder Gabrielle, Psychic William, and Psychic Kayla + // 11 + 767: Potential Double Battle with Cooltrainer Marcel and Cooltrainer Cristin + // 12 + 195: Potential Double Battle with Bird Keeper Alberto and Guitarist Fernando + // 13 + 106: Potential Double Battle with Collector Ed and Hex Maniac Kindra + // 15 + 450: Potential Double Battle with Swimmer Declan and Swimmer Grace + // 18 + 596: Potential Double Battle with Team Aqua Grunts in Weather Institute + // 28 + 193: Potential Double Battle with Team Aqua Grunts in Team Aqua Hideout + // 29 + 249: Potential Double Battle with Expert Fredrick and Psychic Jacki + // 31 + 35 + 145: Potential Double Battles with Black Belt Zander, Hex Maniac Leah, and PokéManiac Mark + // 33 + 567: Potential Double Battle with Shelly and Team Aqua Grunt in Seafloor Cavern + // 37 + 715: Potential Double Battle with Aroma Lady Rose and Youngster Deandre + // 38 + 417: Potential Double Battle with Cooltrainer Felix and Cooltrainer Dianne + // 57 + 698: Potential Double Battle with Tuber Lola and Tuber Chandler + // 64 + 491 + 697: Potential Double Battles with Tuber Ricky, Sailor Edmond, and Tuber Hailey + // 107 + 764: Potential Double Battle with Hex Maniac Tammy and Bug Maniac Cale + // 108 + 475: Potential Double Battle with Hex Maniac Valerie and Psychic Cedric + // 115 + 502: Potential Double Battle with Lady Daphne and Pokéfan Annika + // 118 + 129: Potential Double Battle with Lady Brianna and Beauty Bridget + // 130 + 301: Potential Double Battle with Beauty Olivia and Pokéfan Bethany + // 131 + 614: Potential Double Battle with Beauty Tiffany and Lass Crissy + // 137 + 511: Potential Double Battle with Expert Mollie and Expert Conor + // 144 + 375: Potential Double Battle with Beauty Thalia and Youngster Demetrius + // 146 + 579: Potential Double Battle with Team Magma Grunts on Mt. Chimney + // 160 + 595: Potential Double Battle with Swimmer Roland and Triathlete Isabella + // 168 + 455: Potential Double Battle with Swimmer Santiago and Swimmer Katie + // 170 + 460: Potential Double Battle with Swimmer Franklin and Swimmer Debra + // 171 + 385: Potential Double Battle with Swimmer Kevin and Triathlete Taila + // 180 + 509: Potential Double Battle with Black Belt Hitoshi and Battle Girl Reyna + // 182 + 307 + 748 + 749: Potential Double Battles with Black Belt Koichi, Expert Timothy, Triathlete Kyra, and Ninja Boy Jaiden + // 191 + 649: Potential Double Battle with Guitarist Kirk and Battle Girl Vivian + // 194 + 802: Potential Double Battle with Guitarist Shawn and Bug Maniac Angelo + // 201 + 648: Potential Double Battle with Kindler Cole and Cooltrainer Gerald + // 204 + 501: Potential Double Battle with Kindler Jace and Hiker Eli + // 217 + 566: Potential Double Battle with Picnicker Autumn and Triathlete Julio + // 232 + 701: Potential Double Battle with Psychic Edward and Triathlete Alyssa + // 233 + 246: Potential Double Battle with Psychic Preston and Psychic Maura + // 234 + 244 + 575 + 582: Potential Double Battles with Psychic Virgil, Psychic Hannah, Hex Maniac Sylvia, and Gentleman Nate + // 235 + 245: Potential Double Battle with Psychic Blake and Psychic Samantha + // 248 + 849: Potential Double Battle with Psychic Alexis and Psychic Alvaro + // 273 + 605: Potential Double Battle with School Kid Jerry and Lass Janice + // 302 + 699: Potential Double Battle with Pokéfan Isabel and Pokéfan Kaleb + // 321 + 571: Potential Double Battle with Youngster Tommy and Hiker Marc + // 324 + 325: Potential Double Battle with Cooltrainer Quincy and Cooltrainer Katelynn + // 345 + 742: Potential Double Battle with Fisherman Carter and Bird Keeper Elijah + // 377 + 459: Potential Double Battle with Triathlete Pablo and Swimmer Sienna + // 383 + 576: Potential Double Battle with Triathlete Isobel and Swimmer Leonardo + // 400 + 761: Potential Double Battle with Bird Keeper Phil and Parasol Lady Rachel + // 401 + 655: Potential Double Battle with Bird Keeper Jared and Picnicker Ashley + // 403 + 506: Potential Double Battle with Bird Keeper Presley and Expert Auron + // 413 + 507: Potential Double Battle with Bird Keeper Alex and Sailor Kelvin + // 415 + 759: Potential Double Battle with Ninja Boy Yasu and Guitarist Fabian + // 416 + 760: Potential Double Battle with Ninja Boy Takashi and Kindler Dayton + // 418 + 547: Potential Double Battle with Tuber Jani and Ruin Maniac Garrison + // 420 + 710 + 711: Potential Double Battles with Ninja Boy Lung, Camper Lawrence, and PokéManiac Wyatt + // 436 + 762: Potential Double Battle with Parasol Lady Angelica and Cooltrainer Leonel + // 445 + 739: Potential Double Battle with Swimmer Beth and Triathlete Camron + // 464 + 578: Potential Double Battle with Swimmer Carlee and Swimmer Harrison + // 494 + 495: Potential Double Battle with Sailor Phillip and Sailor Leonard (S.S. Tidal) + // 503 + 539: Potential Double Battle with Cooltrainer Jazmyn and Bug Catcher Davis + // 512 + 700: Potential Double Battle with Collector Edwin and Guitarist Joseph + // 513 + 752: Potential Double Battle with Collector Hector and Psychic Marlene + // 540 + 546: Potential Double Battle with Cooltrainer Mitchell and Cooltrainer Halle + // 577 + 674: Potential Double Battle with Cooltrainer Athena and Bird Keeper Aidan + // 580 + 676: Potential Double Battle with Swimmer Clarence and Swimmer Tisha + // 583 + 584 + 585 + 591: Potential Double Battles with Hex Maniac Kathleen, Gentleman Clifford, Psychic Nicholas, and Psychic Macey + // 594 + 733: Potential Double Battle with Expert Paxton and Cooltrainer Darcy + // 598 + 758: Potential Double Battle with Cooltrainer Jonathan and Expert Makayla + // 629 + 712: Potential Double Battle with Hiker Lucas and Picnicker Angelina + // 631 + 753 + 754: Potential Double Battles with Hiker Clark, Hiker Devan, and Youngster Johnson + // 653 + 763: Potential Double Battle with Ninja Boy Riley and Battle Girl Callie + // 694 + 695: Potential Double Battle with Rich Boy Dawson and Lady Sarah + // 702 + 703: Potential Double Battle with Guitarist Marcos and Black Belt Rhett + // 704 + 705: Potential Double Battle with Camper Tyron and Aroma Lady Celina + // 706 + 707: Potential Double Battle with Picnicker Bianca and Kindler Hayden + // 708 + 709: Potential Double Battle with Picnicker Sophie and Bird Keeper Coby + // 713 + 714: Potential Double Battle with Fisherman Kai and Picnicker Charlotte + // 719 + 720: Potential Double Battle with Team Magma Grunts in Team Magma Hideout + // 727 + 728: Potential Double Battle with Team Magma Grunts in Team Magma Hideout + // 735 + 736: Potential Double Battle with Swimmer Pete and Swimmer Isabelle + // 737 + 738: Potential Double Battle with Ruin Maniac Andres and Bird Keeper Josue + // 740 + 741: Potential Double Battle with Sailor Cory and Cooltrainer Carolina + // 743 + 744 + 745: Potential Double Battles with Picnicker Celia, Ruin Maniac Bryan, and Camper Branden + // 746 + 747: Potential Double Battle with Kindler Bryant and Aroma Lady Shayla + // 750 + 751: Potential Double Battle with Psychic Alix and Battle Girl Helene + // 755 + 756 + 757: Potential Double Battles with Triathlete Melina, Psychic Brandi, and Battle Girl Aisha + // 765 + 766: Potential Double Battle with Pokémon Breeder Myles and Pokémon Breeder Pat + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.POTENTIAL, 1, 3, 8, 9, 11, 12, 13, 14, 15, 18, 28, + 29, 31, 33, 35, 37, 38, 57, 64, 106, 107, 108, 115, 118, 124, 129, 130, 131, 137, 144, 145, 146, 160, + 168, 170, 171, 180, 182, 191, 192, 193, 194, 195, 201, 204, 217, 232, 233, 234, 235, 236, 244, 245, 246, + 247, 248, 249, 273, 301, 302, 307, 321, 324, 325, 345, 375, 377, 383, 385, 400, 401, 403, 413, 415, 416, + 417, 418, 420, 436, 445, 450, 455, 459, 460, 464, 475, 491, 494, 495, 501, 502, 503, 506, 507, 509, 511, + 512, 513, 539, 540, 546, 547, 566, 567, 571, 575, 576, 577, 578, 579, 580, 582, 583, 584, 585, 591, 594, + 595, 596, 598, 605, 614, 629, 631, 648, 649, 653, 655, 674, 676, 694, 695, 697, 698, 699, 700, 701, 702, + 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 719, 720, 727, 728, 733, 735, 736, 737, + 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, + 759, 760, 761, 762, 763, 764, 765, 766, 767, 802, 849 + ); + } + + private static void setMultiBattleStatus(List allTrainers, Trainer.MultiBattleStatus status, int... numbers) { + for (int num : numbers) { + if (allTrainers.size() > (num - 1)) { + allTrainers.get(num - 1).multiBattleStatus = status; + } + } + } + + public static final Map balancedItemPrices = Stream.of(new Integer[][] { + // Skip item index 0. All prices divided by 10 + {Gen3Items.masterBall, 300}, + {Gen3Items.ultraBall, 120}, + {Gen3Items.greatBall, 60}, + {Gen3Items.pokeBall, 20}, + {Gen3Items.safariBall, 50}, + {Gen3Items.netBall, 100}, + {Gen3Items.diveBall, 100}, + {Gen3Items.nestBall, 100}, + {Gen3Items.repeatBall, 100}, + {Gen3Items.timerBall, 100}, + {Gen3Items.luxuryBall, 100}, + {Gen3Items.premierBall, 20}, + {Gen3Items.potion, 30}, + {Gen3Items.antidote, 10}, + {Gen3Items.burnHeal, 25}, + {Gen3Items.iceHeal, 25}, + {Gen3Items.awakening, 25}, + {Gen3Items.parlyzHeal, 20}, + {Gen3Items.fullRestore, 300}, + {Gen3Items.maxPotion, 250}, + {Gen3Items.hyperPotion, 120}, + {Gen3Items.superPotion, 70}, + {Gen3Items.fullHeal, 60}, + {Gen3Items.revive, 150}, + {Gen3Items.maxRevive, 400}, + {Gen3Items.freshWater, 40}, + {Gen3Items.sodaPop, 60}, + {Gen3Items.lemonade, 70}, + {Gen3Items.moomooMilk, 80}, + {Gen3Items.energyPowder, 40}, + {Gen3Items.energyRoot, 110}, + {Gen3Items.healPowder, 45}, + {Gen3Items.revivalHerb, 280}, + {Gen3Items.ether, 300}, + {Gen3Items.maxEther, 450}, + {Gen3Items.elixir, 1500}, + {Gen3Items.maxElixir, 1800}, + {Gen3Items.lavaCookie, 45}, + {Gen3Items.blueFlute, 2}, + {Gen3Items.yellowFlute, 2}, + {Gen3Items.redFlute, 2}, + {Gen3Items.blackFlute, 2}, + {Gen3Items.whiteFlute, 2}, + {Gen3Items.berryJuice, 10}, + {Gen3Items.sacredAsh, 1000}, + {Gen3Items.shoalSalt, 2}, + {Gen3Items.shoalShell, 2}, + {Gen3Items.redShard, 40}, + {Gen3Items.blueShard, 40}, + {Gen3Items.yellowShard, 40}, + {Gen3Items.greenShard, 40}, + {Gen3Items.unknown52, 0}, + {Gen3Items.unknown53, 0}, + {Gen3Items.unknown54, 0}, + {Gen3Items.unknown55, 0}, + {Gen3Items.unknown56, 0}, + {Gen3Items.unknown57, 0}, + {Gen3Items.unknown58, 0}, + {Gen3Items.unknown59, 0}, + {Gen3Items.unknown60, 0}, + {Gen3Items.unknown61, 0}, + {Gen3Items.unknown62, 0}, + {Gen3Items.hpUp, 980}, + {Gen3Items.protein, 980}, + {Gen3Items.iron, 980}, + {Gen3Items.carbos, 980}, + {Gen3Items.calcium, 980}, + {Gen3Items.rareCandy, 1000}, + {Gen3Items.ppUp, 980}, + {Gen3Items.zinc, 980}, + {Gen3Items.ppMax, 2490}, + {Gen3Items.unknown72, 0}, + {Gen3Items.guardSpec, 70}, + {Gen3Items.direHit, 65}, + {Gen3Items.xAttack, 50}, + {Gen3Items.xDefend, 55}, + {Gen3Items.xSpeed, 35}, + {Gen3Items.xAccuracy, 95}, + {Gen3Items.xSpecial, 35}, + {Gen3Items.pokeDoll, 100}, + {Gen3Items.fluffyTail, 100}, + {Gen3Items.unknown82, 0}, + {Gen3Items.superRepel, 50}, + {Gen3Items.maxRepel, 70}, + {Gen3Items.escapeRope, 55}, + {Gen3Items.repel, 35}, + {Gen3Items.unknown87, 0}, + {Gen3Items.unknown88, 0}, + {Gen3Items.unknown89, 0}, + {Gen3Items.unknown90, 0}, + {Gen3Items.unknown91, 0}, + {Gen3Items.unknown92, 0}, + {Gen3Items.sunStone, 300}, + {Gen3Items.moonStone, 300}, + {Gen3Items.fireStone, 300}, + {Gen3Items.thunderstone, 300}, + {Gen3Items.waterStone, 300}, + {Gen3Items.leafStone, 300}, + {Gen3Items.unknown99, 0}, + {Gen3Items.unknown100, 0}, + {Gen3Items.unknown101, 0}, + {Gen3Items.unknown102, 0}, + {Gen3Items.tinyMushroom, 50}, + {Gen3Items.bigMushroom, 500}, + {Gen3Items.unknown105, 0}, + {Gen3Items.pearl, 140}, + {Gen3Items.bigPearl, 750}, + {Gen3Items.stardust, 200}, + {Gen3Items.starPiece, 980}, + {Gen3Items.nugget, 1000}, + {Gen3Items.heartScale, 500}, + {Gen3Items.unknown112, 0}, + {Gen3Items.unknown113, 0}, + {Gen3Items.unknown114, 0}, + {Gen3Items.unknown115, 0}, + {Gen3Items.unknown116, 0}, + {Gen3Items.unknown117, 0}, + {Gen3Items.unknown118, 0}, + {Gen3Items.unknown119, 0}, + {Gen3Items.unknown120, 0}, + {Gen3Items.orangeMail, 5}, + {Gen3Items.harborMail, 5}, + {Gen3Items.glitterMail, 5}, + {Gen3Items.mechMail, 5}, + {Gen3Items.woodMail, 5}, + {Gen3Items.waveMail, 5}, + {Gen3Items.beadMail, 5}, + {Gen3Items.shadowMail, 5}, + {Gen3Items.tropicMail, 5}, + {Gen3Items.dreamMail, 5}, + {Gen3Items.fabMail, 5}, + {Gen3Items.retroMail, 5}, + {Gen3Items.cheriBerry, 20}, + {Gen3Items.chestoBerry, 25}, + {Gen3Items.pechaBerry, 10}, + {Gen3Items.rawstBerry, 25}, + {Gen3Items.aspearBerry, 25}, + {Gen3Items.leppaBerry, 300}, + {Gen3Items.oranBerry, 5}, + {Gen3Items.persimBerry, 20}, + {Gen3Items.lumBerry, 50}, + {Gen3Items.sitrusBerry, 50}, + {Gen3Items.figyBerry, 10}, + {Gen3Items.wikiBerry, 10}, + {Gen3Items.magoBerry, 10}, + {Gen3Items.aguavBerry, 10}, + {Gen3Items.iapapaBerry, 10}, + {Gen3Items.razzBerry, 50}, + {Gen3Items.blukBerry, 50}, + {Gen3Items.nanabBerry, 50}, + {Gen3Items.wepearBerry, 50}, + {Gen3Items.pinapBerry, 50}, + {Gen3Items.pomegBerry, 50}, + {Gen3Items.kelpsyBerry, 50}, + {Gen3Items.qualotBerry, 50}, + {Gen3Items.hondewBerry, 50}, + {Gen3Items.grepaBerry, 50}, + {Gen3Items.tamatoBerry, 50}, + {Gen3Items.cornnBerry, 50}, + {Gen3Items.magostBerry, 50}, + {Gen3Items.rabutaBerry, 50}, + {Gen3Items.nomelBerry, 50}, + {Gen3Items.spelonBerry, 50}, + {Gen3Items.pamtreBerry, 50}, + {Gen3Items.watmelBerry, 50}, + {Gen3Items.durinBerry, 50}, + {Gen3Items.belueBerry, 50}, + {Gen3Items.liechiBerry, 100}, + {Gen3Items.ganlonBerry, 100}, + {Gen3Items.salacBerry, 100}, + {Gen3Items.petayaBerry, 100}, + {Gen3Items.apicotBerry, 100}, + {Gen3Items.lansatBerry, 100}, + {Gen3Items.starfBerry, 100}, + {Gen3Items.enigmaBerry, 100}, + {Gen3Items.unknown176, 0}, + {Gen3Items.unknown177, 0}, + {Gen3Items.unknown178, 0}, + {Gen3Items.brightPowder, 300}, + {Gen3Items.whiteHerb, 100}, + {Gen3Items.machoBrace, 300}, + {Gen3Items.expShare, 600}, + {Gen3Items.quickClaw, 450}, + {Gen3Items.sootheBell, 100}, + {Gen3Items.mentalHerb, 100}, + {Gen3Items.choiceBand, 1000}, + {Gen3Items.kingsRock, 500}, + {Gen3Items.silverPowder, 200}, + {Gen3Items.amuletCoin, 1500}, + {Gen3Items.cleanseTag, 100}, + {Gen3Items.soulDew, 20}, + {Gen3Items.deepSeaTooth, 300}, + {Gen3Items.deepSeaScale, 300}, + {Gen3Items.smokeBall, 20}, + {Gen3Items.everstone, 20}, + {Gen3Items.focusBand, 300}, + {Gen3Items.luckyEgg, 1000}, + {Gen3Items.scopeLens, 500}, + {Gen3Items.metalCoat, 300}, + {Gen3Items.leftovers, 1000}, + {Gen3Items.dragonScale, 300}, + {Gen3Items.lightBall, 10}, + {Gen3Items.softSand, 200}, + {Gen3Items.hardStone, 200}, + {Gen3Items.miracleSeed, 200}, + {Gen3Items.blackGlasses, 200}, + {Gen3Items.blackBelt, 200}, + {Gen3Items.magnet, 200}, + {Gen3Items.mysticWater, 200}, + {Gen3Items.sharpBeak, 200}, + {Gen3Items.poisonBarb, 200}, + {Gen3Items.neverMeltIce, 200}, + {Gen3Items.spellTag, 200}, + {Gen3Items.twistedSpoon, 200}, + {Gen3Items.charcoal, 200}, + {Gen3Items.dragonFang, 200}, + {Gen3Items.silkScarf, 200}, + {Gen3Items.upGrade, 300}, + {Gen3Items.shellBell, 600}, + {Gen3Items.seaIncense, 200}, + {Gen3Items.laxIncense, 300}, + {Gen3Items.luckyPunch, 1}, + {Gen3Items.metalPowder, 1}, + {Gen3Items.thickClub, 50}, + {Gen3Items.stick, 20}, + {Gen3Items.unknown226, 0}, + {Gen3Items.unknown227, 0}, + {Gen3Items.unknown228, 0}, + {Gen3Items.unknown229, 0}, + {Gen3Items.unknown230, 0}, + {Gen3Items.unknown231, 0}, + {Gen3Items.unknown232, 0}, + {Gen3Items.unknown233, 0}, + {Gen3Items.unknown234, 0}, + {Gen3Items.unknown235, 0}, + {Gen3Items.unknown236, 0}, + {Gen3Items.unknown237, 0}, + {Gen3Items.unknown238, 0}, + {Gen3Items.unknown239, 0}, + {Gen3Items.unknown240, 0}, + {Gen3Items.unknown241, 0}, + {Gen3Items.unknown242, 0}, + {Gen3Items.unknown243, 0}, + {Gen3Items.unknown244, 0}, + {Gen3Items.unknown245, 0}, + {Gen3Items.unknown246, 0}, + {Gen3Items.unknown247, 0}, + {Gen3Items.unknown248, 0}, + {Gen3Items.unknown249, 0}, + {Gen3Items.unknown250, 0}, + {Gen3Items.unknown251, 0}, + {Gen3Items.unknown252, 0}, + {Gen3Items.unknown253, 0}, + {Gen3Items.redScarf, 10}, + {Gen3Items.blueScarf, 10}, + {Gen3Items.pinkScarf, 10}, + {Gen3Items.greenScarf, 10}, + {Gen3Items.yellowScarf, 10}, + {Gen3Items.machBike, 0}, + {Gen3Items.coinCase, 0}, + {Gen3Items.itemfinder, 0}, + {Gen3Items.oldRod, 0}, + {Gen3Items.goodRod, 0}, + {Gen3Items.superRod, 0}, + {Gen3Items.ssTicket, 0}, + {Gen3Items.contestPass, 0}, + {Gen3Items.unknown267, 0}, + {Gen3Items.wailmerPail, 0}, + {Gen3Items.devonGoods, 0}, + {Gen3Items.sootSack, 0}, + {Gen3Items.basementKey, 0}, + {Gen3Items.acroBike, 0}, + {Gen3Items.pokeblockCase, 0}, + {Gen3Items.letter, 0}, + {Gen3Items.eonTicket, 0}, + {Gen3Items.redOrb, 0}, + {Gen3Items.blueOrb, 0}, + {Gen3Items.scanner, 0}, + {Gen3Items.goGoggles, 0}, + {Gen3Items.meteorite, 0}, + {Gen3Items.rm1Key, 0}, + {Gen3Items.rm2Key, 0}, + {Gen3Items.rm4Key, 0}, + {Gen3Items.rm6Key, 0}, + {Gen3Items.storageKey, 0}, + {Gen3Items.rootFossil, 0}, + {Gen3Items.clawFossil, 0}, + {Gen3Items.devonScope, 0}, + {Gen3Items.tm01, 300}, + {Gen3Items.tm02, 300}, + {Gen3Items.tm03, 300}, + {Gen3Items.tm04, 150}, + {Gen3Items.tm05, 100}, + {Gen3Items.tm06, 300}, + {Gen3Items.tm07, 200}, + {Gen3Items.tm08, 150}, + {Gen3Items.tm09, 200}, + {Gen3Items.tm10, 200}, + {Gen3Items.tm11, 200}, + {Gen3Items.tm12, 150}, + {Gen3Items.tm13, 300}, + {Gen3Items.tm14, 550}, + {Gen3Items.tm15, 750}, + {Gen3Items.tm16, 200}, + {Gen3Items.tm17, 200}, + {Gen3Items.tm18, 200}, + {Gen3Items.tm19, 300}, + {Gen3Items.tm20, 200}, + {Gen3Items.tm21, 100}, + {Gen3Items.tm22, 300}, + {Gen3Items.tm23, 300}, + {Gen3Items.tm24, 300}, + {Gen3Items.tm25, 550}, + {Gen3Items.tm26, 300}, + {Gen3Items.tm27, 100}, + {Gen3Items.tm28, 200}, + {Gen3Items.tm29, 300}, + {Gen3Items.tm30, 300}, + {Gen3Items.tm31, 300}, + {Gen3Items.tm32, 100}, + {Gen3Items.tm33, 200}, + {Gen3Items.tm34, 300}, + {Gen3Items.tm35, 300}, + {Gen3Items.tm36, 300}, + {Gen3Items.tm37, 200}, + {Gen3Items.tm38, 550}, + {Gen3Items.tm39, 200}, + {Gen3Items.tm40, 300}, + {Gen3Items.tm41, 150}, + {Gen3Items.tm42, 300}, + {Gen3Items.tm43, 200}, + {Gen3Items.tm44, 300}, + {Gen3Items.tm45, 300}, + {Gen3Items.tm46, 200}, + {Gen3Items.tm47, 300}, + {Gen3Items.tm48, 300}, + {Gen3Items.tm49, 150}, + {Gen3Items.tm50, 550}, + {Gen3Items.hm01, 0}, + {Gen3Items.hm02, 0}, + {Gen3Items.hm03, 0}, + {Gen3Items.hm04, 0}, + {Gen3Items.hm05, 0}, + {Gen3Items.hm06, 0}, + {Gen3Items.hm07, 0}, + {Gen3Items.hm08, 0}, + {Gen3Items.unknown347, 0}, + {Gen3Items.unknown348, 0}, + {Gen3Items.oaksParcel, 0}, + {Gen3Items.pokeFlute, 0}, + {Gen3Items.secretKey, 0}, + {Gen3Items.bikeVoucher, 0}, + {Gen3Items.goldTeeth, 0}, + {Gen3Items.oldAmber, 0}, + {Gen3Items.cardKey, 0}, + {Gen3Items.liftKey, 0}, + {Gen3Items.helixFossil, 0}, + {Gen3Items.domeFossil, 0}, + {Gen3Items.silphScope, 0}, + {Gen3Items.bicycle, 0}, + {Gen3Items.townMap, 0}, + {Gen3Items.vsSeeker, 0}, + {Gen3Items.fameChecker, 0}, + {Gen3Items.tmCase, 0}, + {Gen3Items.berryPouch, 0}, + {Gen3Items.teachyTV, 0}, + {Gen3Items.triPass, 0}, + {Gen3Items.rainbowPass, 0}, + {Gen3Items.tea, 0}, + {Gen3Items.mysticTicket, 0}, + {Gen3Items.auroraTicket, 0}, + {Gen3Items.powderJar, 0}, + {Gen3Items.ruby, 0}, + {Gen3Items.sapphire, 0}, + {Gen3Items.magmaEmblem, 0}, + {Gen3Items.oldSeaMap, 0}, + }).collect(Collectors.toMap(kv -> kv[0], kv -> kv[1])); +} diff --git a/src/com/pkrandom/constants/Gen3Items.java b/src/com/pkrandom/constants/Gen3Items.java new file mode 100644 index 0000000..8e02916 --- /dev/null +++ b/src/com/pkrandom/constants/Gen3Items.java @@ -0,0 +1,410 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Gen3Items.java - defines an index number constant for every item in --*/ +/*-- the Generation 3 games. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class Gen3Items { + // https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_(Generation_III) + public static final int nothing = 0; + public static final int masterBall = 1; + public static final int ultraBall = 2; + public static final int greatBall = 3; + public static final int pokeBall = 4; + public static final int safariBall = 5; + public static final int netBall = 6; + public static final int diveBall = 7; + public static final int nestBall = 8; + public static final int repeatBall = 9; + public static final int timerBall = 10; + public static final int luxuryBall = 11; + public static final int premierBall = 12; + public static final int potion = 13; + public static final int antidote = 14; + public static final int burnHeal = 15; + public static final int iceHeal = 16; + public static final int awakening = 17; + public static final int parlyzHeal = 18; + public static final int fullRestore = 19; + public static final int maxPotion = 20; + public static final int hyperPotion = 21; + public static final int superPotion = 22; + public static final int fullHeal = 23; + public static final int revive = 24; + public static final int maxRevive = 25; + public static final int freshWater = 26; + public static final int sodaPop = 27; + public static final int lemonade = 28; + public static final int moomooMilk = 29; + public static final int energyPowder = 30; + public static final int energyRoot = 31; + public static final int healPowder = 32; + public static final int revivalHerb = 33; + public static final int ether = 34; + public static final int maxEther = 35; + public static final int elixir = 36; + public static final int maxElixir = 37; + public static final int lavaCookie = 38; + public static final int blueFlute = 39; + public static final int yellowFlute = 40; + public static final int redFlute = 41; + public static final int blackFlute = 42; + public static final int whiteFlute = 43; + public static final int berryJuice = 44; + public static final int sacredAsh = 45; + public static final int shoalSalt = 46; + public static final int shoalShell = 47; + public static final int redShard = 48; + public static final int blueShard = 49; + public static final int yellowShard = 50; + public static final int greenShard = 51; + public static final int unknown52 = 52; + public static final int unknown53 = 53; + public static final int unknown54 = 54; + public static final int unknown55 = 55; + public static final int unknown56 = 56; + public static final int unknown57 = 57; + public static final int unknown58 = 58; + public static final int unknown59 = 59; + public static final int unknown60 = 60; + public static final int unknown61 = 61; + public static final int unknown62 = 62; + public static final int hpUp = 63; + public static final int protein = 64; + public static final int iron = 65; + public static final int carbos = 66; + public static final int calcium = 67; + public static final int rareCandy = 68; + public static final int ppUp = 69; + public static final int zinc = 70; + public static final int ppMax = 71; + public static final int unknown72 = 72; + public static final int guardSpec = 73; + public static final int direHit = 74; + public static final int xAttack = 75; + public static final int xDefend = 76; + public static final int xSpeed = 77; + public static final int xAccuracy = 78; + public static final int xSpecial = 79; + public static final int pokeDoll = 80; + public static final int fluffyTail = 81; + public static final int unknown82 = 82; + public static final int superRepel = 83; + public static final int maxRepel = 84; + public static final int escapeRope = 85; + public static final int repel = 86; + public static final int unknown87 = 87; + public static final int unknown88 = 88; + public static final int unknown89 = 89; + public static final int unknown90 = 90; + public static final int unknown91 = 91; + public static final int unknown92 = 92; + public static final int sunStone = 93; + public static final int moonStone = 94; + public static final int fireStone = 95; + public static final int thunderstone = 96; + public static final int waterStone = 97; + public static final int leafStone = 98; + public static final int unknown99 = 99; + public static final int unknown100 = 100; + public static final int unknown101 = 101; + public static final int unknown102 = 102; + public static final int tinyMushroom = 103; + public static final int bigMushroom = 104; + public static final int unknown105 = 105; + public static final int pearl = 106; + public static final int bigPearl = 107; + public static final int stardust = 108; + public static final int starPiece = 109; + public static final int nugget = 110; + public static final int heartScale = 111; + public static final int unknown112 = 112; + public static final int unknown113 = 113; + public static final int unknown114 = 114; + public static final int unknown115 = 115; + public static final int unknown116 = 116; + public static final int unknown117 = 117; + public static final int unknown118 = 118; + public static final int unknown119 = 119; + public static final int unknown120 = 120; + public static final int orangeMail = 121; + public static final int harborMail = 122; + public static final int glitterMail = 123; + public static final int mechMail = 124; + public static final int woodMail = 125; + public static final int waveMail = 126; + public static final int beadMail = 127; + public static final int shadowMail = 128; + public static final int tropicMail = 129; + public static final int dreamMail = 130; + public static final int fabMail = 131; + public static final int retroMail = 132; + public static final int cheriBerry = 133; + public static final int chestoBerry = 134; + public static final int pechaBerry = 135; + public static final int rawstBerry = 136; + public static final int aspearBerry = 137; + public static final int leppaBerry = 138; + public static final int oranBerry = 139; + public static final int persimBerry = 140; + public static final int lumBerry = 141; + public static final int sitrusBerry = 142; + public static final int figyBerry = 143; + public static final int wikiBerry = 144; + public static final int magoBerry = 145; + public static final int aguavBerry = 146; + public static final int iapapaBerry = 147; + public static final int razzBerry = 148; + public static final int blukBerry = 149; + public static final int nanabBerry = 150; + public static final int wepearBerry = 151; + public static final int pinapBerry = 152; + public static final int pomegBerry = 153; + public static final int kelpsyBerry = 154; + public static final int qualotBerry = 155; + public static final int hondewBerry = 156; + public static final int grepaBerry = 157; + public static final int tamatoBerry = 158; + public static final int cornnBerry = 159; + public static final int magostBerry = 160; + public static final int rabutaBerry = 161; + public static final int nomelBerry = 162; + public static final int spelonBerry = 163; + public static final int pamtreBerry = 164; + public static final int watmelBerry = 165; + public static final int durinBerry = 166; + public static final int belueBerry = 167; + public static final int liechiBerry = 168; + public static final int ganlonBerry = 169; + public static final int salacBerry = 170; + public static final int petayaBerry = 171; + public static final int apicotBerry = 172; + public static final int lansatBerry = 173; + public static final int starfBerry = 174; + public static final int enigmaBerry = 175; + public static final int unknown176 = 176; + public static final int unknown177 = 177; + public static final int unknown178 = 178; + public static final int brightPowder = 179; + public static final int whiteHerb = 180; + public static final int machoBrace = 181; + public static final int expShare = 182; + public static final int quickClaw = 183; + public static final int sootheBell = 184; + public static final int mentalHerb = 185; + public static final int choiceBand = 186; + public static final int kingsRock = 187; + public static final int silverPowder = 188; + public static final int amuletCoin = 189; + public static final int cleanseTag = 190; + public static final int soulDew = 191; + public static final int deepSeaTooth = 192; + public static final int deepSeaScale = 193; + public static final int smokeBall = 194; + public static final int everstone = 195; + public static final int focusBand = 196; + public static final int luckyEgg = 197; + public static final int scopeLens = 198; + public static final int metalCoat = 199; + public static final int leftovers = 200; + public static final int dragonScale = 201; + public static final int lightBall = 202; + public static final int softSand = 203; + public static final int hardStone = 204; + public static final int miracleSeed = 205; + public static final int blackGlasses = 206; + public static final int blackBelt = 207; + public static final int magnet = 208; + public static final int mysticWater = 209; + public static final int sharpBeak = 210; + public static final int poisonBarb = 211; + public static final int neverMeltIce = 212; + public static final int spellTag = 213; + public static final int twistedSpoon = 214; + public static final int charcoal = 215; + public static final int dragonFang = 216; + public static final int silkScarf = 217; + public static final int upGrade = 218; + public static final int shellBell = 219; + public static final int seaIncense = 220; + public static final int laxIncense = 221; + public static final int luckyPunch = 222; + public static final int metalPowder = 223; + public static final int thickClub = 224; + public static final int stick = 225; + public static final int unknown226 = 226; + public static final int unknown227 = 227; + public static final int unknown228 = 228; + public static final int unknown229 = 229; + public static final int unknown230 = 230; + public static final int unknown231 = 231; + public static final int unknown232 = 232; + public static final int unknown233 = 233; + public static final int unknown234 = 234; + public static final int unknown235 = 235; + public static final int unknown236 = 236; + public static final int unknown237 = 237; + public static final int unknown238 = 238; + public static final int unknown239 = 239; + public static final int unknown240 = 240; + public static final int unknown241 = 241; + public static final int unknown242 = 242; + public static final int unknown243 = 243; + public static final int unknown244 = 244; + public static final int unknown245 = 245; + public static final int unknown246 = 246; + public static final int unknown247 = 247; + public static final int unknown248 = 248; + public static final int unknown249 = 249; + public static final int unknown250 = 250; + public static final int unknown251 = 251; + public static final int unknown252 = 252; + public static final int unknown253 = 253; + public static final int redScarf = 254; + public static final int blueScarf = 255; + public static final int pinkScarf = 256; + public static final int greenScarf = 257; + public static final int yellowScarf = 258; + public static final int machBike = 259; + public static final int coinCase = 260; + public static final int itemfinder = 261; + public static final int oldRod = 262; + public static final int goodRod = 263; + public static final int superRod = 264; + public static final int ssTicket = 265; + public static final int contestPass = 266; + public static final int unknown267 = 267; + public static final int wailmerPail = 268; + public static final int devonGoods = 269; + public static final int sootSack = 270; + public static final int basementKey = 271; + public static final int acroBike = 272; + public static final int pokeblockCase = 273; + public static final int letter = 274; + public static final int eonTicket = 275; + public static final int redOrb = 276; + public static final int blueOrb = 277; + public static final int scanner = 278; + public static final int goGoggles = 279; + public static final int meteorite = 280; + public static final int rm1Key = 281; + public static final int rm2Key = 282; + public static final int rm4Key = 283; + public static final int rm6Key = 284; + public static final int storageKey = 285; + public static final int rootFossil = 286; + public static final int clawFossil = 287; + public static final int devonScope = 288; + public static final int tm01 = 289; + public static final int tm02 = 290; + public static final int tm03 = 291; + public static final int tm04 = 292; + public static final int tm05 = 293; + public static final int tm06 = 294; + public static final int tm07 = 295; + public static final int tm08 = 296; + public static final int tm09 = 297; + public static final int tm10 = 298; + public static final int tm11 = 299; + public static final int tm12 = 300; + public static final int tm13 = 301; + public static final int tm14 = 302; + public static final int tm15 = 303; + public static final int tm16 = 304; + public static final int tm17 = 305; + public static final int tm18 = 306; + public static final int tm19 = 307; + public static final int tm20 = 308; + public static final int tm21 = 309; + public static final int tm22 = 310; + public static final int tm23 = 311; + public static final int tm24 = 312; + public static final int tm25 = 313; + public static final int tm26 = 314; + public static final int tm27 = 315; + public static final int tm28 = 316; + public static final int tm29 = 317; + public static final int tm30 = 318; + public static final int tm31 = 319; + public static final int tm32 = 320; + public static final int tm33 = 321; + public static final int tm34 = 322; + public static final int tm35 = 323; + public static final int tm36 = 324; + public static final int tm37 = 325; + public static final int tm38 = 326; + public static final int tm39 = 327; + public static final int tm40 = 328; + public static final int tm41 = 329; + public static final int tm42 = 330; + public static final int tm43 = 331; + public static final int tm44 = 332; + public static final int tm45 = 333; + public static final int tm46 = 334; + public static final int tm47 = 335; + public static final int tm48 = 336; + public static final int tm49 = 337; + public static final int tm50 = 338; + public static final int hm01 = 339; + public static final int hm02 = 340; + public static final int hm03 = 341; + public static final int hm04 = 342; + public static final int hm05 = 343; + public static final int hm06 = 344; + public static final int hm07 = 345; + public static final int hm08 = 346; + public static final int unknown347 = 347; + public static final int unknown348 = 348; + + /* Exclusive to FRLG and Emerald */ + public static final int oaksParcel = 349; + public static final int pokeFlute = 350; + public static final int secretKey = 351; + public static final int bikeVoucher = 352; + public static final int goldTeeth = 353; + public static final int oldAmber = 354; + public static final int cardKey = 355; + public static final int liftKey = 356; + public static final int helixFossil = 357; + public static final int domeFossil = 358; + public static final int silphScope = 359; + public static final int bicycle = 360; + public static final int townMap = 361; + public static final int vsSeeker = 362; + public static final int fameChecker = 363; + public static final int tmCase = 364; + public static final int berryPouch = 365; + public static final int teachyTV = 366; + public static final int triPass = 367; + public static final int rainbowPass = 368; + public static final int tea = 369; + public static final int mysticTicket = 370; + public static final int auroraTicket = 371; + public static final int powderJar = 372; + public static final int ruby = 373; + public static final int sapphire = 374; + + /* Exclusive to Emerald */ + public static final int magmaEmblem = 375; + public static final int oldSeaMap = 376; +} diff --git a/src/com/pkrandom/constants/Gen4Constants.java b/src/com/pkrandom/constants/Gen4Constants.java new file mode 100644 index 0000000..91c5ff2 --- /dev/null +++ b/src/com/pkrandom/constants/Gen4Constants.java @@ -0,0 +1,2107 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Gen4Constants.java - Constants for DPPt and HGSS --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.pkrandom.pokemon.*; + +public class Gen4Constants { + + public static final int Type_DP = 0; + public static final int Type_Plat = 1; + public static final int Type_HGSS = 2; + + public static final int arm9Offset = 0x02000000; + + public static final int pokemonCount = 493, moveCount = 467; + private static final int dpFormeCount = 5, platHgSsFormeCount = 12; + public static final int formeOffset = 2; + + public static final int bsHPOffset = 0, bsAttackOffset = 1, bsDefenseOffset = 2, bsSpeedOffset = 3, + bsSpAtkOffset = 4, bsSpDefOffset = 5, bsPrimaryTypeOffset = 6, bsSecondaryTypeOffset = 7, + bsCatchRateOffset = 8, bsCommonHeldItemOffset = 12, bsRareHeldItemOffset = 14, bsGenderRatioOffset = 16, + bsGrowthCurveOffset = 19, bsAbility1Offset = 22, bsAbility2Offset = 23, bsTMHMCompatOffset = 28; + + public static final String starterCriesPrefix = "0004000C10BD0000000000000000000000E000000000000000E0000000000200"; + + public static final byte[] hgssStarterCodeSuffix = { 0x03, 0x03, 0x1A, 0x12, 0x1, 0x23, 0x0, 0x0 }; + + public static final int[] hgssFilesWithRivalScript = { 7, 23, 96, 110, 819, 850, 866 }; + + public static final byte[] hgssRivalScriptMagic = { (byte) 0xCE, 0x00, 0x0C, (byte) 0x80, 0x11, 0x00, 0x0C, + (byte) 0x80, (byte) 152, 0, 0x1C, 0x00, 0x05 }; + + public static final int[] ptFilesWithRivalScript = { 31, 36, 112, 123, 186, 427, 429, 1096 }; + + public static final int[] dpFilesWithRivalScript = { 34, 90, 118, 180, 195, 394 }; + + public static final byte[] dpptRivalScriptMagic = { (byte) 0xDE, 0x00, 0x0C, (byte) 0x80, 0x11, 0x00, 0x0C, + (byte) 0x80, (byte) 0x83, 0x01, 0x1C, 0x00, 0x01 }; + + public static final byte[] dpptTagBattleScriptMagic1 = { (byte) 0xDE, 0x00, 0x0C, (byte) 0x80, 0x28, 0x00, 0x04, + (byte) 0x80 }; + + public static final byte[] dpptTagBattleScriptMagic2 = { 0x11, 0x00, 0x0C, (byte) 0x80, (byte) 0x86, 0x01, 0x1C, + 0x00, 0x01 }; + + public static final int[] ptFilesWithTagScript = { 2, 136, 201, 236 }; + + public static final int[] dpFilesWithTagScript = { 2, 131, 230 }; + + public static final int dpStarterStringIndex = 19, ptStarterStringIndex = 36; + + public static final int fossilCount = 7; + + public static final String dpptTMDataPrefix = "D100D200D300D400", hgssTMDataPrefix = "1E003200"; + + public static final int tmCount = 92, hmCount = 8; + + public static final int tmItemOffset = Items.tm01; + + private static final int dpptTextCharsPerLine = 38, hgssTextCharsPerLine = 36; + + public static final String dpItemPalettesPrefix = "8D018E01210132018D018F0122013301", + pthgssItemPalettesPrefix = "8D018E01210133018D018F0122013401"; + + public static final int ptSpearPillarPortalScriptFile = 237; + + public static final int evolutionMethodCount = 26; + + public static final int highestAbilityIndex = Abilities.badDreams; + + public static final List consumableHeldItems = Arrays.asList( + Items.cheriBerry, Items.chestoBerry, Items.pechaBerry, Items.rawstBerry, Items.aspearBerry, + Items.leppaBerry, Items.oranBerry, Items.persimBerry, Items.lumBerry, Items.sitrusBerry, Items.figyBerry, + Items.wikiBerry, Items.magoBerry, Items.aguavBerry, Items.iapapaBerry, Items.occaBerry, Items.passhoBerry, + Items.wacanBerry, Items.rindoBerry, Items.yacheBerry, Items.chopleBerry, Items.kebiaBerry, Items.shucaBerry, + Items.cobaBerry, Items.payapaBerry, Items.tangaBerry, Items.chartiBerry, Items.kasibBerry, Items.habanBerry, + Items.colburBerry, Items.babiriBerry, Items.chilanBerry, Items.liechiBerry, Items.ganlonBerry, + Items.salacBerry, Items.petayaBerry, Items.apicotBerry, Items.lansatBerry, Items.starfBerry, + Items.enigmaBerry, Items.micleBerry, Items.custapBerry, Items.jabocaBerry, Items.rowapBerry, + Items.berryJuice, Items.whiteHerb, Items.mentalHerb, Items.powerHerb, Items.focusSash); + + public static final List allHeldItems = setupAllHeldItems(); + + private static List setupAllHeldItems() { + List list = new ArrayList<>(); + list.addAll(Arrays.asList(Items.brightPowder, Items.quickClaw, Items.choiceBand, Items.kingsRock, + Items.silverPowder, Items.focusBand, Items.scopeLens, Items.metalCoat, Items.leftovers, Items.softSand, + Items.hardStone, Items.miracleSeed, Items.blackGlasses, Items.blackBelt, Items.magnet, + Items.mysticWater, Items.sharpBeak, Items.poisonBarb, Items.neverMeltIce, Items.spellTag, + Items.twistedSpoon, Items.charcoal, Items.dragonFang, Items.silkScarf, Items.shellBell, + Items.seaIncense, Items.laxIncense, Items.wideLens, Items.muscleBand, Items.wiseGlasses, + Items.expertBelt, Items.lightClay, Items.lifeOrb, Items.toxicOrb, Items.flameOrb, Items.zoomLens, + Items.metronome, Items.ironBall, Items.laggingTail, Items.destinyKnot, Items.blackSludge, Items.icyRock, + Items.smoothRock, Items.heatRock, Items.dampRock, Items.gripClaw, Items.choiceScarf, Items.stickyBarb, + Items.shedShell, Items.bigRoot, Items.choiceSpecs, Items.flamePlate, Items.splashPlate, Items.zapPlate, + Items.meadowPlate, Items.iciclePlate, Items.fistPlate, Items.toxicPlate, Items.earthPlate, + Items.skyPlate, Items.mindPlate, Items.insectPlate, Items.stonePlate, Items.spookyPlate, + Items.dracoPlate, Items.dreadPlate, Items.ironPlate, Items.oddIncense, Items.rockIncense, + Items.fullIncense, Items.waveIncense, Items.roseIncense, Items.razorClaw, Items.razorFang)); + list.addAll(consumableHeldItems); + return list; + } + + public static final List generalPurposeConsumableItems = Collections.unmodifiableList(Arrays.asList( + Items.cheriBerry, Items.chestoBerry, Items.pechaBerry, Items.rawstBerry, Items.aspearBerry, Items.leppaBerry, + Items.oranBerry, Items.persimBerry, Items.lumBerry, Items.sitrusBerry, Items.ganlonBerry, Items.salacBerry, + // An NPC pokemon's nature is generated randomly with IVs during gameplay. Therefore, we do not include + // the flavor berries because, prior to Gen 7, they aren't worth the risk. + Items.apicotBerry, Items.lansatBerry, Items.starfBerry, Items.enigmaBerry, Items.micleBerry, Items.custapBerry, + Items.jabocaBerry, Items.rowapBerry, Items.berryJuice, Items.whiteHerb, Items.mentalHerb, Items.focusSash)); + + public static final List generalPurposeItems = Collections.unmodifiableList(Arrays.asList( + Items.brightPowder, Items.quickClaw, Items.kingsRock, Items.focusBand, Items.scopeLens, Items.leftovers, + Items.shellBell, Items.laxIncense, Items.wideLens, Items.expertBelt, Items.lifeOrb, Items.zoomLens, + Items.destinyKnot, Items.shedShell, Items.razorClaw, Items.razorFang)); + + public static final Map> typeBoostingItems = initializeTypeBoostingItems(); + + private static Map> initializeTypeBoostingItems() { + Map> map = new HashMap<>(); + map.put(Type.BUG, Arrays.asList(Items.silverPowder, Items.insectPlate)); + map.put(Type.DARK, Arrays.asList(Items.blackGlasses, Items.dreadPlate)); + map.put(Type.DRAGON, Arrays.asList(Items.dragonFang, Items.dracoPlate)); + map.put(Type.ELECTRIC, Arrays.asList(Items.magnet, Items.zapPlate)); + map.put(Type.FIGHTING, Arrays.asList(Items.blackBelt, Items.fistPlate)); + map.put(Type.FIRE, Arrays.asList(Items.charcoal, Items.flamePlate)); + map.put(Type.FLYING, Arrays.asList(Items.sharpBeak, Items.skyPlate)); + map.put(Type.GHOST, Arrays.asList(Items.spellTag, Items.spookyPlate)); + map.put(Type.GRASS, Arrays.asList(Items.miracleSeed, Items.meadowPlate, Items.roseIncense)); + map.put(Type.GROUND, Arrays.asList(Items.softSand, Items.earthPlate)); + map.put(Type.ICE, Arrays.asList(Items.neverMeltIce, Items.iciclePlate)); + map.put(Type.NORMAL, Arrays.asList(Items.silkScarf)); + map.put(Type.POISON, Arrays.asList(Items.poisonBarb, Items.toxicPlate)); + map.put(Type.PSYCHIC, Arrays.asList(Items.twistedSpoon, Items.mindPlate, Items.oddIncense)); + map.put(Type.ROCK, Arrays.asList(Items.hardStone, Items.stonePlate, Items.rockIncense)); + map.put(Type.STEEL, Arrays.asList(Items.metalCoat, Items.ironPlate)); + map.put(Type.WATER, Arrays.asList(Items.mysticWater, Items.seaIncense, Items.splashPlate, Items.waveIncense)); + map.put(null, Collections.emptyList()); // ??? type + return Collections.unmodifiableMap(map); + } + + public static final Map> moveBoostingItems = initializeMoveBoostingItems(); + + private static Map> initializeMoveBoostingItems() { + Map> map = new HashMap<>(); + map.put(Moves.bounce, Arrays.asList(Items.powerHerb)); + map.put(Moves.dig, Arrays.asList(Items.powerHerb)); + map.put(Moves.dive, Arrays.asList(Items.powerHerb)); + map.put(Moves.fly, Arrays.asList(Items.powerHerb)); + map.put(Moves.razorWind, Arrays.asList(Items.powerHerb)); + map.put(Moves.skullBash, Arrays.asList(Items.powerHerb)); + map.put(Moves.skyAttack, Arrays.asList(Items.powerHerb)); + map.put(Moves.solarBeam, Arrays.asList(Items.powerHerb)); + + map.put(Moves.fling, Arrays.asList(Items.toxicOrb, Items.flameOrb, Items.ironBall)); + + map.put(Moves.trick, Arrays.asList(Items.toxicOrb, Items.flameOrb, Items.fullIncense, Items.laggingTail)); + map.put(Moves.switcheroo, Arrays.asList(Items.toxicOrb, Items.flameOrb, Items.fullIncense, Items.laggingTail)); + + map.put(Moves.trickRoom, Arrays.asList(Items.ironBall)); + + map.put(Moves.facade, Arrays.asList(Items.toxicOrb, Items.flameOrb)); + + map.put(Moves.psychoShift, Arrays.asList(Items.toxicOrb, Items.flameOrb)); + + map.put(Moves.lightScreen, Arrays.asList(Items.lightClay)); + map.put(Moves.reflect, Arrays.asList(Items.lightClay)); + + map.put(Moves.hail, Arrays.asList(Items.icyRock)); + + map.put(Moves.sandstorm, Arrays.asList(Items.smoothRock)); + + map.put(Moves.sunnyDay, Arrays.asList(Items.heatRock)); + + map.put(Moves.rainDance, Arrays.asList(Items.dampRock)); + + map.put(Moves.bind, Arrays.asList(Items.gripClaw)); + map.put(Moves.clamp, Arrays.asList(Items.gripClaw)); + map.put(Moves.fireSpin, Arrays.asList(Items.gripClaw)); + map.put(Moves.magmaStorm, Arrays.asList(Items.gripClaw)); + map.put(Moves.outrage, Arrays.asList(Items.gripClaw)); + map.put(Moves.sandTomb, Arrays.asList(Items.gripClaw)); + map.put(Moves.uproar, Arrays.asList(Items.gripClaw)); + map.put(Moves.whirlpool, Arrays.asList(Items.gripClaw)); + map.put(Moves.wrap, Arrays.asList(Items.gripClaw)); + + map.put(Moves.absorb, Arrays.asList(Items.bigRoot)); + map.put(Moves.aquaRing, Arrays.asList(Items.bigRoot)); + map.put(Moves.drainPunch, Arrays.asList(Items.bigRoot)); + map.put(Moves.dreamEater, Arrays.asList(Items.bigRoot)); + map.put(Moves.gigaDrain, Arrays.asList(Items.bigRoot)); + map.put(Moves.ingrain, Arrays.asList(Items.bigRoot)); + map.put(Moves.leechLife, Arrays.asList(Items.bigRoot)); + map.put(Moves.leechSeed, Arrays.asList(Items.bigRoot)); + map.put(Moves.megaDrain, Arrays.asList(Items.bigRoot)); + + return Collections.unmodifiableMap(map); + } + + public static final Map weaknessReducingBerries = initializeWeaknessReducingBerries(); + + private static Map initializeWeaknessReducingBerries() { + Map map = new HashMap<>(); + map.put(Type.FIRE, Items.occaBerry); + map.put(Type.WATER, Items.passhoBerry); + map.put(Type.ELECTRIC, Items.wacanBerry); + map.put(Type.GRASS, Items.rindoBerry); + map.put(Type.ICE, Items.yacheBerry); + map.put(Type.FIGHTING, Items.chopleBerry); + map.put(Type.POISON, Items.kebiaBerry); + map.put(Type.GROUND, Items.shucaBerry); + map.put(Type.FLYING, Items.cobaBerry); + map.put(Type.PSYCHIC, Items.payapaBerry); + map.put(Type.BUG, Items.tangaBerry); + map.put(Type.ROCK, Items.chartiBerry); + map.put(Type.GHOST, Items.kasibBerry); + map.put(Type.DRAGON, Items.habanBerry); + map.put(Type.DARK, Items.colburBerry); + map.put(Type.STEEL, Items.babiriBerry); + return Collections.unmodifiableMap(map); + } + + public static final Map> speciesBoostingItems = initializeSpeciesBoostingItems(); + + private static Map> initializeSpeciesBoostingItems() { + Map> map = new HashMap<>(); + map.put(Species.dialga, Arrays.asList(Items.adamantOrb)); + map.put(Species.palkia, Arrays.asList(Items.lustrousOrb)); + map.put(Species.latias, Arrays.asList(Items.soulDew)); + map.put(Species.latios, Arrays.asList(Items.soulDew)); + map.put(Species.clamperl, Arrays.asList(Items.deepSeaTooth, Items.deepSeaScale)); + map.put(Species.pikachu, Arrays.asList(Items.lightBall)); + map.put(Species.chansey, Arrays.asList(Items.luckyPunch)); + map.put(Species.ditto, Arrays.asList(Items.metalPowder, Items.quickPowder)); + map.put(Species.cubone, Arrays.asList(Items.thickClub)); + map.put(Species.marowak, Arrays.asList(Items.thickClub)); + map.put(Species.farfetchd, Arrays.asList(Items.leek)); + return Collections.unmodifiableMap(map); + } + + public static final Map> abilityBoostingItems = initializeAbilityBoostingItems(); + + private static Map> initializeAbilityBoostingItems() { + Map> map = new HashMap<>(); + map.put(Abilities.guts, Arrays.asList(Items.flameOrb, Items.toxicOrb)); + map.put(Abilities.magicGuard, Arrays.asList(Items.stickyBarb, Items.lifeOrb)); + return Collections.unmodifiableMap(map); + } + + public static final Map> abilityVariations = setupAbilityVariations(); + + private static Map> setupAbilityVariations() { + Map> map = new HashMap<>(); + map.put(Abilities.insomnia, Arrays.asList(Abilities.insomnia, Abilities.vitalSpirit)); + map.put(Abilities.clearBody, Arrays.asList(Abilities.clearBody, Abilities.whiteSmoke)); + map.put(Abilities.hugePower, Arrays.asList(Abilities.hugePower, Abilities.purePower)); + map.put(Abilities.battleArmor, Arrays.asList(Abilities.battleArmor, Abilities.shellArmor)); + map.put(Abilities.cloudNine, Arrays.asList(Abilities.cloudNine, Abilities.airLock)); + map.put(Abilities.filter, Arrays.asList(Abilities.filter, Abilities.solidRock)); + + return map; + } + + // Note: Flower Gift is NOT useless in this generation; it is in this list solely for consistency with future generations. + public static final List uselessAbilities = Arrays.asList(Abilities.forecast, Abilities.multitype, Abilities.flowerGift); + + public static final int dpptSetVarScript = 0x28, hgssSetVarScript = 0x29; + + public static final int scriptListTerminator = 0xFD13; + + public static final int itemScriptVariable = 0x8008; + + private static List dpShopNames = Arrays.asList( + "Sunyshore Secondary", + "Jubilife Secondary", + "Floaroma Secondary", + "Oreburgh Secondary", + "Eterna Secondary", + "Eterna Herbs", + "Snowpoint Secondary", + "Solaceon Secondary", + "Pastoria Secondary", + "Celestic Secondary", + "Hearthome Secondary", + "Canalave Secondary", + "Veilstone Department Store Secret Base Decorations 1", + "Veilstone Department Store Secret Base Decorations 2", + "Veilstone Department Store Vitamins", + "Veilstone Department Store TMs 1", + "Sunyshore Market Seals 1", + "Sunyshore Market Seals 2", + "Sunyshore Market Seals 3", + "Sunyshore Market Seals 4", + "Veilstone Department Store TMs 2", + "Sunyshore Market Seals 5", + "Sunyshore Market Seals 6", + "Sunyshore Market Seals 7", + "Pokemon League Secondary", + "Veilstone Department Store X Items", + "Veilstone Department Store Healing", + "Veilstone Department Store Balls Etc.", + "Progressive Shops" + ); + + private static List ptShopNames = Arrays.asList( + "Jubilife Secondary", + "Sunyshore Secondary", + "Floaroma Secondary", + "Oreburgh Secondary", + "Eterna Herbs", + "Canalave Secondary", + "Pastoria Secondary", + "Celestic Secondary", + "Snowpoint Secondary", + "Solaceon Secondary", + "Eterna Secondary", + "Hearthome Secondary", + "Veilstone Department Store B1 Berries", + "Veilstone Department Store Secret Base Decorations 1", + "Veilstone Department Store Vitamins", + "Veilstone Department Store Secret Base Decorations 2", + "Veilstone Department Store TMs 1", + "Sunyshore Market Seals 1", + "Sunyshore Market Seals 2", + "Sunyshore Market Seals 3", + "Sunyshore Market Seals 4", + "Veilstone Department Store TMs 2", + "Sunyshore Market Seals 5", + "Sunyshore Market Seals 6", + "Sunyshore Market Seals 7", + "Pokemon League Secondary", + "Veilstone Department Store X Items", + "Veilstone Department Store Healing", + "Veilstone Department Store Balls Etc.", + "Progressive Shops" + ); + + private static List hgssShopNames = Arrays.asList( + "Cherrygrove Secondary", + "Cerulean Secondary", + "Ecruteak Secondary", + "Celadon Department Store Mail", + "Saffron Secondary", + "Violet Secondary", + "Blackthorn Secondary", + "Olivine Secondary", + "Fuchsia Secondary", + "Lavender Secondary", + "Pewter Secondary", + "Viridian Secondary", + "Azalea Secondary", + "Mahogany Before Hideout", + "Safari Zone Gate Southwest", + "Goldenrod Herb Shop", + "Cianwood Pharmacy", + "Veilstone Department Store Secret Base Decorations 1", + "Veilstone Department Store Secret Base Decorations 2", + "Goldenrod Department Store Vitamins", + "Celadon Department Store Vitamins", + "Mt. Moon Square", + "Sunyshore Market Seals 1", + "Sunyshore Market Seals 2", + "Sunyshore Market Seals 3", + "Sunyshore Market Seals 4", + "Sunyshore Market Seals 5", + "Sunyshore Market Seals 6", + "Unused Secondary", + "Sunyshore Market Seals 7", + "Pokeathlon Dome Data Card Shop 25-27", + "Goldenrod Department Store X Items", + "Celadon Department Store X Items", + "Mahogany After Hideout", + "Goldenrod Department Store Healing", + "Celadon Department Store Healing", + "Goldenrod Department Store Balls Etc.", + "Goldenrod TMs", + "Celadon Department Store Balls Etc.", + "Celadon TMs", + "Pokeathlon Dome Athlete Shop Sunday (Pre-National Dex)", + "Pokeathlon Dome Data Card Shop 19-24", + "Pokeathlon Dome Data Card Shop 1-6", + "Pokeathlon Dome Athlete Shop Monday (Pre-National Dex)", + "Pokeathlon Dome Athlete Shop Tuesday (Pre-National Dex)", + "Pokeathlon Dome Data Card Shop 7-12", + "Pokeathlon Dome Athlete Shop Wednesday (Pre-National Dex)", + "Pokeathlon Dome Athlete Shop Thursday (Pre-National Dex)", + "Pokeathlon Dome Athlete Shop Friday (Pre-National Dex)", + "Pokeathlon Dome Athlete Shop Saturday (Pre-National Dex)", + "Pokeathlon Dome Data Card Shop 13-18", + "Pokeathlon Dome Athlete Shop Sunday (Post-National Dex)", + "Pokeathlon Dome Athlete Shop Monday (Post-National Dex)", + "Pokeathlon Dome Athlete Shop Tuesday (Post-National Dex)", + "Pokeathlon Dome Athlete Shop Wednesday (Post-National Dex)", + "Pokeathlon Dome Athlete Shop Thursday (Post-National Dex)", + "Pokeathlon Dome Athlete Shop Friday (Post-National Dex)", + "Pokeathlon Dome Athlete Shop Saturday (Post-National Dex)", + "Progressive Shops" + ); + + public static List getShopNames(int romType) { + if (romType == Type_DP) { + return dpShopNames; + } else if (romType == Type_Plat) { + return ptShopNames; + } else if (romType == Type_HGSS) { + return hgssShopNames; + } + return null; + } + + public static final List evolutionItems = Arrays.asList(Items.sunStone, Items.moonStone, Items.fireStone, + Items.thunderStone, Items.waterStone, Items.leafStone, Items.shinyStone, Items.duskStone, Items.dawnStone, + Items.ovalStone, Items.kingsRock, Items.deepSeaTooth, Items.deepSeaScale, Items.metalCoat, Items.dragonScale, + Items.upgrade, Items.protector, Items.electirizer, Items.magmarizer, Items.dubiousDisc, Items.reaperCloth, + Items.razorClaw, Items.razorFang); + + public static final Map formeSuffixes = setupFormeSuffixes(); + public static final Map formeMappings = setupFormeMappings(); + public static final Map cosmeticForms = setupCosmeticForms(); + + private static final Map> formeSuffixesByBaseForme = setupFormeSuffixesByBaseForme(); + private static final Map dummyFormeSuffixes = setupDummyFormeSuffixes(); + + private static final Map> absolutePokeNumsByBaseForme = setupAbsolutePokeNumsByBaseForme(); + private static final Map dummyAbsolutePokeNums = setupDummyAbsolutePokeNums(); + + public static String getFormeSuffixByBaseForme(int baseForme, int formNum) { + return formeSuffixesByBaseForme.getOrDefault(baseForme,dummyFormeSuffixes).getOrDefault(formNum,""); + } + + public static Integer getAbsolutePokeNumByBaseForme(int baseForme, int formNum) { + return absolutePokeNumsByBaseForme.getOrDefault(baseForme,dummyAbsolutePokeNums).getOrDefault(formNum,baseForme); + } + + public static final String lyraEthanMarillSpritePrefix = "274E0604C301274E0704E101274E0804"; + + public static final List hgssBigOverworldPokemon = Arrays.asList( + 536, // MMODEL_FOLLOWER_MON_STEELIX + 537, // MMODEL_FOLLOWER_MON_STEELIX_F + 579, // MMODEL_FOLLOWER_MON_LUGIA + 580, // MMODEL_FOLLOWER_MON_HO_OH + 651, // MMODEL_FOLLOWER_MON_WAILORD + 712, // MMODEL_FOLLOWER_MON_KYOGRE + 713, // MMODEL_FOLLOWER_MON_GROUDON + 714, // MMODEL_FOLLOWER_MON_RAYQUAZA + 833, // MMODEL_FOLLOWER_MON_DIALGA + 834, // MMODEL_FOLLOWER_MON_PALKIA + 836, // MMODEL_FOLLOWER_MON_REGIGIGAS + 837, // MMODEL_FOLLOWER_MON_GIRATINA + 838, // MMODEL_FOLLOWER_MON_GIRATINA_ORIGIN + 845, // MMODEL_FOLLOWER_MON_ARCEUS_NORMAL + 846, // MMODEL_FOLLOWER_MON_ARCEUS_FIGHTING + 847, // MMODEL_FOLLOWER_MON_ARCEUS_FLYING + 848, // MMODEL_FOLLOWER_MON_ARCEUS_POISON + 849, // MMODEL_FOLLOWER_MON_ARCEUS_GROUND + 850, // MMODEL_FOLLOWER_MON_ARCEUS_ROCK + 851, // MMODEL_FOLLOWER_MON_ARCEUS_BUG + 852, // MMODEL_FOLLOWER_MON_ARCEUS_GHOST + 853, // MMODEL_FOLLOWER_MON_ARCEUS_STEEL + 854, // MMODEL_FOLLOWER_MON_ARCEUS_MYSTERY + 855, // MMODEL_FOLLOWER_MON_ARCEUS_FIRE + 856, // MMODEL_FOLLOWER_MON_ARCEUS_WATER + 857, // MMODEL_FOLLOWER_MON_ARCEUS_GRASS + 858, // MMODEL_FOLLOWER_MON_ARCEUS_ELECTRIC + 859, // MMODEL_FOLLOWER_MON_ARCEUS_PSYCHIC + 860, // MMODEL_FOLLOWER_MON_ARCEUS_ICE + 861, // MMODEL_FOLLOWER_MON_ARCEUS_DRAGON + 862 // MMODEL_FOLLOWER_MON_ARCEUS_DARK + ); + + public static final List hgssBannedOverworldPokemon = Arrays.asList( + // Unown alts (to avoid 28x chance of getting Unown) + // Arcues alts (to avoid 18x chance of getting Arceus) + 502, // MMODEL_FOLLOWER_MON_UNOWN_B + 503, // MMODEL_FOLLOWER_MON_UNOWN_C + 504, // MMODEL_FOLLOWER_MON_UNOWN_D + 505, // MMODEL_FOLLOWER_MON_UNOWN_E + 506, // MMODEL_FOLLOWER_MON_UNOWN_F + 507, // MMODEL_FOLLOWER_MON_UNOWN_G + 508, // MMODEL_FOLLOWER_MON_UNOWN_H + 509, // MMODEL_FOLLOWER_MON_UNOWN_I + 510, // MMODEL_FOLLOWER_MON_UNOWN_J + 511, // MMODEL_FOLLOWER_MON_UNOWN_K + 512, // MMODEL_FOLLOWER_MON_UNOWN_L + 513, // MMODEL_FOLLOWER_MON_UNOWN_M + 514, // MMODEL_FOLLOWER_MON_UNOWN_N + 515, // MMODEL_FOLLOWER_MON_UNOWN_O + 516, // MMODEL_FOLLOWER_MON_UNOWN_P + 517, // MMODEL_FOLLOWER_MON_UNOWN_Q + 518, // MMODEL_FOLLOWER_MON_UNOWN_R + 519, // MMODEL_FOLLOWER_MON_UNOWN_S + 520, // MMODEL_FOLLOWER_MON_UNOWN_T + 521, // MMODEL_FOLLOWER_MON_UNOWN_U + 522, // MMODEL_FOLLOWER_MON_UNOWN_V + 523, // MMODEL_FOLLOWER_MON_UNOWN_W + 524, // MMODEL_FOLLOWER_MON_UNOWN_X + 525, // MMODEL_FOLLOWER_MON_UNOWN_Y + 526, // MMODEL_FOLLOWER_MON_UNOWN_Z + 527, // MMODEL_FOLLOWER_MON_UNOWN_QMARK + 528, // MMODEL_FOLLOWER_MON_UNOWN_EXCL + 846, // MMODEL_FOLLOWER_MON_ARCEUS_FIGHTING + 847, // MMODEL_FOLLOWER_MON_ARCEUS_FLYING + 848, // MMODEL_FOLLOWER_MON_ARCEUS_POISON + 849, // MMODEL_FOLLOWER_MON_ARCEUS_GROUND + 850, // MMODEL_FOLLOWER_MON_ARCEUS_ROCK + 851, // MMODEL_FOLLOWER_MON_ARCEUS_BUG + 852, // MMODEL_FOLLOWER_MON_ARCEUS_GHOST + 853, // MMODEL_FOLLOWER_MON_ARCEUS_STEEL + 854, // MMODEL_FOLLOWER_MON_ARCEUS_MYSTERY + 855, // MMODEL_FOLLOWER_MON_ARCEUS_FIRE + 856, // MMODEL_FOLLOWER_MON_ARCEUS_WATER + 857, // MMODEL_FOLLOWER_MON_ARCEUS_GRASS + 858, // MMODEL_FOLLOWER_MON_ARCEUS_ELECTRIC + 859, // MMODEL_FOLLOWER_MON_ARCEUS_PSYCHIC + 860, // MMODEL_FOLLOWER_MON_ARCEUS_ICE + 861, // MMODEL_FOLLOWER_MON_ARCEUS_DRAGON + 862 // MMODEL_FOLLOWER_MON_ARCEUS_DARK + ); + + public static final int convertOverworldSpriteToSpecies(int overworldSpriteID) { + int speciesID = overworldSpriteID - 296; + + // Venusaur + if (overworldSpriteID >= 300) { + speciesID -= 1; + } + + // Pikachu + if (overworldSpriteID >= 323) { + speciesID -= 1; + } + + // Meganium + if (overworldSpriteID >= 453) { + speciesID -= 1; + } + + // Pichu + if (overworldSpriteID >= 472) { + speciesID -= 1; + } + + // Unown + if (overworldSpriteID >= 528) { + speciesID -= 27; + } else if (overworldSpriteID > 501) { + speciesID -= (overworldSpriteID - 501); + } + + // Wobbuffet + if (overworldSpriteID >= 530) { + speciesID -= 1; + } + + // Steelix + if (overworldSpriteID >= 537) { + speciesID -= 1; + } + + // Heracross + if (overworldSpriteID >= 544) { + speciesID -= 1; + } + + // Deoxys + if (overworldSpriteID >= 719) { + speciesID -= 3; + } else if (overworldSpriteID > 716) { + speciesID -= (overworldSpriteID - 716); + } + + // Burmy + if (overworldSpriteID >= 747) { + speciesID -= 2; + } else if (overworldSpriteID > 745) { + speciesID -= (overworldSpriteID - 745); + } + + // Wormadam + if (overworldSpriteID >= 750) { + speciesID -= 2; + } else if (overworldSpriteID > 748) { + speciesID -= (overworldSpriteID - 748); + } + + // Combee + if (overworldSpriteID >= 753) { + speciesID -= 1; + } + + // Shellos + if (overworldSpriteID >= 761) { + speciesID -= 1; + } + + // Gastrodon + if (overworldSpriteID >= 763) { + speciesID -= 1; + } + + // Gible + if (overworldSpriteID >= 784) { + speciesID -= 1; + } + + // Gabite + if (overworldSpriteID >= 786) { + speciesID -= 1; + } + + // Garchomp + if (overworldSpriteID >= 788) { + speciesID -= 1; + } + + // Hippopotas + if (overworldSpriteID >= 793) { + speciesID -= 1; + } + + // Hippowdon + if (overworldSpriteID >= 795) { + speciesID -= 1; + } + + // Rotom + if (overworldSpriteID >= 829) { + speciesID -= 5; + } else if (overworldSpriteID > 824) { + speciesID -= (overworldSpriteID - 824); + } + + // Giratina + if (overworldSpriteID >= 838) { + speciesID -= 1; + } + + // Arceus + if (overworldSpriteID > 845) { + speciesID -= (overworldSpriteID - 845); + } + + return speciesID; + } + + // The original slot each of the 20 "alternate" slots is mapped to + // swarmx2, dayx2, nightx2, pokeradarx4, GBAx10 + // NOTE: in the game data there are 6 fillers between pokeradar and GBA + + public static final int[] dpptAlternateSlots = new int[] { 0, 1, 2, 3, 2, 3, 4, 5, 10, 11, 8, 9, 8, 9, 8, 9, 8, 9, + 8, 9 }; + + public static final String[] dpptWaterSlotSetNames = new String[] { "Surfing", "Filler", "Old Rod", "Good Rod", + "Super Rod" }; + + public static final String[] hgssTimeOfDayNames = new String[] { "Morning", "Day", "Night" }; + + public static final String[] hgssNonGrassSetNames = new String[] { "", "Surfing", "Rock Smash", "Old Rod", + "Good Rod", "Super Rod" }; + public static final int hgssGoodRodReplacementIndex = 3, hgssSuperRodReplacementIndex = 1; + + public static final MoveCategory[] moveCategoryIndices = { MoveCategory.PHYSICAL, MoveCategory.SPECIAL, + MoveCategory.STATUS }; + + public static byte moveCategoryToByte(MoveCategory cat) { + switch (cat) { + case PHYSICAL: + return 0; + case SPECIAL: + return 1; + case STATUS: + default: + return 2; + } + } + + public static final int noDamageSleepEffect = 1, damagePoisonEffect = 2, damageAbsorbEffect = 3, damageBurnEffect = 4, + damageFreezeEffect = 5, damageParalyzeEffect = 6, dreamEaterEffect = 8, noDamageAtkPlusOneEffect = 10, + noDamageDefPlusOneEffect = 11, noDamageSpAtkPlusOneEffect = 13, noDamageEvasionPlusOneEffect = 16, + noDamageAtkMinusOneEffect = 18, noDamageDefMinusOneEffect = 19, noDamageSpeMinusOneEffect = 20, + noDamageAccuracyMinusOneEffect = 23, noDamageEvasionMinusOneEffect = 24, flinchEffect = 31, toxicEffect = 33, + razorWindEffect = 39, bindingEffect = 42, increasedCritEffect = 43, damageRecoil25PercentEffect = 48, + noDamageConfusionEffect = 49, noDamageAtkPlusTwoEffect = 50, noDamageDefPlusTwoEffect = 51, + noDamageSpePlusTwoEffect = 52, noDamageSpAtkPlusTwoEffect = 53, noDamageSpDefPlusTwoEffect = 54, + noDamageAtkMinusTwoEffect = 58, noDamageDefMinusTwoEffect = 59, noDamageSpeMinusTwoEffect = 60, + noDamageSpDefMinusTwoEffect = 62, noDamagePoisonEffect = 66, noDamageParalyzeEffect = 67, + damageAtkMinusOneEffect = 68, damageDefMinusOneEffect = 69, damageSpeMinusOneEffect = 70, + damageSpAtkMinusOneEffect = 71, damageSpDefMinusOneEffect = 72, damageAccuracyMinusOneEffect = 73, + skyAttackEffect = 75, damageConfusionEffect = 76, twineedleEffect = 77, rechargeEffect = 80, snoreEffect = 92, + trappingEffect = 106, minimizeEffect = 108, swaggerEffect = 118, damageBurnAndThawUserEffect = 125, + damageUserDefPlusOneEffect = 138, damageUserAtkPlusOneEffect = 139, damageUserAllPlusOneEffect = 140, + skullBashEffect = 145, twisterEffect = 146, futureSightAndDoomDesireEffect = 148, stompEffect = 150, + solarbeamEffect = 151, thunderEffect = 152, flyEffect = 155, defenseCurlEffect = 156, + fakeOutEffect = 158, flatterEffect = 166, noDamageBurnEffect = 167, chargeEffect = 174, + damageUserAtkAndDefMinusOneEffect = 182, damageRecoil33PercentEffect = 198, teeterDanceEffect = 199, + blazeKickEffect = 200, poisonFangEffect = 202, damageUserSpAtkMinusTwoEffect = 204, + noDamageAtkAndDefMinusOneEffect = 205, noDamageDefAndSpDefPlusOneEffect = 206, + noDamageAtkAndDefPlusOneEffect = 208, damagePoisonWithIncreasedCritEffect = 209, + noDamageSpAtkAndSpDefPlusOneEffect = 211, noDamageAtkAndSpePlusOneEffect = 212, + damageUserSpeMinusOneEffect = 218, damageUserDefAndSpDefMinusOneEffect = 229, flareBlitzEffect = 253, + diveEffect = 255, digEffect = 256, blizzardEffect = 260, voltTackleEffect = 262, bounceEffect = 263, + noDamageSpAtkMinusTwoEffect = 265, chatterEffect = 267, damageRecoil50PercentEffect = 269, + damageSpDefMinusTwoEffect = 271, shadowForceEffect = 272, fireFangEffect = 273, iceFangEffect = 274, + thunderFangEffect = 275, damageUserSpAtkPlusOneEffect = 276; + + public static final List soundMoves = Arrays.asList(Moves.growl, Moves.roar, Moves.sing, Moves.supersonic, + Moves.screech, Moves.snore, Moves.uproar, Moves.metalSound, Moves.grassWhistle, Moves.hyperVoice, + Moves.bugBuzz, Moves.chatter, Moves.perishSong, Moves.healBell); + + public static final List punchMoves = Arrays.asList(Moves.icePunch, Moves.firePunch, Moves.thunderPunch, + Moves.machPunch, Moves.focusPunch, Moves.dizzyPunch, Moves.dynamicPunch, Moves.hammerArm, Moves.megaPunch, + Moves.cometPunch, Moves.meteorMash, Moves.shadowPunch, Moves.drainPunch, Moves.bulletPunch, Moves.skyUppercut); + + public static final List dpRequiredFieldTMs = Arrays.asList(2, 3, 5, 9, 12, 19, 23, 28, + 34, 39, 41, 43, 46, 47, 49, 50, 62, 69, 79, 80, 82, 84, 85, 87); + + public static final List ptRequiredFieldTMs = Arrays.asList(2, 3, 5, 7, 9, 11, 12, 18, 19, + 23, 28, 34, 37, 39, 41, 43, 46, 47, 49, 50, 62, 69, 79, 80, 82, 84, 85, 87); + + public static final List dpptFieldMoves = Arrays.asList( + Moves.cut, Moves.fly, Moves.surf, Moves.strength, Moves.flash, Moves.dig, Moves.teleport, + Moves.waterfall, Moves.rockSmash, Moves.sweetScent, Moves.defog, Moves.rockClimb); + + public static final List hgssFieldMoves = Arrays.asList( + Moves.cut, Moves.fly, Moves.surf, Moves.strength, Moves.flash, Moves.dig, Moves.teleport, + Moves.whirlpool, Moves.waterfall, Moves.rockSmash, Moves.headbutt, Moves.sweetScent, Moves.rockClimb); + + public static final List dpptEarlyRequiredHMMoves = Arrays.asList(Moves.rockSmash, Moves.cut); + + public static final List hgssEarlyRequiredHMMoves = Collections.singletonList(Moves.cut); + + public static ItemList allowedItems, nonBadItems; + public static List regularShopItems, opShopItems; + + public static final String shedinjaSpeciesLocator = "492080000090281C0521"; + + public static final int ilexForestScriptFile = 92, ilexForestStringsFile = 115; + public static final List headbuttTutorScriptOffsets = Arrays.asList(0xF55, 0xFC5, 0x100A, 0x104C); + + private static final String doubleBattleFixPrefixDP = "022912D90221214201", doubleBattleFixPrefixPt = "022919D90221214205", + doubleBattleFixPrefixHGSS = "2C2815D00221214201"; + + public static final String feebasLevelPrefixDPPt = "019813B0F0BD", honeyTreeLevelPrefixDPPt = "F0BDF0B589B0051C0C1C"; + + private static final String runningShoesCheckPrefixDPPt = "281C0C24", runningShoesCheckPrefixHGSS = "301C0C24"; + + public static final String distortionWorldGroundCheckPrefix = "23D849187944C988090409148F44"; + + public static final List dpptIntroPrefixes = Arrays.asList("381CF8BDC046", "08B0F8BD"); + + public static final String hpBarSpeedPrefix = "0CD106200090", expBarSpeedPrefix = "011C00D101212E6C", bothBarsSpeedPrefix = "70BD90421DDA"; + + public static final String dpptEggMoveTablePrefix = "40016601"; + + public static final String typeEffectivenessTableLocator = "000505000805"; + + private static final int trophyGardenGrassEncounterIndexDP = 304, trophyGardenGrassEncounterIndexPt = 308; + private static final List marshGrassEncounterIndicesDP = Arrays.asList(76, 82, 88, 94, 100, 102), + marshGrassEncounterIndicesPt = Arrays.asList(76, 82, 88, 94, 100, 106); + + public static final String pickupTableLocator = "110012001A000300", rarePickupTableLocator = "19005C00DD00"; + public static final int numberOfCommonPickupItems = 18, numberOfRarePickupItems = 11; + + public static final String friendshipValueForEvoLocator = "DC286AD3"; + + public static final String perfectOddsBranchLocator = "FF2901D30425"; + + public static final int[] dpptOverworldDexMaps = new int[] { + 1, 2, 3, 4, 5, -1, -1, 6, -1, 7, // 0-9 (cities, pkmn league, wind/ironworks) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-19 (all mt coronet) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-29 (mt coronet, great marsh, solaceon ruins) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 30-39 (all solaceon ruins) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 40-49 (solaceon ruins/v.road) + -1, -1, -1, -1, -1, -1, 8, -1, -1, -1, // 50-59 (v.road, stark mountain outer then inner, sendoff spring) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 60-69 (unknown, turnback cave) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 70-79 (all turnback cave) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80-89 (all unknown) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90-99 (all unknown) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 100-109 (unknown, snowpoint temple) + -1, -1, -1, -1, -1, -1, -1, -1, 9, -1, // 110-119 (various dungeons, iron island outer/inner) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 120-129 (rest of iron island inner, old chateau) + -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, // 130-139 (old chateau, inner lakes, lakefronts) + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, // 140-149 (first few routes) + 22, -1, -1, -1, -1, -1, 23, 24, 25, 26, // 150-159 (route 209 + lost tower, more routes) + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // 160-169 (routes; 220 is skipped until later) + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, // 170-179 (last few land routes, towns, resort area, first sea route) + 47, 48, 49, // 180-182 (other sea routes) + }; + + public static final int[] dpptDungeonDexMaps = new int[] { + -1, -1, -1, -1, -1, 1, 1, -1, 2, -1, // 0-9 (cities, pkmn league, wind/ironworks, mine/forest) + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 10-19 (all mt coronet) + 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, // 20-29 (mt coronet, great marsh, solaceon ruins) + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 30-39 (all solaceon ruins) + 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, // 40-49 (solaceon ruins/v.road) + 6, 6, 6, 7, 8, 8, -1, 9, 9, 10, // 50-59 (v.road, stark mountain outer then inner, sendoff spring) + -1, -1, -1, 10, 10, 10, 10, 10, 10, 10, // 60-69 (unknown, turnback cave) + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, // 70-79 (all turnback cave) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80-89 (all unknown) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90-99 (all unknown) + -1, -1, -1, -1, -1, -1, 11, 11, 11, 11, // 100-109 (unknown, snowpoint temple) + 11, 11, 12, 12, 13, 13, 13, 14, -1, 15, // 110-119 (various dungeons, iron island outer/inner) + 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, // 120-129 (rest of iron island inner, old chateau) + 16, 16, 16, 16, 17, 17, 18, 19, -1, -1, // 130-139 (old chateau, inner lakes, lakefronts) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 140-149 (first few routes) + -1, 20, 20, 20, 20, 20, -1, -1, -1, -1, // 150-159 (route 209 + lost tower, more routes) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160-169 (routes; 220 is skipped until later) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 170-179 (last few land routes, towns, resort area, first sea route) + -1, -1, -1, // 180-182 (other sea routes) + }; + + public static final int[] hgssOverworldDexMaps = new int[] { + 1, 2, 3, 4, 5, 6, -1, -1, 7, -1, // 0-9 (first few cities/routes, sprout tower + alph) + -1, -1, -1, -1, -1, -1, -1, 8, -1, -1, // 10-19 (more alph, union cave, r33, slowpoke) + -1, 9, 10, -1, -1, 11, 12, 13, -1, -1, // 20-29 (ilex, routes, natpark, routes, burned) + -1, -1, -1, -1, -1, -1, -1, -1, 14, 15, // 30-39 (bell tower, routes) + 16, 17, 18, -1, -1, -1, -1, -1, -1, -1, // 40-49 (olivine, routes, whirl islands, missing slots) + -1, 19, 20, -1, -1, -1, -1, 21, 22, 23, // 50-59 (missing, cianwood, routes, mortar) + -1, -1, -1, -1, -1, 24, -1, 25, 26, -1, // 60-69 (ice path, missing, blackthorn, dragons, routes, dark) + -1, 27, -1, -1, -1, -1, -1, -1, -1, -1, // 70-79 (dark, route 47, moon, seafoam, silver cave) + -1, -1, -1, -1, -1, 28, -1, -1, -1, -1, // 80-89 (more silver cave, cliff stuff, random bell tower) + -1, -1, 29, 30, 31, 32, 33, 34, 35, 36, // 90-99 (missing, saf zone, kanto routes/cities) + 37, 38, 39, 40, 41, 42, -1, -1, -1, -1, // 100-109 (more cities, some routes, more moon, RT) + -1, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 110-119 (vroad, routes 1-9) + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // 120-129 (routes 10-21) + 62, 63, -1, -1, -1, -1, 64, -1, -1, -1, // 130-139 (last 2 routes, tohjo, DC, VR, route 2 north, VF, CC) + -1, -1, // 140-141 (cerulean cave) + }; + + public static final int[] hgssDungeonDexMaps = new int[] { + -1, -1, -1, -1, -1, -1, 1, 1, -1, 2, // 0-9 (first few cities/routes, sprout tower + alph) + 2, 2, 2, 2, 3, 3, 3, -1, 4, 4, // 10-19 (more alph, union cave, r33, slowpoke) + 5, -1, -1, 6, -1, -1, -1, -1, 7, 7, // 20-29 (ilex, routes, natpark, routes, burned) + 8, 8, 8, 8, 8, 8, 8, 8, -1, -1, // 30-39 (bell tower, routes) + -1, -1, -1, 9, 9, -1, 9, -1, 9, -1, // 40-49 (olivine, routes, whirl islands, missing slots) + -1, -1, -1, 10, 10, 10, 10, -1, -1, -1, // 50-59 (missing, cianwood, routes, mortar) + 11, 11, 11, 11, -1, -1, 12, -1, -1, 13, // 60-69 (ice path, missing, blackthorn, dragons, routes, dark) + 13, -1, 14, 14, 15, 15, 15, 15, 15, 16, // 70-79 (dark, route 47, moon, seafoam, silver cave) + 16, 16, 17, 18, 8, -1, 16, 16, 16, 16, // 80-89 (more silver cave, cliff stuff, random bell tower) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90-99 (missing, saf zone, kanto routes/cities) + -1, -1, -1, -1, -1, -1, 14, 14, 20, 20, // 100-109 (more cities, some routes, more moon, RT) + 21, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 110-119 (vroad, routes 1-9) + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 120-129 (routes 10-21) + -1, -1, 22, 23, 21, 21, -1, 24, -1, 25, // 130-139 (last 2 routes, tohjo, DC, VR, route 2 north, VF, CC) + 25, 25, // 140-141 (cerulean cave) + }; + + public static final int[] hgssHeadbuttOverworldDexMaps = new int[] { + 43, 44, 45, 46, 47, 48, 49, 50, 53, 29, // Routes 1-12, skipping 9 and 10 + 54, 55, 56, 59, 61, 63, 40, 41, 42, 2, // Routes 13-15, Route 18, Route 22, Routes 25-29 + 4, 5, 7, 8, 9, 10, 11, 12, 14, 15, // Routes 30-39 + 20, 21, 23, 25, 26, 32, 33, 65, 34, 35, // Routes 42-46, first five Kanto cities + 36, 37, 1, 3, 6, 66, 13, 22, 28, 60, // Remaining Kanto cities, Johto cities, Lake of Rage, Mt Silver, Route 21 + -1, -1, -1, 27, 39, 67, 64, 57, -1, // National Park, Ilex/Viridian Forest, Routes 47-48, Safari Zone Gate, Routes 2 (north) and 16, Mt Silver Cave + }; + + public static final int[] hgssHeadbuttDungeonDexMaps = new int[] { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Routes 1-12, skipping 9 and 10 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Routes 13-15, Route 18, Route 22, Routes 25-29 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Routes 30-39 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Routes 42-46, first five Kanto cities + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Remaining Kanto cities, Johto cities, Lake of Rage, Mt Silver, Route 21 + 6, 5, 24, -1, -1, -1, -1, -1, 16, // National Park, Ilex/Viridian Forest, Routes 47-48, Safari Zone Gate, Routes 2 (north) and 16, Mt Silver Cave + }; + + public static final int pokedexAreaDataSize = 495; + public static final int dpptMtCoronetDexIndex = 3, dpptGreatMarshDexIndex = 4, dpptTrophyGardenDexIndex = 14, dpptFloaromaMeadowDexIndex = 21; + public static final List dpptOverworldHoneyTreeDexIndicies = Arrays.asList(6, 7, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 34, 36, 37, 50); + public static final List partnerTrainerIndices = Arrays.asList(608, 609, 610, 611, 612); + + static { + setupAllowedItems(); + } + + private static void setupAllowedItems() { + allowedItems = new ItemList(Items.enigmaStone); + // Key items + version exclusives + allowedItems.banRange(Items.explorerKit, 109); + // Unknown blank items or version exclusives + allowedItems.banRange(Items.griseousOrb, 23); + // HMs + allowedItems.banRange(Items.hm01, 8); + // TMs + allowedItems.tmRange(Items.tm01, 92); + + // non-bad items + // ban specific pokemon hold items, berries, apricorns, mail + nonBadItems = allowedItems.copy(); + + nonBadItems.banSingles(Items.oddKeystone, Items.griseousOrb, Items.soulDew, Items.lightBall, + Items.oranBerry, Items.quickPowder); + nonBadItems.banRange(Items.shoalSalt,2); + nonBadItems.banRange(Items.growthMulch, 4); // mulch + nonBadItems.banRange(Items.adamantOrb, 2); // orbs + nonBadItems.banRange(Items.mail1, 12); // mails + nonBadItems.banRange(Items.figyBerry, 25); // berries without useful battle effects + nonBadItems.banRange(Items.luckyPunch, 4); // pokemon specific + nonBadItems.banRange(Items.redScarf, 5); // contest scarves + + regularShopItems = new ArrayList<>(); + + regularShopItems.addAll(IntStream.rangeClosed(Items.ultraBall, Items.pokeBall).boxed().collect(Collectors.toList())); + regularShopItems.addAll(IntStream.rangeClosed(Items.potion, Items.revive).boxed().collect(Collectors.toList())); + regularShopItems.addAll(IntStream.rangeClosed(Items.superRepel, Items.repel).boxed().collect(Collectors.toList())); + + opShopItems = new ArrayList<>(); + + // "Money items" etc + opShopItems.add(Items.rareCandy); + opShopItems.addAll(IntStream.rangeClosed(Items.tinyMushroom, Items.nugget).boxed().collect(Collectors.toList())); + opShopItems.add(Items.rareBone); + opShopItems.add(Items.luckyEgg); + } + + public static String getDoubleBattleFixPrefix(int romType) { + if (romType == Gen4Constants.Type_DP) { + return doubleBattleFixPrefixDP; + } else if (romType == Gen4Constants.Type_Plat) { + return doubleBattleFixPrefixPt; + } else { + return doubleBattleFixPrefixHGSS; + } + } + + public static String getRunWithoutRunningShoesPrefix(int romType) { + if (romType == Gen4Constants.Type_DP || romType == Gen4Constants.Type_Plat) { + return runningShoesCheckPrefixDPPt; + } else { + return runningShoesCheckPrefixHGSS; + } + } + + public static int getTrophyGardenGrassEncounterIndex(int romType) { + if (romType == Gen4Constants.Type_DP) { + return trophyGardenGrassEncounterIndexDP; + } else { + return trophyGardenGrassEncounterIndexPt; + } + } + + public static List getMarshGrassEncounterIndices(int romType) { + if (romType == Gen4Constants.Type_DP) { + return marshGrassEncounterIndicesDP; + } else { + return marshGrassEncounterIndicesPt; + } + } + + public static int getTextCharsPerLine(int romType) { + if (romType == Gen4Constants.Type_HGSS) { + return hgssTextCharsPerLine; + } else { + return dpptTextCharsPerLine; + } + } + + public static final Map balancedItemPrices = Stream.of(new Integer[][] { + // Skip item index 0. All prices divided by 10 + {Items.masterBall, 300}, + {Items.ultraBall, 120}, + {Items.greatBall, 60}, + {Items.pokeBall, 20}, + {Items.safariBall, 50}, + {Items.netBall, 100}, + {Items.diveBall, 100}, + {Items.nestBall, 100}, + {Items.repeatBall, 100}, + {Items.timerBall, 100}, + {Items.luxuryBall, 100}, + {Items.premierBall, 20}, + {Items.duskBall, 100}, + {Items.healBall, 30}, + {Items.quickBall, 100}, + {Items.cherishBall, 20}, + {Items.potion, 30}, + {Items.antidote, 10}, + {Items.burnHeal, 25}, + {Items.iceHeal, 25}, + {Items.awakening, 25}, + {Items.paralyzeHeal, 20}, + {Items.fullRestore, 300}, + {Items.maxPotion, 250}, + {Items.hyperPotion, 120}, + {Items.superPotion, 70}, + {Items.fullHeal, 60}, + {Items.revive, 150}, + {Items.maxRevive, 400}, + {Items.freshWater, 40}, + {Items.sodaPop, 60}, + {Items.lemonade, 70}, + {Items.moomooMilk, 80}, + {Items.energyPowder, 40}, + {Items.energyRoot, 110}, + {Items.healPowder, 45}, + {Items.revivalHerb, 280}, + {Items.ether, 300}, + {Items.maxEther, 450}, + {Items.elixir, 1500}, + {Items.maxElixir, 1800}, + {Items.lavaCookie, 45}, + {Items.berryJuice, 10}, + {Items.sacredAsh, 1000}, + {Items.hpUp, 980}, + {Items.protein, 980}, + {Items.iron, 980}, + {Items.carbos, 980}, + {Items.calcium, 980}, + {Items.rareCandy, 1000}, + {Items.ppUp, 980}, + {Items.zinc, 980}, + {Items.ppMax, 2490}, + {Items.oldGateau, 45}, + {Items.guardSpec, 70}, + {Items.direHit, 65}, + {Items.xAttack, 50}, + {Items.xDefense, 55}, + {Items.xSpeed, 35}, + {Items.xAccuracy, 95}, + {Items.xSpAtk, 35}, + {Items.xSpDef, 35}, + {Items.pokeDoll, 100}, + {Items.fluffyTail, 100}, + {Items.blueFlute, 2}, + {Items.yellowFlute, 2}, + {Items.redFlute, 2}, + {Items.blackFlute, 2}, + {Items.whiteFlute, 2}, + {Items.shoalSalt, 2}, + {Items.shoalShell, 2}, + {Items.redShard, 40}, + {Items.blueShard, 40}, + {Items.yellowShard, 40}, + {Items.greenShard, 40}, + {Items.superRepel, 50}, + {Items.maxRepel, 70}, + {Items.escapeRope, 55}, + {Items.repel, 35}, + {Items.sunStone, 300}, + {Items.moonStone, 300}, + {Items.fireStone, 300}, + {Items.thunderStone, 300}, + {Items.waterStone, 300}, + {Items.leafStone, 300}, + {Items.tinyMushroom, 50}, + {Items.bigMushroom, 500}, + {Items.pearl, 140}, + {Items.bigPearl, 750}, + {Items.stardust, 200}, + {Items.starPiece, 980}, + {Items.nugget, 1000}, + {Items.heartScale, 500}, + {Items.honey, 50}, + {Items.growthMulch, 20}, + {Items.dampMulch, 20}, + {Items.stableMulch, 20}, + {Items.gooeyMulch, 20}, + {Items.rootFossil, 500}, + {Items.clawFossil, 500}, + {Items.helixFossil, 500}, + {Items.domeFossil, 500}, + {Items.oldAmber, 800}, + {Items.armorFossil, 500}, + {Items.skullFossil, 500}, + {Items.rareBone, 1000}, + {Items.shinyStone, 300}, + {Items.duskStone, 300}, + {Items.dawnStone, 300}, + {Items.ovalStone, 300}, + {Items.oddKeystone, 210}, + {Items.griseousOrb, 1000}, + {Items.tea, 0}, // unused in Gen 4 + {Items.unused114, 0}, + {Items.autograph, 0}, // unused in Gen 4 + {Items.douseDrive, 0}, // unused in Gen 4 + {Items.shockDrive, 0}, // unused in Gen 4 + {Items.burnDrive, 0}, // unused in Gen 4 + {Items.chillDrive, 0}, // unused in Gen 4 + {Items.unused120, 0}, // unused in Gen 4 + {Items.pokemonBox, 0}, // unused in Gen 4 + {Items.medicinePocket, 0}, // unused in Gen 4 + {Items.tmCase, 0}, // unused in Gen 4 + {Items.candyJar, 0}, // unused in Gen 4 + {Items.powerUpPocket, 0}, // unused in Gen 4 + {Items.clothingTrunk, 0}, // unused in Gen 4 + {Items.catchingPocket, 0}, // unused in Gen 4 + {Items.battlePocket, 0}, // unused in Gen 4 + {Items.unused129, 0}, + {Items.unused130, 0}, + {Items.unused131, 0}, + {Items.unused132, 0}, + {Items.unused133, 0}, + {Items.sweetHeart, 0}, // unused in Gen 4 + {Items.adamantOrb, 1000}, + {Items.lustrousOrb, 1000}, + {Items.mail1, 5}, + {Items.mail2, 5}, + {Items.mail3, 5}, + {Items.mail4, 5}, + {Items.mail5, 5}, + {Items.mail6, 5}, + {Items.mail7, 5}, + {Items.mail8, 5}, + {Items.mail9, 5}, + {Items.mail10, 5}, + {Items.mail11, 5}, + {Items.mail12, 5}, + {Items.cheriBerry, 20}, + {Items.chestoBerry, 25}, + {Items.pechaBerry, 10}, + {Items.rawstBerry, 25}, + {Items.aspearBerry, 25}, + {Items.leppaBerry, 300}, + {Items.oranBerry, 5}, + {Items.persimBerry, 20}, + {Items.lumBerry, 50}, + {Items.sitrusBerry, 50}, + {Items.figyBerry, 10}, + {Items.wikiBerry, 10}, + {Items.magoBerry, 10}, + {Items.aguavBerry, 10}, + {Items.iapapaBerry, 10}, + {Items.razzBerry, 50}, + {Items.blukBerry, 50}, + {Items.nanabBerry, 50}, + {Items.wepearBerry, 50}, + {Items.pinapBerry, 50}, + {Items.pomegBerry, 50}, + {Items.kelpsyBerry, 50}, + {Items.qualotBerry, 50}, + {Items.hondewBerry, 50}, + {Items.grepaBerry, 50}, + {Items.tamatoBerry, 50}, + {Items.cornnBerry, 50}, + {Items.magostBerry, 50}, + {Items.rabutaBerry, 50}, + {Items.nomelBerry, 50}, + {Items.spelonBerry, 50}, + {Items.pamtreBerry, 50}, + {Items.watmelBerry, 50}, + {Items.durinBerry, 50}, + {Items.belueBerry, 50}, + {Items.occaBerry, 100}, + {Items.passhoBerry, 100}, + {Items.wacanBerry, 100}, + {Items.rindoBerry, 100}, + {Items.yacheBerry, 100}, + {Items.chopleBerry, 100}, + {Items.kebiaBerry, 100}, + {Items.shucaBerry, 100}, + {Items.cobaBerry, 100}, + {Items.payapaBerry, 100}, + {Items.tangaBerry, 100}, + {Items.chartiBerry, 100}, + {Items.kasibBerry, 100}, + {Items.habanBerry, 100}, + {Items.colburBerry, 100}, + {Items.babiriBerry, 100}, + {Items.chilanBerry, 100}, + {Items.liechiBerry, 100}, + {Items.ganlonBerry, 100}, + {Items.salacBerry, 100}, + {Items.petayaBerry, 100}, + {Items.apicotBerry, 100}, + {Items.lansatBerry, 100}, + {Items.starfBerry, 100}, + {Items.enigmaBerry, 100}, + {Items.micleBerry, 100}, + {Items.custapBerry, 100}, + {Items.jabocaBerry, 100}, + {Items.rowapBerry, 100}, + {Items.brightPowder, 300}, + {Items.whiteHerb, 100}, + {Items.machoBrace, 300}, + {Items.expShare, 600}, + {Items.quickClaw, 450}, + {Items.sootheBell, 100}, + {Items.mentalHerb, 100}, + {Items.choiceBand, 1000}, + {Items.kingsRock, 500}, + {Items.silverPowder, 200}, + {Items.amuletCoin, 1500}, + {Items.cleanseTag, 100}, + {Items.soulDew, 20}, + {Items.deepSeaTooth, 300}, + {Items.deepSeaScale, 300}, + {Items.smokeBall, 20}, + {Items.everstone, 20}, + {Items.focusBand, 300}, + {Items.luckyEgg, 1000}, + {Items.scopeLens, 500}, + {Items.metalCoat, 300}, + {Items.leftovers, 1000}, + {Items.dragonScale, 300}, + {Items.lightBall, 10}, + {Items.softSand, 200}, + {Items.hardStone, 200}, + {Items.miracleSeed, 200}, + {Items.blackGlasses, 200}, + {Items.blackBelt, 200}, + {Items.magnet, 200}, + {Items.mysticWater, 200}, + {Items.sharpBeak, 200}, + {Items.poisonBarb, 200}, + {Items.neverMeltIce, 200}, + {Items.spellTag, 200}, + {Items.twistedSpoon, 200}, + {Items.charcoal, 200}, + {Items.dragonFang, 200}, + {Items.silkScarf, 200}, + {Items.upgrade, 300}, + {Items.shellBell, 600}, + {Items.seaIncense, 200}, + {Items.laxIncense, 300}, + {Items.luckyPunch, 1}, + {Items.metalPowder, 1}, + {Items.thickClub, 50}, + {Items.leek, 20}, + {Items.redScarf, 10}, + {Items.blueScarf, 10}, + {Items.pinkScarf, 10}, + {Items.greenScarf, 10}, + {Items.yellowScarf, 10}, + {Items.wideLens, 150}, + {Items.muscleBand, 200}, + {Items.wiseGlasses, 200}, + {Items.expertBelt, 600}, + {Items.lightClay, 150}, + {Items.lifeOrb, 1000}, + {Items.powerHerb, 100}, + {Items.toxicOrb, 150}, + {Items.flameOrb, 150}, + {Items.quickPowder, 1}, + {Items.focusSash, 200}, + {Items.zoomLens, 150}, + {Items.metronome, 300}, + {Items.ironBall, 100}, + {Items.laggingTail, 100}, + {Items.destinyKnot, 150}, + {Items.blackSludge, 500}, + {Items.icyRock, 20}, + {Items.smoothRock, 20}, + {Items.heatRock, 20}, + {Items.dampRock, 20}, + {Items.gripClaw, 150}, + {Items.choiceScarf, 1000}, + {Items.stickyBarb, 150}, + {Items.powerBracer, 300}, + {Items.powerBelt, 300}, + {Items.powerLens, 300}, + {Items.powerBand, 300}, + {Items.powerAnklet, 300}, + {Items.powerWeight, 300}, + {Items.shedShell, 50}, + {Items.bigRoot, 150}, + {Items.choiceSpecs, 1000}, + {Items.flamePlate, 200}, + {Items.splashPlate, 200}, + {Items.zapPlate, 200}, + {Items.meadowPlate, 200}, + {Items.iciclePlate, 200}, + {Items.fistPlate, 200}, + {Items.toxicPlate, 200}, + {Items.earthPlate, 200}, + {Items.skyPlate, 200}, + {Items.mindPlate, 200}, + {Items.insectPlate, 200}, + {Items.stonePlate, 200}, + {Items.spookyPlate, 200}, + {Items.dracoPlate, 200}, + {Items.dreadPlate, 200}, + {Items.ironPlate, 200}, + {Items.oddIncense, 200}, + {Items.rockIncense, 200}, + {Items.fullIncense, 100}, + {Items.waveIncense, 200}, + {Items.roseIncense, 200}, + {Items.luckIncense, 1500}, + {Items.pureIncense, 100}, + {Items.protector, 300}, + {Items.electirizer, 300}, + {Items.magmarizer, 300}, + {Items.dubiousDisc, 300}, + {Items.reaperCloth, 300}, + {Items.razorClaw, 500}, + {Items.razorFang, 500}, + {Items.tm01, 300}, + {Items.tm02, 300}, + {Items.tm03, 300}, + {Items.tm04, 150}, + {Items.tm05, 100}, + {Items.tm06, 300}, + {Items.tm07, 200}, + {Items.tm08, 150}, + {Items.tm09, 200}, + {Items.tm10, 200}, + {Items.tm11, 200}, + {Items.tm12, 150}, + {Items.tm13, 300}, + {Items.tm14, 550}, + {Items.tm15, 750}, + {Items.tm16, 200}, + {Items.tm17, 200}, + {Items.tm18, 200}, + {Items.tm19, 300}, + {Items.tm20, 200}, + {Items.tm21, 100}, + {Items.tm22, 300}, + {Items.tm23, 300}, + {Items.tm24, 300}, + {Items.tm25, 550}, + {Items.tm26, 300}, + {Items.tm27, 100}, + {Items.tm28, 200}, + {Items.tm29, 300}, + {Items.tm30, 300}, + {Items.tm31, 300}, + {Items.tm32, 100}, + {Items.tm33, 200}, + {Items.tm34, 300}, + {Items.tm35, 300}, + {Items.tm36, 300}, + {Items.tm37, 200}, + {Items.tm38, 550}, + {Items.tm39, 200}, + {Items.tm40, 300}, + {Items.tm41, 150}, + {Items.tm42, 300}, + {Items.tm43, 200}, + {Items.tm44, 300}, + {Items.tm45, 300}, + {Items.tm46, 200}, + {Items.tm47, 300}, + {Items.tm48, 300}, + {Items.tm49, 150}, + {Items.tm50, 550}, + {Items.tm51, 200}, + {Items.tm52, 550}, + {Items.tm53, 300}, + {Items.tm54, 200}, + {Items.tm55, 300}, + {Items.tm56, 200}, + {Items.tm57, 300}, + {Items.tm58, 200}, + {Items.tm59, 300}, + {Items.tm60, 300}, + {Items.tm61, 200}, + {Items.tm62, 300}, + {Items.tm63, 200}, + {Items.tm64, 750}, + {Items.tm65, 300}, + {Items.tm66, 300}, + {Items.tm67, 100}, + {Items.tm68, 750}, + {Items.tm69, 150}, + {Items.tm70, 100}, + {Items.tm71, 300}, + {Items.tm72, 300}, + {Items.tm73, 200}, + {Items.tm74, 300}, + {Items.tm75, 150}, + {Items.tm76, 200}, + {Items.tm77, 150}, + {Items.tm78, 150}, + {Items.tm79, 300}, + {Items.tm80, 300}, + {Items.tm81, 300}, + {Items.tm82, 100}, + {Items.tm83, 200}, + {Items.tm84, 300}, + {Items.tm85, 300}, + {Items.tm86, 300}, + {Items.tm87, 150}, + {Items.tm88, 300}, + {Items.tm89, 300}, + {Items.tm90, 200}, + {Items.tm91, 300}, + {Items.tm92, 550}, + {Items.hm01, 0}, + {Items.hm02, 0}, + {Items.hm03, 0}, + {Items.hm04, 0}, + {Items.hm05, 0}, + {Items.hm06, 0}, + {Items.hm07, 0}, + {Items.hm08, 0}, + {Items.explorerKit, 0}, + {Items.lootSack, 0}, + {Items.ruleBook, 0}, + {Items.pokeRadar, 0}, + {Items.pointCard, 0}, + {Items.journal, 0}, + {Items.sealCase, 0}, + {Items.fashionCase, 0}, + {Items.sealBag, 0}, + {Items.palPad, 0}, + {Items.worksKey, 0}, + {Items.oldCharm, 0}, + {Items.galacticKey, 0}, + {Items.redChain, 0}, + {Items.townMap, 0}, + {Items.vsSeeker, 0}, + {Items.coinCase, 0}, + {Items.oldRod, 0}, + {Items.goodRod, 0}, + {Items.superRod, 0}, + {Items.sprayduck, 0}, + {Items.poffinCase, 0}, + {Items.bike, 0}, + {Items.suiteKey, 0}, + {Items.oaksLetter, 0}, + {Items.lunarWing, 0}, + {Items.memberCard, 0}, + {Items.azureFlute, 0}, + {Items.ssTicketJohto, 0}, + {Items.contestPass, 0}, + {Items.magmaStone, 0}, + {Items.parcelSinnoh, 0}, + {Items.coupon1, 0}, + {Items.coupon2, 0}, + {Items.coupon3, 0}, + {Items.storageKeySinnoh, 0}, + {Items.secretPotion, 0}, + {Items.vsRecorder, 0}, + {Items.gracidea, 0}, + {Items.secretKeySinnoh, 0}, + {Items.apricornBox, 0}, + {Items.unownReport, 0}, + {Items.berryPots, 0}, + {Items.dowsingMachine, 0}, + {Items.blueCard, 0}, + {Items.slowpokeTail, 0}, + {Items.clearBell, 0}, + {Items.cardKeyJohto, 0}, + {Items.basementKeyJohto, 0}, + {Items.squirtBottle, 0}, + {Items.redScale, 0}, + {Items.lostItem, 0}, + {Items.pass, 0}, + {Items.machinePart, 0}, + {Items.silverWing, 0}, + {Items.rainbowWing, 0}, + {Items.mysteryEgg, 0}, + {Items.redApricorn, 2}, + {Items.blueApricorn, 2}, + {Items.yellowApricorn, 2}, + {Items.greenApricorn, 2}, + {Items.pinkApricorn, 2}, + {Items.whiteApricorn, 2}, + {Items.blackApricorn, 2}, + {Items.fastBall, 30}, + {Items.levelBall, 30}, + {Items.lureBall, 30}, + {Items.heavyBall, 30}, + {Items.loveBall, 30}, + {Items.friendBall, 30}, + {Items.moonBall, 30}, + {Items.sportBall, 30}, + {Items.parkBall, 0}, + {Items.photoAlbum, 0}, + {Items.gbSounds, 0}, + {Items.tidalBell, 0}, + {Items.rageCandyBar, 0}, + {Items.dataCard01, 0}, + {Items.dataCard02, 0}, + {Items.dataCard03, 0}, + {Items.dataCard04, 0}, + {Items.dataCard05, 0}, + {Items.dataCard06, 0}, + {Items.dataCard07, 0}, + {Items.dataCard08, 0}, + {Items.dataCard09, 0}, + {Items.dataCard10, 0}, + {Items.dataCard11, 0}, + {Items.dataCard12, 0}, + {Items.dataCard13, 0}, + {Items.dataCard14, 0}, + {Items.dataCard15, 0}, + {Items.dataCard16, 0}, + {Items.dataCard17, 0}, + {Items.dataCard18, 0}, + {Items.dataCard19, 0}, + {Items.dataCard20, 0}, + {Items.dataCard21, 0}, + {Items.dataCard22, 0}, + {Items.dataCard23, 0}, + {Items.dataCard24, 0}, + {Items.dataCard25, 0}, + {Items.dataCard26, 0}, + {Items.dataCard27, 0}, + {Items.jadeOrb, 0}, + {Items.lockCapsule, 0}, + {Items.redOrb, 0}, + {Items.blueOrb, 0}, + {Items.enigmaStone, 0}, + }).collect(Collectors.toMap(kv -> kv[0], kv -> kv[1])); + + public static final Type[] typeTable = constructTypeTable(); + + private static Type[] constructTypeTable() { + Type[] table = new Type[256]; + table[0x00] = Type.NORMAL; + table[0x01] = Type.FIGHTING; + table[0x02] = Type.FLYING; + table[0x03] = Type.POISON; + table[0x04] = Type.GROUND; + table[0x05] = Type.ROCK; + table[0x06] = Type.BUG; + table[0x07] = Type.GHOST; + table[0x08] = Type.STEEL; + table[0x0A] = Type.FIRE; + table[0x0B] = Type.WATER; + table[0x0C] = Type.GRASS; + table[0x0D] = Type.ELECTRIC; + table[0x0E] = Type.PSYCHIC; + table[0x0F] = Type.ICE; + table[0x10] = Type.DRAGON; + table[0x11] = Type.DARK; + return table; + } + + public static byte typeToByte(Type type) { + if (type == null) { + return 0x09; // ???-type + } + switch (type) { + case NORMAL: + return 0x00; + case FIGHTING: + return 0x01; + case FLYING: + return 0x02; + case POISON: + return 0x03; + case GROUND: + return 0x04; + case ROCK: + return 0x05; + case BUG: + return 0x06; + case GHOST: + return 0x07; + case FIRE: + return 0x0A; + case WATER: + return 0x0B; + case GRASS: + return 0x0C; + case ELECTRIC: + return 0x0D; + case PSYCHIC: + return 0x0E; + case ICE: + return 0x0F; + case DRAGON: + return 0x10; + case STEEL: + return 0x08; + case DARK: + return 0x11; + default: + return 0; // normal by default + } + } + + public static int getFormeCount(int romType) { + if (romType == Type_DP) { + return dpFormeCount; + } else if (romType == Type_Plat || romType == Type_HGSS) { + return platHgSsFormeCount; + } + return 0; + } + + private static Map setupFormeSuffixes() { + Map formeSuffixes = new HashMap<>(); + formeSuffixes.put(Species.Gen4Formes.deoxysA + formeOffset,"-A"); + formeSuffixes.put(Species.Gen4Formes.deoxysD + formeOffset,"-D"); + formeSuffixes.put(Species.Gen4Formes.deoxysS + formeOffset,"-S"); + formeSuffixes.put(Species.Gen4Formes.wormadamS + formeOffset,"-S"); + formeSuffixes.put(Species.Gen4Formes.wormadamT + formeOffset,"-T"); + formeSuffixes.put(Species.Gen4Formes.giratinaO + formeOffset,"-O"); + formeSuffixes.put(Species.Gen4Formes.shayminS + formeOffset,"-S"); + formeSuffixes.put(Species.Gen4Formes.rotomH + formeOffset,"-H"); + formeSuffixes.put(Species.Gen4Formes.rotomW + formeOffset,"-W"); + formeSuffixes.put(Species.Gen4Formes.rotomFr + formeOffset,"-Fr"); + formeSuffixes.put(Species.Gen4Formes.rotomFa + formeOffset,"-Fa"); + formeSuffixes.put(Species.Gen4Formes.rotomM + formeOffset,"-M"); + return formeSuffixes; + } + + private static Map setupFormeMappings() { + Map formeMappings = new TreeMap<>(); + + formeMappings.put(Species.Gen4Formes.deoxysA + formeOffset,new FormeInfo(Species.deoxys,1, 0)); + formeMappings.put(Species.Gen4Formes.deoxysD + formeOffset,new FormeInfo(Species.deoxys,2, 0)); + formeMappings.put(Species.Gen4Formes.deoxysS + formeOffset,new FormeInfo(Species.deoxys,3, 0)); + formeMappings.put(Species.Gen4Formes.wormadamS + formeOffset,new FormeInfo(Species.wormadam,1, 0)); + formeMappings.put(Species.Gen4Formes.wormadamT + formeOffset,new FormeInfo(Species.wormadam,2, 0)); + formeMappings.put(Species.Gen4Formes.giratinaO + formeOffset,new FormeInfo(Species.giratina,1, 0)); + formeMappings.put(Species.Gen4Formes.shayminS + formeOffset,new FormeInfo(Species.shaymin,1, 0)); + formeMappings.put(Species.Gen4Formes.rotomH + formeOffset,new FormeInfo(Species.rotom,1, 0)); + formeMappings.put(Species.Gen4Formes.rotomW + formeOffset,new FormeInfo(Species.rotom,2, 0)); + formeMappings.put(Species.Gen4Formes.rotomFr + formeOffset,new FormeInfo(Species.rotom,3, 0)); + formeMappings.put(Species.Gen4Formes.rotomFa + formeOffset,new FormeInfo(Species.rotom,4, 0)); + formeMappings.put(Species.Gen4Formes.rotomM + formeOffset,new FormeInfo(Species.rotom,5, 0)); + + return formeMappings; + } + + private static Map setupCosmeticForms() { + Map cosmeticForms = new TreeMap<>(); + + cosmeticForms.put(Species.unown, 28); + cosmeticForms.put(Species.burmy, 3); + cosmeticForms.put(Species.shellos, 2); + cosmeticForms.put(Species.gastrodon, 2); + return cosmeticForms; + } + + private static Map> setupFormeSuffixesByBaseForme() { + Map> map = new HashMap<>(); + + Map deoxysMap = new HashMap<>(); + deoxysMap.put(1,"-A"); + deoxysMap.put(2,"-D"); + deoxysMap.put(3,"-S"); + map.put(Species.deoxys,deoxysMap); + + Map wormadamMap = new HashMap<>(); + wormadamMap.put(1,"-S"); + wormadamMap.put(2,"-T"); + map.put(Species.wormadam,wormadamMap); + + Map shayminMap = new HashMap<>(); + shayminMap.put(1,"-S"); + map.put(Species.shaymin,shayminMap); + + Map giratinaMap = new HashMap<>(); + giratinaMap.put(1,"-O"); + map.put(Species.giratina,giratinaMap); + + Map rotomMap = new HashMap<>(); + rotomMap.put(1,"-H"); + rotomMap.put(2,"-W"); + rotomMap.put(3,"-Fr"); + rotomMap.put(4,"-Fa"); + rotomMap.put(5,"-M"); + map.put(Species.rotom,rotomMap); + + return map; + } + + private static Map setupDummyFormeSuffixes() { + Map m = new HashMap<>(); + m.put(0,""); + return m; + } + + private static Map> setupAbsolutePokeNumsByBaseForme() { + Map> map = new HashMap<>(); + + Map deoxysMap = new HashMap<>(); + deoxysMap.put(1,Species.Gen4Formes.deoxysA); + deoxysMap.put(2,Species.Gen4Formes.deoxysD); + deoxysMap.put(3,Species.Gen4Formes.deoxysS); + map.put(Species.deoxys, deoxysMap); + + Map wormadamMap = new HashMap<>(); + wormadamMap.put(1,Species.Gen4Formes.wormadamS); + wormadamMap.put(2,Species.Gen4Formes.wormadamT); + map.put(Species.wormadam, wormadamMap); + + Map giratinaMap = new HashMap<>(); + giratinaMap.put(1,Species.Gen4Formes.giratinaO); + map.put(Species.giratina, giratinaMap); + + Map shayminMap = new HashMap<>(); + shayminMap.put(1,Species.Gen4Formes.shayminS); + map.put(Species.shaymin, shayminMap); + + Map rotomMap = new HashMap<>(); + rotomMap.put(1,Species.Gen4Formes.rotomH); + rotomMap.put(2,Species.Gen4Formes.rotomW); + rotomMap.put(3,Species.Gen4Formes.rotomFr); + rotomMap.put(4,Species.Gen4Formes.rotomFa); + rotomMap.put(5,Species.Gen4Formes.rotomM); + map.put(Species.rotom, rotomMap); + + return map; + } + + private static Map setupDummyAbsolutePokeNums() { + Map m = new HashMap<>(); + m.put(255,0); + return m; + } + + public static void tagTrainersDP(List trs) { + // Gym Trainers + tag(trs, "GYM1", 0xf4, 0xf5); + tag(trs, "GYM2", 0x144, 0x103, 0x104, 0x15C); + tag(trs, "GYM3", 0x135, 0x136, 0x137, 0x138); + tag(trs, "GYM4", 0x1f1, 0x1f2, 0x191, 0x153, 0x125, 0x1E3); + tag(trs, "GYM5", 0x165, 0x145, 0x10a, 0x14a, 0x154, 0x157, 0x118, 0x11c); + tag(trs, "GYM6", 0x13a, 0x100, 0x101, 0x117, 0x16f, 0xe8, 0x11b); + tag(trs, "GYM7", 0x10c, 0x10d, 0x10e, 0x10f, 0x33b, 0x33c); + tag(trs, "GYM8", 0x158, 0x155, 0x12d, 0x12e, 0x12f, 0x11d, 0x119); + + // Gym Leaders + tag(trs, 0xf6, "GYM1-LEADER"); + tag(trs, 0x13b, "GYM2-LEADER"); + tag(trs, 0x13d, "GYM3-LEADER"); // Maylene + tag(trs, 0x13c, "GYM4-LEADER"); // Wake + tag(trs, 0x13e, "GYM5-LEADER"); // Fantina + tag(trs, 0xfa, "GYM6-LEADER"); // Byron + tag(trs, 0x13f, "GYM7-LEADER"); // Candice + tag(trs, 0x140, "GYM8-LEADER"); // Volkner + + // Elite 4 + tag(trs, 0x105, "ELITE1"); + tag(trs, 0x106, "ELITE2"); + tag(trs, 0x107, "ELITE3"); + tag(trs, 0x108, "ELITE4"); + tag(trs, 0x10b, "CHAMPION"); + + // Rival battles (8) + tagRivalConsecutive(trs, "RIVAL1", 0xf8); + tagRivalConsecutive(trs, "RIVAL2", 0x1d7); + tagRivalConsecutive(trs, "RIVAL3", 0x1da); + tagRivalConsecutive(trs, "RIVAL4", 0x1dd); + // Tag battle is not following ze usual format + tag(trs, 0x26b, "RIVAL5-0"); + tag(trs, 0x26c, "RIVAL5-1"); + tag(trs, 0x25f, "RIVAL5-2"); + // Back to normal + tagRivalConsecutive(trs, "RIVAL6", 0x1e0); + tagRivalConsecutive(trs, "RIVAL7", 0x346); + tagRivalConsecutive(trs, "RIVAL8", 0x349); + + // Themed + tag(trs, "THEMED:CYRUS-LEADER", 0x193, 0x194); + tag(trs, "THEMED:MARS-STRONG", 0x127, 0x195, 0x210); + tag(trs, "THEMED:JUPITER-STRONG", 0x196, 0x197); + tag(trs, "THEMED:SATURN-STRONG", 0x198, 0x199); + + // Lucas & Dawn tag battles + tagFriendConsecutive(trs, "FRIEND1", 0x265); + tagFriendConsecutive(trs, "FRIEND1", 0x268); + tagFriendConsecutive2(trs, "FRIEND2", 0x26D); + tagFriendConsecutive2(trs, "FRIEND2", 0x270); + + } + + public static void tagTrainersPt(List trs) { + // Gym Trainers + tag(trs, "GYM1", 0xf4, 0xf5); + tag(trs, "GYM2", 0x144, 0x103, 0x104, 0x15C); + tag(trs, "GYM3", 0x165, 0x145, 0x154, 0x157, 0x118, 0x11c); + tag(trs, "GYM4", 0x135, 0x136, 0x137, 0x138); + tag(trs, "GYM5", 0x1f1, 0x1f2, 0x191, 0x153, 0x125, 0x1E3); + tag(trs, "GYM6", 0x13a, 0x100, 0x101, 0x117, 0x16f, 0xe8, 0x11b); + tag(trs, "GYM7", 0x10c, 0x10d, 0x10e, 0x10f, 0x33b, 0x33c); + tag(trs, "GYM8", 0x158, 0x155, 0x12d, 0x12e, 0x12f, 0x11d, 0x119, 0x14b); + + // Gym Leaders + tag(trs, 0xf6, "GYM1-LEADER"); + tag(trs, 0x13b, "GYM2-LEADER"); + tag(trs, 0x13e, "GYM3-LEADER"); // Fantina + tag(trs, 0x13d, "GYM4-LEADER"); // Maylene + tag(trs, 0x13c, "GYM5-LEADER"); // Wake + tag(trs, 0xfa, "GYM6-LEADER"); // Byron + tag(trs, 0x13f, "GYM7-LEADER"); // Candice + tag(trs, 0x140, "GYM8-LEADER"); // Volkner + + // Elite 4 + tag(trs, 0x105, "ELITE1"); + tag(trs, 0x106, "ELITE2"); + tag(trs, 0x107, "ELITE3"); + tag(trs, 0x108, "ELITE4"); + tag(trs, 0x10b, "CHAMPION"); + + // Rival battles (10) + tagRivalConsecutive(trs, "RIVAL1", 0x353); + tagRivalConsecutive(trs, "RIVAL2", 0xf8); + tagRivalConsecutive(trs, "RIVAL3", 0x1d7); + tagRivalConsecutive(trs, "RIVAL4", 0x1da); + tagRivalConsecutive(trs, "RIVAL5", 0x1dd); + // Tag battle is not following ze usual format + tag(trs, 0x26b, "RIVAL6-0"); + tag(trs, 0x26c, "RIVAL6-1"); + tag(trs, 0x25f, "RIVAL6-2"); + // Back to normal + tagRivalConsecutive(trs, "RIVAL7", 0x1e0); + tagRivalConsecutive(trs, "RIVAL8", 0x346); + tagRivalConsecutive(trs, "RIVAL9", 0x349); + tagRivalConsecutive(trs, "RIVAL10", 0x368); + + // Battleground Gym Leaders + tag(trs, 0x35A, "GYM1"); + tag(trs, 0x359, "GYM2"); + tag(trs, 0x35C, "GYM3"); + tag(trs, 0x356, "GYM4"); + tag(trs, 0x35B, "GYM5"); + tag(trs, 0x358, "GYM6"); + tag(trs, 0x355, "GYM7"); + tag(trs, 0x357, "GYM8"); + + // Match vs Volkner and Flint in Battle Frontier + tag(trs, 0x399, "GYM8"); + tag(trs, 0x39A, "ELITE3"); + + // E4 rematch + tag(trs, 0x362, "ELITE1"); + tag(trs, 0x363, "ELITE2"); + tag(trs, 0x364, "ELITE3"); + tag(trs, 0x365, "ELITE4"); + tag(trs, 0x366, "CHAMPION"); + + // Themed + tag(trs, "THEMED:CYRUS-LEADER", 0x391, 0x193, 0x194); + tag(trs, "THEMED:MARS-STRONG", 0x127, 0x195, 0x210, 0x39e); + tag(trs, "THEMED:JUPITER-STRONG", 0x196, 0x197, 0x39f); + tag(trs, "THEMED:SATURN-STRONG", 0x198, 0x199); + + // Lucas & Dawn tag battles + tagFriendConsecutive(trs, "FRIEND1", 0x265); + tagFriendConsecutive(trs, "FRIEND1", 0x268); + tagFriendConsecutive2(trs, "FRIEND2", 0x26D); + tagFriendConsecutive2(trs, "FRIEND2", 0x270); + + } + + public static void tagTrainersHGSS(List trs) { + // Gym Trainers + tag(trs, "GYM1", 0x32, 0x1D); + tag(trs, "GYM2", 0x43, 0x44, 0x45, 0x0a); + tag(trs, "GYM3", 0x05, 0x46, 0x47, 0x16); + tag(trs, "GYM4", 0x1ed, 0x1ee, 0x59, 0x2e); + tag(trs, "GYM5", 0x9c, 0x9d, 0x9f, 0xfb); + tag(trs, "GYM7", 0x1e0, 0x1e1, 0x1e2, 0x1e3, 0x1e4); + tag(trs, "GYM8", 0x6e, 0x6f, 0x70, 0x75, 0x77); + + tag(trs, "GYM9", 0x134, 0x2ad); + tag(trs, "GYM10", 0x2a4, 0x2a5, 0x2a6, 0x129, 0x12a); + tag(trs, "GYM11", 0x18c, 0xe8, 0x151); + tag(trs, "GYM12", 0x150, 0x146, 0x164, 0x15a); + tag(trs, "GYM13", 0x53, 0x54, 0xb7, 0x88); + tag(trs, "GYM14", 0x170, 0x171, 0xe6, 0x19f); + tag(trs, "GYM15", 0x2b1, 0x2b2, 0x2b3, 0x2b4, 0x2b5, 0x2b6); + tag(trs, "GYM16", 0x2a9, 0x2aa, 0x2ab, 0x2ac); + + // Gym Leaders + tag(trs, 0x14, "GYM1-LEADER"); + tag(trs, 0x15, "GYM2-LEADER"); + tag(trs, 0x1e, "GYM3-LEADER"); + tag(trs, 0x1f, "GYM4-LEADER"); + tag(trs, 0x22, "GYM5-LEADER"); + tag(trs, 0x21, "GYM6-LEADER"); + tag(trs, 0x20, "GYM7-LEADER"); + tag(trs, 0x23, "GYM8-LEADER"); + + tag(trs, 0xFD, "GYM9-LEADER"); + tag(trs, 0xFE, "GYM10-LEADER"); + tag(trs, 0xFF, "GYM11-LEADER"); + tag(trs, 0x100, "GYM12-LEADER"); + tag(trs, 0x101, "GYM13-LEADER"); + tag(trs, 0x102, "GYM14-LEADER"); + tag(trs, 0x103, "GYM15-LEADER"); + tag(trs, 0x105, "GYM16-LEADER"); + + // Elite 4 + tag(trs, 0xf5, "ELITE1"); + tag(trs, 0xf7, "ELITE2"); + tag(trs, 0x1a2, "ELITE3"); + tag(trs, 0xf6, "ELITE4"); + tag(trs, 0xf4, "CHAMPION"); + + // Red + tag(trs, 0x104, "UBER"); + + // Gym Rematches + tag(trs, 0x2c8, "GYM1-LEADER"); + tag(trs, 0x2c9, "GYM2-LEADER"); + tag(trs, 0x2ca, "GYM3-LEADER"); + tag(trs, 0x2cb, "GYM4-LEADER"); + tag(trs, 0x2ce, "GYM5-LEADER"); + tag(trs, 0x2cd, "GYM6-LEADER"); + tag(trs, 0x2cc, "GYM7-LEADER"); + tag(trs, 0x2cf, "GYM8-LEADER"); + + tag(trs, 0x2d0, "GYM9-LEADER"); + tag(trs, 0x2d1, "GYM10-LEADER"); + tag(trs, 0x2d2, "GYM11-LEADER"); + tag(trs, 0x2d3, "GYM12-LEADER"); + tag(trs, 0x2d4, "GYM13-LEADER"); + tag(trs, 0x2d5, "GYM14-LEADER"); + tag(trs, 0x2d6, "GYM15-LEADER"); + tag(trs, 0x2d7, "GYM16-LEADER"); + + // Elite 4 Rematch + tag(trs, 0x2be, "ELITE1"); + tag(trs, 0x2bf, "ELITE2"); + tag(trs, 0x2c0, "ELITE3"); + tag(trs, 0x2c1, "ELITE4"); + tag(trs, 0x2bd, "CHAMPION"); + + // Rival Battles + tagRivalConsecutive(trs, "RIVAL1", 0x1F0); + + tag(trs, 0x10a, "RIVAL2-0"); + tag(trs, 0x10d, "RIVAL2-1"); + tag(trs, 0x1, "RIVAL2-2"); + + tag(trs, 0x10B, "RIVAL3-0"); + tag(trs, 0x10E, "RIVAL3-1"); + tag(trs, 0x107, "RIVAL3-2"); + + tag(trs, 0x121, "RIVAL4-0"); + tag(trs, 0x10f, "RIVAL4-1"); + tag(trs, 0x120, "RIVAL4-2"); + + tag(trs, 0x10C, "RIVAL5-0"); + tag(trs, 0x110, "RIVAL5-1"); + tag(trs, 0x108, "RIVAL5-2"); + + tagRivalConsecutive(trs, "RIVAL6", 0x11e); + tagRivalConsecutive(trs, "RIVAL7", 0x2e0); // dragons den tag battle + tagRivalConsecutive(trs, "RIVAL8", 0x1EA); + + // Clair & Lance match in Dragons Den + tag(trs, 0x2DE, "GYM8"); + tag(trs, 0x2DD, "CHAMPION"); + + tag(trs, 0xa0, "KIMONO1-STRONG"); + tag(trs, 0xa1, "KIMONO2-STRONG"); + tag(trs, 0xa2, "KIMONO3-STRONG"); + tag(trs, 0xa3, "KIMONO4-STRONG"); + tag(trs, 0xa4, "KIMONO5-STRONG"); + + // Themed + tag(trs, "THEMED:ARIANA-STRONG", 0x1df, 0x1de); + tag(trs, "THEMED:PETREL-STRONG", 0x1e8, 0x1e7); + tag(trs, "THEMED:PROTON-STRONG", 0x1e6, 0x2c2); + tag(trs, "THEMED:SPROUTTOWER", 0x2b, 0x33, 0x34, 0x35, 0x36, 0x37, 0x122); + + tag(trs,"LEADER",485); // Archer + } + + private static void tag(List allTrainers, int number, String tag) { + allTrainers.get(number - 1).tag = tag; + } + + private static void tag(List allTrainers, String tag, int... numbers) { + for (int num : numbers) { + allTrainers.get(num - 1).tag = tag; + } + } + + private static void tagRivalConsecutive(List allTrainers, String tag, int offsetFire) { + allTrainers.get(offsetFire - 1).tag = tag + "-0"; + allTrainers.get(offsetFire).tag = tag + "-1"; + allTrainers.get(offsetFire - 2).tag = tag + "-2"; + + } + + private static void tagFriendConsecutive(List allTrainers, String tag, int offsetGrass) { + allTrainers.get(offsetGrass - 1).tag = tag + "-1"; + allTrainers.get(offsetGrass).tag = tag + "-2"; + allTrainers.get(offsetGrass + 1).tag = tag + "-0"; + + } + + private static void tagFriendConsecutive2(List allTrainers, String tag, int offsetWater) { + allTrainers.get(offsetWater - 1).tag = tag + "-0"; + allTrainers.get(offsetWater).tag = tag + "-1"; + allTrainers.get(offsetWater + 1).tag = tag + "-2"; + + } + + public static void setMultiBattleStatusDP(List trs) { + // 407 + 528: Commander Mars and Commander Jupiter Multi Battle on Spear Pillar + // 414 + 415: Galactic Grunts in Jubilife City + // 419 + 426: Galactic Grunts in Lake Verity + // 420 + 427: Galactic Grunts in Lake Verity + // 521 + 527: Galactic Grunts on Spear Pillar + // 561 + 590: Double Battle with Dragon Tamer Drake and Black Belt Jarrett + // 835 + 836: Galactic Grunts in Iron Island + // 848 + 849: Galactic Grunts in Veilstone City + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.ALWAYS, 414, 415, 419, 420, 426, 427, 521, 527, + 528, 561, 590, 835, 836, 848, 849 + ); + + // 34 + 35: Potential Double Battle with Camper Anthony and Picnicker Lauren + // 82 + 83: Potential Double Battle with Rich Boy Jason and Lady Melissa + // 84 + 85: Potential Double Battle with Gentleman Jeremy and Socialite Reina + // 95 + 96: Potential Double Battle with PKMN Ranger Jeffrey and PKMN Ranger Allison + // 104 + 106: Potential Double Battle with Swimmer Evan and Swimmer Mary + // 160 + 494: Potential Double Battle with Swimmer Erik and Swimmer Claire + // 186 + 191: Potential Double Battle with Swimmer Colton and Swimmer Paige + // 201 + 204: Potential Double Battle with Bug Catcher Jack and Lass Briana + // 202 + 203: Potential Double Battle with Bug Catcher Phillip and Bug Catcher Donald + // 205 + 206: Potential Double Battle with Psychic Elijah and Psychic Lindsey + // 278 + 287: Potential Double Battle with Ace Trainer Maya and Ace Trainer Dennis + // 337 + 359: Potential Double Battle with Sailor Marc and Tuber Conner + // 358 + 360: Potential Double Battle with Tuber Trenton and Tuber Mariel + // 372 + 445: Potential Double Battle with Battle Girl Tyler and Black Belt Kendal + // 373 + 386: Potential Double Battle with Bird Keeper Autumn and Dragon Tamer Joe + // 379 + 459: Potential Double Battle with Camper Diego and Picnicker Ana + // 383 + 443: Potential Double Battle with Collector Terry and Ruin Maniac Gerald + // 388 + 392: Potential Double Battle with Ace Trainer Jonah and Ace Trainer Brenda + // 389 + 393: Potential Double Battle with Ace Trainer Micah and Ace Trainer Brandi + // 390 + 394: Potential Double Battle with Ace Trainer Arthur and Ace Trainer Clarice + // 395 + 398: Potential Double Battle with Psychic Kody and Psychic Rachael + // 396 + 399: Potential Double Battle with Psychic Landon and Psychic Desiree + // 397 + 400: Potential Double Battle with Psychic Deandre and Psychic Kendra + // 446 + 499: Potential Double Battle with Black Belt Eddie and Veteran Terrell + // 447 + 500: Potential Double Battle with Black Belt Willie and Veteran Brenden + // 450 + 496: Potential Double Battle with Lass Cassidy and Youngster Wayne + // 452 + 453: Potential Double Battle with Hiker Damon and Hiker Maurice + // 454 + 455: Potential Double Battle with Hiker Reginald and Hiker Lorenzo + // 505 + 506: Potential Double Battle with Worker Brendon and Worker Quentin + // 555 + 560: Potential Double Battle with Bird Keeper Geneva and Dragon Tamer Stanley + // 556 + 589: Potential Double Battle with Bird Keeper Krystal and Black Belt Ray + // 562 + 606: Potential Double Battle with Dragon Tamer Kenny and Veteran Harlan + // 566 + 575: Potential Double Battle with Ace Trainer Felix and Ace Trainer Dana + // 569 + 579: Potential Double Battle with Ace Trainer Keenan and Ace Trainer Kassandra + // 570 + 580: Potential Double Battle with Ace Trainer Stefan and Ace Trainer Jasmin + // 571 + 581: Potential Double Battle with Ace Trainer Skylar and Ace Trainer Natasha + // 572 + 582: Potential Double Battle with Ace Trainer Abel and Ace Trainer Monique + // 584 + 586: Potential Double Battle with Psychic Sterling and Psychic Chelsey + // 591 + 596: Potential Double Battle with PKMN Ranger Kyler and PKMN Ranger Krista + // 594 + 554/585: Potential Double Battle with PKMN Ranger Ashlee and either Bird Keeper Audrey or Psychic Daisy + // 599 + 602: Potential Double Battle with Swimmer Sam and Swimmer Sophia + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.POTENTIAL, 34, 35, 82, 83, 84, 85, 95, 96, 104, + 106, 160, 186, 191, 201, 202, 203, 204, 205, 206, 278, 287, 337, 358, 359, 360, 372, 373, 379, 383, 386, + 388, 389, 390, 392, 393, 394, 395, 396, 397, 398, 399, 400, 443, 445, 446, 447, 450, 452, 453, 454, 455, + 459, 494, 496, 499, 500, 505, 506, 554, 555, 556, 560, 562, 566, 569, 570, 571, 572, 575, 579, 580, 581, + 582, 584, 585, 586, 589, 591, 594, 596, 599, 602, 606 + ); + } + + public static void setMultiBattleStatusPt(List trs) { + // In addition to every single trainer listed in setCouldBeMultiBattleDP... + // 921 + 922: Elite Four Flint and Leader Volkner Multi Battle in the Fight Area + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.ALWAYS, 414, 415, 419, 420, 426, 427, 521, 527, + 528, 561, 590, 835, 836, 848, 849, 921, 922 + ); + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.POTENTIAL, 34, 35, 82, 83, 84, 85, 95, 96, 104, + 106, 160, 186, 191, 201, 202, 203, 204, 205, 206, 278, 287, 337, 358, 359, 360, 372, 373, 379, 383, 386, + 388, 389, 390, 392, 393, 394, 395, 396, 397, 398, 399, 400, 443, 445, 446, 447, 450, 452, 453, 454, 455, + 459, 494, 496, 499, 500, 505, 506, 554, 555, 556, 560, 562, 566, 569, 570, 571, 572, 575, 579, 580, 581, + 582, 584, 585, 586, 589, 591, 594, 596, 599, 602, 606 + ); + } + + public static void setMultiBattleStatusHGSS(List trs) { + // 120 + 417: Double Battle with Ace Trainer Irene and Ace Trainer Jenn + // 354 + 355: Double Battle with Lass Laura and Lass Shannon + // 479 + 499: Multi Battle with Executive Ariana and Team Rocket Grunt in Team Rocket HQ + // 679 + 680: Double Battle with Beauty Callie and Beauty Kassandra + // 733 + 734: Multi Battle with Champion Lance and Leader Clair in the Dragon's Den + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.ALWAYS, 120, 354, 355, 417, 479, 499, 679, 680, 733, 734); + + // 147 + 151: Potential Double Battle with Camper Ted and Picnicker Erin + // 423: Potential Double Battle with Pokéfan Jeremy. His potential teammate (Pokéfan Georgia) has more than + // three Pokemon in the vanilla game, so we leave her be. + // 564 + 567: Potential Double Battle with Teacher Clarice and School Kid Torin + // 575 + 576: Potential Double Battle with Biker Dan and Biker Theron + // 577 + 579: Potential Double Battle with Biker Markey and Biker Teddy + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.POTENTIAL, 147, 151, 423, 564, 567, 575, 576, 577, 579); + } + + private static void setMultiBattleStatus(List allTrainers, Trainer.MultiBattleStatus status, int... numbers) { + for (int num : numbers) { + if (allTrainers.size() > (num - 1)) { + allTrainers.get(num - 1).multiBattleStatus = status; + } + } + } + +} diff --git a/src/com/pkrandom/constants/Gen5Constants.java b/src/com/pkrandom/constants/Gen5Constants.java new file mode 100644 index 0000000..e5e39ce --- /dev/null +++ b/src/com/pkrandom/constants/Gen5Constants.java @@ -0,0 +1,1948 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Gen5Constants.java - Constants for Black/White/Black 2/White 2 --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.pkrandom.pokemon.ItemList; +import com.pkrandom.pokemon.MoveCategory; +import com.pkrandom.pokemon.Trainer; +import com.pkrandom.pokemon.Type; + +public class Gen5Constants { + + public static final int Type_BW = 0; + public static final int Type_BW2 = 1; + + public static final int arm9Offset = 0x02004000; + + public static final int pokemonCount = 649, moveCount = 559; + private static final int bw1FormeCount = 18, bw2FormeCount = 24; + private static final int bw1formeOffset = 0, bw2formeOffset = 35; + + private static final int bw1NonPokemonBattleSpriteCount = 3; + private static final int bw2NonPokemonBattleSpriteCount = 36; + + public static final int bsHPOffset = 0, bsAttackOffset = 1, bsDefenseOffset = 2, bsSpeedOffset = 3, + bsSpAtkOffset = 4, bsSpDefOffset = 5, bsPrimaryTypeOffset = 6, bsSecondaryTypeOffset = 7, + bsCatchRateOffset = 8, bsCommonHeldItemOffset = 12, bsRareHeldItemOffset = 14, + bsDarkGrassHeldItemOffset = 16, bsGrowthCurveOffset = 21, bsAbility1Offset = 24, bsAbility2Offset = 25, + bsAbility3Offset = 26, bsFormeOffset = 28, bsFormeSpriteOffset = 30, bsFormeCountOffset = 32, + bsTMHMCompatOffset = 40, bsMTCompatOffset = 60; + + public static final byte[] bw1NewStarterScript = { 0x24, 0x00, (byte) 0xA7, 0x02, (byte) 0xE7, 0x00, 0x00, 0x00, + (byte) 0xDE, 0x00, 0x00, 0x00, (byte) 0xF8, 0x01, 0x05, 0x00 }; + + public static final String bw1StarterScriptMagic = "2400A702"; + + public static final int bw1StarterTextOffset = 18, bw1CherenText1Offset = 26, bw1CherenText2Offset = 53; + + public static final byte[] bw2NewStarterScript = { 0x28, 0x00, (byte) 0xA1, 0x40, 0x04, 0x00, (byte) 0xDE, 0x00, + 0x00, 0x00, (byte) 0xFD, 0x01, 0x05, 0x00 }; + + public static final String bw2StarterScriptMagic = "2800A1400400"; + + public static final int bw2StarterTextOffset = 37, bw2RivalTextOffset = 60; + + public static final int perSeasonEncounterDataLength = 232; + private static final int bw1AreaDataEntryLength = 249, bw2AreaDataEntryLength = 345, bw1EncounterAreaCount = 61, bw2EncounterAreaCount = 85; + + public static final int[] encountersOfEachType = { 12, 12, 12, 5, 5, 5, 5 }; + + public static final String[] encounterTypeNames = { "Grass/Cave", "Doubles Grass", "Shaking Spots", "Surfing", + "Surfing Spots", "Fishing", "Fishing Spots" }; + + public static final int[] habitatClassificationOfEachType = { 0, 0, 0, 1, 1, 2, 2 }; + + public static final int bw2Route4AreaIndex = 40, bw2VictoryRoadAreaIndex = 76, bw2ReversalMountainAreaIndex = 73; + + public static final int b2Route4EncounterFile = 104, b2VRExclusiveRoom1 = 71, b2VRExclusiveRoom2 = 73, + b2ReversalMountainStart = 49, b2ReversalMountainEnd = 54; + + public static final int w2Route4EncounterFile = 105, w2VRExclusiveRoom1 = 78, w2VRExclusiveRoom2 = 79, + w2ReversalMountainStart = 55, w2ReversalMountainEnd = 60; + + public static final List bw2HiddenHollowUnovaPokemon = Arrays.asList(Species.watchog, Species.herdier, Species.liepard, + Species.pansage, Species.pansear, Species.panpour, Species.pidove, Species.zebstrika, Species.boldore, + Species.woobat, Species.drilbur, Species.audino, Species.gurdurr, Species.tympole, Species.throh, + Species.sawk, Species.leavanny, Species.scolipede, Species.cottonee, Species.petilil, Species.basculin, + Species.krookodile, Species.maractus, Species.crustle, Species.scraggy, Species.sigilyph, Species.tirtouga, + Species.garbodor, Species.minccino, Species.gothorita, Species.duosion, Species.ducklett, Species.vanillish, + Species.emolga, Species.karrablast, Species.alomomola, Species.galvantula, Species.klinklang, Species.elgyem, + Species.litwick, Species.axew, Species.cubchoo, Species.shelmet, Species.stunfisk, Species.mienfoo, + Species.druddigon, Species.golett, Species.pawniard, Species.bouffalant, Species.braviary, Species.mandibuzz, + Species.heatmor, Species.durant); + + public static final String tmDataPrefix = "87038803"; + + public static final int tmCount = 95, hmCount = 6, tmBlockOneCount = 92, tmBlockOneOffset = Items.tm01, + tmBlockTwoOffset = Items.tm93; + + public static final String bw1ItemPalettesPrefix = "E903EA03020003000400050006000700", + bw2ItemPalettesPrefix = "FD03FE03020003000400050006000700"; + + public static final int bw2MoveTutorCount = 60, bw2MoveTutorBytesPerEntry = 12; + + public static final int evolutionMethodCount = 27; + + public static final int highestAbilityIndex = Abilities.teravolt; + + public static final int fossilPokemonFile = 877; + public static final int fossilPokemonLevelOffset = 0x3F7; + + public static final Map> abilityVariations = setupAbilityVariations(); + + private static Map> setupAbilityVariations() { + Map> map = new HashMap<>(); + map.put(Abilities.insomnia, Arrays.asList(Abilities.insomnia, Abilities.vitalSpirit)); + map.put(Abilities.clearBody, Arrays.asList(Abilities.clearBody, Abilities.whiteSmoke)); + map.put(Abilities.hugePower, Arrays.asList(Abilities.hugePower, Abilities.purePower)); + map.put(Abilities.battleArmor, Arrays.asList(Abilities.battleArmor, Abilities.shellArmor)); + map.put(Abilities.cloudNine, Arrays.asList(Abilities.cloudNine, Abilities.airLock)); + map.put(Abilities.filter, Arrays.asList(Abilities.filter, Abilities.solidRock)); + map.put(Abilities.roughSkin, Arrays.asList(Abilities.roughSkin, Abilities.ironBarbs)); + map.put(Abilities.moldBreaker, Arrays.asList(Abilities.moldBreaker, Abilities.turboblaze, Abilities.teravolt)); + + return map; + } + + public static final List uselessAbilities = Arrays.asList(Abilities.forecast, Abilities.multitype, + Abilities.flowerGift, Abilities.zenMode); + + public static final int normalItemSetVarCommand = 0x28, hiddenItemSetVarCommand = 0x2A, normalItemVarSet = 0x800C, + hiddenItemVarSet = 0x8000; + + public static final int scriptListTerminator = 0xFD13; + + public static final int[] mulchIndices = {Items.growthMulch, Items.dampMulch, Items.stableMulch, Items.gooeyMulch}; + + public static final MoveCategory[] moveCategoryIndices = { MoveCategory.STATUS, MoveCategory.PHYSICAL, + MoveCategory.SPECIAL }; + + public static byte moveCategoryToByte(MoveCategory cat) { + switch (cat) { + case PHYSICAL: + return 1; + case SPECIAL: + return 2; + case STATUS: + default: + return 0; + } + } + + public static final int trappingEffect = 106; + + public static final int noDamageStatusQuality = 1, noDamageStatChangeQuality = 2, damageStatusQuality = 4, + noDamageStatusAndStatChangeQuality = 5, damageTargetDebuffQuality = 6, damageUserBuffQuality = 7, + damageAbsorbQuality = 8; + + public static final Type[] typeTable = constructTypeTable(); + + private static final Map bw1FormeSuffixes = setupFormeSuffixes(Gen5Constants.Type_BW); + + private static final Map bw2FormeSuffixes = setupFormeSuffixes(Gen5Constants.Type_BW2); + + private static final Map> formeSuffixesByBaseForme = setupFormeSuffixesByBaseForme(); + private static final Map dummyFormeSuffixes = setupDummyFormeSuffixes(); + + private static final Map> absolutePokeNumsByBaseForme = setupAbsolutePokeNumsByBaseForme(); + private static final Map dummyAbsolutePokeNums = setupDummyAbsolutePokeNums(); + + public static String getFormeSuffixByBaseForme(int baseForme, int formNum) { + return formeSuffixesByBaseForme.getOrDefault(baseForme,dummyFormeSuffixes).getOrDefault(formNum,""); + } + + public static Integer getAbsolutePokeNumByBaseForme(int baseForme, int formNum) { + return absolutePokeNumsByBaseForme.getOrDefault(baseForme,dummyAbsolutePokeNums).getOrDefault(formNum,baseForme); + } + + private static final List bw1IrregularFormes = Arrays.asList( + Species.Gen5Formes.castformF, Species.Gen5Formes.castformW, Species.Gen5Formes.castformI, + Species.Gen5Formes.darmanitanZ, + Species.Gen5Formes.meloettaP + ); + + private static final List bw2IrregularFormes = Arrays.asList( + Species.Gen5Formes.castformF, Species.Gen5Formes.castformW, Species.Gen5Formes.castformI, + Species.Gen5Formes.darmanitanZ, + Species.Gen5Formes.meloettaP, + Species.Gen5Formes.kyuremW, + Species.Gen5Formes.kyuremB + ); + + public static final List emptyPlaythroughTrainers = Arrays.asList(new Integer[] { }); + + public static final List bw1MainPlaythroughTrainers = Arrays.asList( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 93, 94, 95, 96, 97, 98, + 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 137, 138, + 139, 140, 141, 142, 143, 144, 145, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, + 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, + 182, 183, 184, 186, 187, 188, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, + 204, 212, 213, 214, 215, 216, 217, 218, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, + 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, + 251, 252, 253, 254, 255, 256, 257, 258, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, + 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 290, 291, 292, 293, + 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, + 313, 315, 316, 401, 402, 408, 409, 412, 413, 438, 439, 441, 442, 443, 445, 447, 450, + 460, 461, 462, 465, 466, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, + 484, 485, 488, 489, 490, 501, 502, 503, 504, 505, 506, + 513, 514, 515, 516, 517, 518, 519, 520, 526, 531, 532, 533, 534, 535, 536, 537, + 538, 544, 545, 546, 549, 550, 552, 553, 554, 555, 556, 557, 582, 583, 584, 585, 586, + 587, 600, 601, 602, 603, 604, 605, 606, 607, 610, 611, 612, 613); + + public static final List bw2MainPlaythroughTrainers = Arrays.asList( + 4, 5, 6, 133, 134, 135, 136, 137, 138, 139, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, + 164, 165, 169, 170, 171, 172, 173, 174, 175, 176, 177, + 178, 179, 180, 181, 182, 203, 204, 205, 206, 207, 208, 209, 210, 211, + 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, + 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 237, 238, 239, 240, + 242, 243, 244, 245, 247, 248, 249, 250, 252, 253, 254, 255, 256, 257, + 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, + 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, + 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, + 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, + 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, + 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, + 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, + 372, 373, 374, 375, 376, 377, 381, 382, 383, + 384, 385, 386, 387, 388, 389, 390, 391, 392, 426, 427, 428, 429, 430, + 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, + 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 461, 462, 463, + 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, + 478, 479, 480, 481, 482, 483, 484, 485, 486, 497, 498, 499, 500, 501, + 502, 503, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, + 522, 523, 524, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, + 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, + 562, 563, 564, 565, 566, 567, 568, 569, 570, 580, 581, 583, 584, 585, + 586, 587, 592, 593, 594, 595, 596, 597, 598, 599, 600, + 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, + 615, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 657, 658, + 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, + 673, 679, 680, 681, 682, 683, 690, 691, 692, 703, 704, + 705, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, + 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, + 745, 746, 747, 748, 749, 750, 751, 752, 754, 755, 756, 763, 764, 765, + 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 786, 787, 788, + 789, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, + 807, 808, 809, 810, 811, 812); + + public static final List bw2DriftveilTrainerOffsets = Arrays.asList(56, 57, 0, 1, 2, 3, 4, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77); + + public static final int normalTrainerNameLength = 813, normalTrainerClassLength = 236; + +// public static final Map bw1ShopIndex = new HashMap() {1:"Check"}; + + public static final List bw1MainGameShops = Arrays.asList( + 3, 5, 6, 8, 9, 12, 14, 17, 18, 19, 21, 22 + ); + + public static final List bw1ShopNames = Arrays.asList( + "Primary 0 Badges", + "Shopping Mall 9 TMs", + "Icirrus Secondary (TMs)", + "Driftveil Herb Salesman", + "Mistralton Secondary (TMs)", + "Shopping Mall 9 F3 Left", + "Accumula Secondary", + "Nimbasa Secondary (TMs)", + "Striaton Secondary", + "League Secondary", + "Lacunosa Secondary", + "Black City/White Forest Secondary", + "Nacrene/Shopping Mall 9 X Items", + "Driftveil Incense Salesman", + "Nacrene Secondary", + "Undella Secondary", + "Primary 2 Badges", + "Castelia Secondary", + "Driftveil Secondary", + "Opelucid Secondary", + "Primary 3 Badges", + "Shopping Mall 9 F1", + "Shopping Mall 9 F2", + "Primary 5 Badges", + "Primary 7 Badges", + "Primary 8 Badges"); + + public static final List bw2MainGameShops = Arrays.asList( + 9, 11, 14, 15, 16, 18, 20, 21, 22, 23, 25, 26, 27, 28, 29, 30, 31 + ); + + public static final List bw2ShopNames = Arrays.asList( + "Primary 0 Badges", + "Primary 1 Badges", + "Primary 3 Badges", + "Primary 5 Badges", + "Primary 7 Badges", + "Primary 8 Badges", + "Accumula Secondary", + "Striaton Secondary (TMs)", + "Nacrene Secondary", + "Castelia Secondary", + "Nimbasa Secondary (TMs)", + "Driftveil Secondary", + "Mistralton Secondary (TMs)", + "Icirrus Secondary", + "Opelucid Secondary", + "Victory Road Secondary", + "Pokemon League Secondary", + "Lacunosa Secondary (TMs)", + "Undella Secondary", + "Black City/White Forest Secondary", + "Nacrene/Shopping Mall 9 X Items", + "Driftveil Herb Salesman", + "Driftveil Incense Salesman", + "Shopping Mall 9 F1", + "Shopping Mall 9 TMs", + "Shopping Mall 9 F2", + "Shopping Mall 9 F3 Left", + "Aspertia Secondary", + "Virbank Secondary", + "Humilau Secondary", + "Floccesy Secondary", + "Lentimas Secondary"); + + + public static final List evolutionItems = Arrays.asList(Items.sunStone, Items.moonStone, Items.fireStone, + Items.thunderStone, Items.waterStone, Items.leafStone, Items.shinyStone, Items.duskStone, Items.dawnStone, + Items.ovalStone, Items.kingsRock, Items.deepSeaTooth, Items.deepSeaScale, Items.metalCoat, Items.dragonScale, + Items.upgrade, Items.protector, Items.electirizer, Items.magmarizer, Items.dubiousDisc, Items.reaperCloth, + Items.razorClaw, Items.razorFang, Items.prismScale); + + public static final List bw1RequiredFieldTMs = Arrays.asList(2, 3, 5, 6, 9, 12, 13, 19, + 22, 24, 26, 29, 30, 35, 36, 39, 41, 46, 47, 50, 52, 53, 55, 58, 61, 63, 65, 66, 71, 80, 81, 84, 85, 86, 90, + 91, 92, 93); + + public static final List bw2RequiredFieldTMs = Arrays.asList(1, 2, 3, 5, 6, 12, 13, 19, + 22, 26, 28, 29, 30, 36, 39, 41, 46, 47, 50, 52, 53, 56, 58, 61, 63, 65, 66, 67, 69, 71, 80, 81, 84, 85, 86, + 90, 91, 92, 93); + + public static final List bw1EarlyRequiredHMMoves = Collections.singletonList(Moves.cut); + + public static final List bw2EarlyRequiredHMMoves = Collections.emptyList(); + + public static final List fieldMoves = Arrays.asList( + Moves.cut, Moves.fly, Moves.surf, Moves.strength, Moves.flash, Moves.dig, Moves.teleport, + Moves.waterfall, Moves.sweetScent, Moves.dive); + + public static final String shedinjaSpeciesLocator = "24010000"; + + public static final String runningShoesPrefix = "01D0012008BD002008BD63"; + + public static final String introGraphicPrefix = "5A0000010000001700000001000000", bw1IntroCryPrefix = "0021009101910291", bw2IntroCryLocator = "3D020000F8B51C1C"; + + public static final String typeEffectivenessTableLocator = "0404040404020400"; + + public static final String forceChallengeModeLocator = "816A406B0B1C07490022434090000858834201D1"; + + public static final String pickupTableLocator = "19005C00DD00"; + public static final int numberOfPickupItems = 29; + + public static final String friendshipValueForEvoLocator = "DC282FD3"; + + public static final String perfectOddsBranchLocator = "08DB002801D0012000E0"; + + public static final String lowHealthMusicLocator = "00D10127"; + + public static final List consumableHeldItems = setupAllConsumableItems(); + + private static List setupAllConsumableItems() { + List list = new ArrayList<>(Gen4Constants.consumableHeldItems); + list.addAll(Arrays.asList(Items.airBalloon, Items.redCard, Items.absorbBulb, Items.cellBattery, + Items.ejectButton, Items.fireGem, Items.waterGem, Items.electricGem, Items.grassGem, Items.iceGem, + Items.fightingGem, Items.poisonGem, Items.groundGem, Items.flyingGem, Items.psychicGem, Items.bugGem, + Items.rockGem, Items.ghostGem, Items.dragonGem, Items.darkGem, Items.steelGem, Items.normalGem)); + return Collections.unmodifiableList(list); + } + + public static final List allHeldItems = setupAllHeldItems(); + + private static List setupAllHeldItems() { + List list = new ArrayList<>(Gen4Constants.allHeldItems); + list.addAll(Arrays.asList(Items.airBalloon, Items.redCard, Items.absorbBulb, Items.cellBattery, + Items.ejectButton, Items.fireGem, Items.waterGem, Items.electricGem, Items.grassGem, Items.iceGem, + Items.fightingGem, Items.poisonGem, Items.groundGem, Items.flyingGem, Items.psychicGem, Items.bugGem, + Items.rockGem, Items.ghostGem, Items.dragonGem, Items.darkGem, Items.steelGem, Items.normalGem)); + list.addAll(Arrays.asList(Items.eviolite, Items.floatStone, Items.rockyHelmet, Items.ringTarget, Items.bindingBand)); + return Collections.unmodifiableList(list); + } + + public static final List generalPurposeConsumableItems = initializeGeneralPurposeConsumableItems(); + + private static List initializeGeneralPurposeConsumableItems() { + List list = new ArrayList<>(Gen4Constants.generalPurposeConsumableItems); + list.addAll(Arrays.asList(Items.redCard, Items.absorbBulb, Items.cellBattery, Items.ejectButton)); + return Collections.unmodifiableList(list); + } + + public static final List generalPurposeItems = initializeGeneralPurposeItems(); + + private static List initializeGeneralPurposeItems() { + List list = new ArrayList<>(Gen4Constants.generalPurposeItems); + list.addAll(Arrays.asList(Items.floatStone, Items.rockyHelmet)); + return Collections.unmodifiableList(list); + } + + public static final Map consumableTypeBoostingItems = initializeConsumableTypeBoostingItems(); + + private static Map initializeConsumableTypeBoostingItems() { + Map map = new HashMap<>(); + map.put(Type.FIRE, Items.fireGem); + map.put(Type.WATER, Items.waterGem); + map.put(Type.ELECTRIC, Items.electricGem); + map.put(Type.GRASS, Items.grassGem); + map.put(Type.ICE, Items.iceGem); + map.put(Type.FIGHTING, Items.fightingGem); + map.put(Type.POISON, Items.poisonGem); + map.put(Type.GROUND, Items.groundGem); + map.put(Type.FLYING, Items.flyingGem); + map.put(Type.PSYCHIC, Items.psychicGem); + map.put(Type.BUG, Items.bugGem); + map.put(Type.ROCK, Items.rockGem); + map.put(Type.GHOST, Items.ghostGem); + map.put(Type.DRAGON, Items.dragonGem); + map.put(Type.DARK, Items.darkGem); + map.put(Type.STEEL, Items.steelGem); + map.put(Type.NORMAL, Items.normalGem); + return Collections.unmodifiableMap(map); + } + + public static final Map> moveBoostingItems = initializeMoveBoostingItems(); + + private static Map> initializeMoveBoostingItems() { + Map> map = new HashMap<>(Gen4Constants.moveBoostingItems); + map.put(Moves.trick, Arrays.asList(Items.toxicOrb, Items.flameOrb, Items.ringTarget)); + map.put(Moves.switcheroo, Arrays.asList(Items.toxicOrb, Items.flameOrb, Items.ringTarget)); + + map.put(Moves.bind, Arrays.asList(Items.gripClaw, Items.bindingBand)); + map.put(Moves.clamp, Arrays.asList(Items.gripClaw, Items.bindingBand)); + map.put(Moves.fireSpin, Arrays.asList(Items.gripClaw, Items.bindingBand)); + map.put(Moves.magmaStorm, Arrays.asList(Items.gripClaw, Items.bindingBand)); + map.put(Moves.sandTomb, Arrays.asList(Items.gripClaw, Items.bindingBand)); + map.put(Moves.whirlpool, Arrays.asList(Items.gripClaw, Items.bindingBand)); + map.put(Moves.wrap, Arrays.asList(Items.gripClaw, Items.bindingBand)); + + map.put(Moves.hornLeech, Arrays.asList(Items.bigRoot)); + return Collections.unmodifiableMap(map); + } + + // None of these have new entries in Gen V. + public static final Map> abilityBoostingItems = Gen4Constants.abilityBoostingItems; + public static final Map> speciesBoostingItems = Gen4Constants.speciesBoostingItems; + public static final Map> typeBoostingItems = Gen4Constants.typeBoostingItems; + public static final Map weaknessReducingBerries = Gen4Constants.weaknessReducingBerries; + + private static Type[] constructTypeTable() { + Type[] table = new Type[256]; + table[0x00] = Type.NORMAL; + table[0x01] = Type.FIGHTING; + table[0x02] = Type.FLYING; + table[0x03] = Type.POISON; + table[0x04] = Type.GROUND; + table[0x05] = Type.ROCK; + table[0x06] = Type.BUG; + table[0x07] = Type.GHOST; + table[0x08] = Type.STEEL; + table[0x09] = Type.FIRE; + table[0x0A] = Type.WATER; + table[0x0B] = Type.GRASS; + table[0x0C] = Type.ELECTRIC; + table[0x0D] = Type.PSYCHIC; + table[0x0E] = Type.ICE; + table[0x0F] = Type.DRAGON; + table[0x10] = Type.DARK; + return table; + } + + public static byte typeToByte(Type type) { + if (type == null) { + return 0x00; // normal? + } + switch (type) { + case NORMAL: + return 0x00; + case FIGHTING: + return 0x01; + case FLYING: + return 0x02; + case POISON: + return 0x03; + case GROUND: + return 0x04; + case ROCK: + return 0x05; + case BUG: + return 0x06; + case GHOST: + return 0x07; + case FIRE: + return 0x09; + case WATER: + return 0x0A; + case GRASS: + return 0x0B; + case ELECTRIC: + return 0x0C; + case PSYCHIC: + return 0x0D; + case ICE: + return 0x0E; + case DRAGON: + return 0x0F; + case STEEL: + return 0x08; + case DARK: + return 0x10; + default: + return 0; // normal by default + } + } + + public static int getAreaDataEntryLength(int romType) { + if (romType == Type_BW) { + return bw1AreaDataEntryLength; + } else if (romType == Type_BW2) { + return bw2AreaDataEntryLength; + } + return 0; + } + + public static int getEncounterAreaCount(int romType) { + if (romType == Type_BW) { + return bw1EncounterAreaCount; + } else if (romType == Type_BW2) { + return bw2EncounterAreaCount; + } + return 0; + } + + public static int[] getWildFileToAreaMap(int romType) { + if (romType == Type_BW) { + return bw1WildFileToAreaMap; + } else if (romType == Type_BW2) { + return bw2WildFileToAreaMap; + } + return new int[0]; + } + + public static List getMainGameShops(int romType) { + if (romType == Type_BW) { + return bw1MainGameShops; + } else if (romType == Type_BW2) { + return bw2MainGameShops; + } + return new ArrayList<>(); + } + + public static List getIrregularFormes(int romType) { + if (romType == Type_BW) { + return bw1IrregularFormes; + } else if (romType == Type_BW2) { + return bw2IrregularFormes; + } + return new ArrayList<>(); + } + + public static int getFormeCount(int romType) { + if (romType == Type_BW) { + return bw1FormeCount; + } else if (romType == Type_BW2) { + return bw2FormeCount; + } + return 0; + } + + public static int getFormeOffset(int romType) { + if (romType == Type_BW) { + return bw1formeOffset; + } else if (romType == Type_BW2) { + return bw2formeOffset; + } + return 0; + } + + public static int getNonPokemonBattleSpriteCount(int romType) { + if (romType == Type_BW) { + return bw1NonPokemonBattleSpriteCount; + } else if (romType == Type_BW2) { + return bw2NonPokemonBattleSpriteCount; + } + return 0; + } + + public static String getFormeSuffix(int internalIndex, int romType) { + if (romType == Type_BW) { + return bw1FormeSuffixes.getOrDefault(internalIndex,""); + } else if (romType == Type_BW2) { + return bw2FormeSuffixes.getOrDefault(internalIndex,""); + } else { + return ""; + } + } + + private static Map setupFormeSuffixes(int gameVersion) { + Map formeSuffixes = new HashMap<>(); + if (gameVersion == Gen5Constants.Type_BW) { + formeSuffixes.put(Species.Gen5Formes.deoxysA,"-A"); + formeSuffixes.put(Species.Gen5Formes.deoxysD,"-D"); + formeSuffixes.put(Species.Gen5Formes.deoxysS,"-S"); + formeSuffixes.put(Species.Gen5Formes.wormadamS,"-S"); + formeSuffixes.put(Species.Gen5Formes.wormadamT,"-T"); + formeSuffixes.put(Species.Gen5Formes.shayminS,"-S"); + formeSuffixes.put(Species.Gen5Formes.giratinaO,"-O"); + formeSuffixes.put(Species.Gen5Formes.rotomH,"-H"); + formeSuffixes.put(Species.Gen5Formes.rotomW,"-W"); + formeSuffixes.put(Species.Gen5Formes.rotomFr,"-Fr"); + formeSuffixes.put(Species.Gen5Formes.rotomFa,"-Fa"); + formeSuffixes.put(Species.Gen5Formes.rotomM,"-M"); + formeSuffixes.put(Species.Gen5Formes.castformF,"-F"); + formeSuffixes.put(Species.Gen5Formes.castformW,"-W"); + formeSuffixes.put(Species.Gen5Formes.castformI,"-I"); + formeSuffixes.put(Species.Gen5Formes.basculinB,"-B"); + formeSuffixes.put(Species.Gen5Formes.darmanitanZ,"-Z"); + formeSuffixes.put(Species.Gen5Formes.meloettaP,"-P"); + } else if (gameVersion == Gen5Constants.Type_BW2) { + formeSuffixes.put(Species.Gen5Formes.deoxysA + bw2formeOffset,"-A"); + formeSuffixes.put(Species.Gen5Formes.deoxysD + bw2formeOffset,"-D"); + formeSuffixes.put(Species.Gen5Formes.deoxysS + bw2formeOffset,"-S"); + formeSuffixes.put(Species.Gen5Formes.wormadamS + bw2formeOffset,"-S"); + formeSuffixes.put(Species.Gen5Formes.wormadamT + bw2formeOffset,"-T"); + formeSuffixes.put(Species.Gen5Formes.shayminS + bw2formeOffset,"-S"); + formeSuffixes.put(Species.Gen5Formes.giratinaO + bw2formeOffset,"-O"); + formeSuffixes.put(Species.Gen5Formes.rotomH + bw2formeOffset,"-H"); + formeSuffixes.put(Species.Gen5Formes.rotomW + bw2formeOffset,"-W"); + formeSuffixes.put(Species.Gen5Formes.rotomFr + bw2formeOffset,"-Fr"); + formeSuffixes.put(Species.Gen5Formes.rotomFa + bw2formeOffset,"-Fa"); + formeSuffixes.put(Species.Gen5Formes.rotomM + bw2formeOffset,"-M"); + formeSuffixes.put(Species.Gen5Formes.castformF + bw2formeOffset,"-F"); + formeSuffixes.put(Species.Gen5Formes.castformW + bw2formeOffset,"-W"); + formeSuffixes.put(Species.Gen5Formes.castformI + bw2formeOffset,"-I"); + formeSuffixes.put(Species.Gen5Formes.basculinB + bw2formeOffset,"-B"); + formeSuffixes.put(Species.Gen5Formes.darmanitanZ + bw2formeOffset,"-Z"); + formeSuffixes.put(Species.Gen5Formes.meloettaP + bw2formeOffset,"-P"); + formeSuffixes.put(Species.Gen5Formes.kyuremW + bw2formeOffset,"-W"); + formeSuffixes.put(Species.Gen5Formes.kyuremB + bw2formeOffset,"-B"); + formeSuffixes.put(Species.Gen5Formes.tornadusT + bw2formeOffset,"-T"); + formeSuffixes.put(Species.Gen5Formes.thundurusT + bw2formeOffset,"-T"); + formeSuffixes.put(Species.Gen5Formes.landorusT + bw2formeOffset,"-T"); + } + + return formeSuffixes; + } + + private static Map> setupFormeSuffixesByBaseForme() { + Map> map = new HashMap<>(); + + Map deoxysMap = new HashMap<>(); + deoxysMap.put(1,"-A"); + deoxysMap.put(2,"-D"); + deoxysMap.put(3,"-S"); + map.put(Species.deoxys, deoxysMap); + + Map wormadamMap = new HashMap<>(); + wormadamMap.put(1,"-S"); + wormadamMap.put(2,"-T"); + map.put(Species.wormadam, wormadamMap); + + Map shayminMap = new HashMap<>(); + shayminMap.put(1,"-S"); + map.put(Species.shaymin, shayminMap); + + Map giratinaMap = new HashMap<>(); + giratinaMap.put(1,"-O"); + map.put(Species.giratina, giratinaMap); + + Map rotomMap = new HashMap<>(); + rotomMap.put(1,"-H"); + rotomMap.put(2,"-W"); + rotomMap.put(3,"-Fr"); + rotomMap.put(4,"-Fa"); + rotomMap.put(5,"-M"); + map.put(Species.rotom, rotomMap); + + Map castformMap = new HashMap<>(); + castformMap.put(1,"-F"); + castformMap.put(2,"-W"); + castformMap.put(3,"-I"); + map.put(Species.castform, castformMap); + + Map basculinMap = new HashMap<>(); + basculinMap.put(1,"-B"); + map.put(Species.basculin, basculinMap); + + Map darmanitanMap = new HashMap<>(); + darmanitanMap.put(1,"-Z"); + map.put(Species.darmanitan, darmanitanMap); + + Map meloettaMap = new HashMap<>(); + meloettaMap.put(1,"-P"); + map.put(Species.meloetta, meloettaMap); + + Map kyuremMap = new HashMap<>(); + kyuremMap.put(1,"-W"); + kyuremMap.put(2,"-B"); + map.put(Species.kyurem, kyuremMap); + + Map keldeoMap = new HashMap(); + keldeoMap.put(1,"-R"); + map.put(Species.keldeo, keldeoMap); + + Map tornadusMap = new HashMap<>(); + tornadusMap.put(1,"-T"); + map.put(Species.tornadus, tornadusMap); + + Map thundurusMap = new HashMap<>(); + thundurusMap.put(1,"-T"); + map.put(Species.thundurus, thundurusMap); + + Map landorusMap = new HashMap<>(); + landorusMap.put(1,"-T"); + map.put(Species.landorus, landorusMap); + + return map; + } + + private static Map setupDummyFormeSuffixes() { + Map m = new HashMap<>(); + m.put(0,""); + return m; + } + + private static Map> setupAbsolutePokeNumsByBaseForme() { + + Map> map = new HashMap<>(); + + Map deoxysMap = new HashMap<>(); + deoxysMap.put(1,Species.Gen5Formes.deoxysA); + deoxysMap.put(2,Species.Gen5Formes.deoxysD); + deoxysMap.put(3,Species.Gen5Formes.deoxysS); + map.put(Species.deoxys, deoxysMap); + + Map wormadamMap = new HashMap<>(); + wormadamMap.put(1,Species.Gen5Formes.wormadamS); + wormadamMap.put(2,Species.Gen5Formes.wormadamT); + map.put(Species.wormadam, wormadamMap); + + Map shayminMap = new HashMap<>(); + shayminMap.put(1,Species.Gen5Formes.shayminS); + map.put(Species.shaymin, shayminMap); + + Map giratinaMap = new HashMap<>(); + giratinaMap.put(1,Species.Gen5Formes.giratinaO); + map.put(Species.giratina, giratinaMap); + + Map rotomMap = new HashMap<>(); + rotomMap.put(1,Species.Gen5Formes.rotomH); + rotomMap.put(2,Species.Gen5Formes.rotomW); + rotomMap.put(3,Species.Gen5Formes.rotomFr); + rotomMap.put(4,Species.Gen5Formes.rotomFa); + rotomMap.put(5,Species.Gen5Formes.rotomM); + map.put(Species.rotom, rotomMap); + + Map castformMap = new HashMap<>(); + castformMap.put(1,Species.Gen5Formes.castformF); + castformMap.put(2,Species.Gen5Formes.castformW); + castformMap.put(3,Species.Gen5Formes.castformI); + map.put(Species.castform, castformMap); + + Map basculinMap = new HashMap<>(); + basculinMap.put(1,Species.Gen5Formes.basculinB); + map.put(Species.basculin, basculinMap); + + Map darmanitanMap = new HashMap<>(); + darmanitanMap.put(1,Species.Gen5Formes.darmanitanZ); + map.put(Species.darmanitan, darmanitanMap); + + Map meloettaMap = new HashMap<>(); + meloettaMap.put(1,Species.Gen5Formes.meloettaP); + map.put(Species.meloetta, meloettaMap); + + Map kyuremMap = new HashMap<>(); + kyuremMap.put(1,Species.Gen5Formes.kyuremW); + kyuremMap.put(2,Species.Gen5Formes.kyuremB); + map.put(Species.kyurem, kyuremMap); + + Map keldeoMap = new HashMap<>(); + keldeoMap.put(1,Species.Gen5Formes.keldeoCosmetic1); + map.put(Species.keldeo, keldeoMap); + + Map tornadusMap = new HashMap<>(); + tornadusMap.put(1,Species.Gen5Formes.tornadusT); + map.put(Species.tornadus, tornadusMap); + + Map thundurusMap = new HashMap<>(); + thundurusMap.put(1,Species.Gen5Formes.thundurusT); + map.put(Species.thundurus, thundurusMap); + + Map landorusMap = new HashMap<>(); + landorusMap.put(1,Species.Gen5Formes.landorusT); + map.put(Species.landorus, landorusMap); + + return map; + } + + private static Map setupDummyAbsolutePokeNums() { + Map m = new HashMap<>(); + m.put(255,0); + return m; + } + + public static ItemList allowedItems, nonBadItemsBW1, nonBadItemsBW2; + public static List regularShopItems, opShopItems; + + public static String blackBoxLegendaryCheckPrefix1 = "79F6BAEF07B0F0BDC046", blackBoxLegendaryCheckPrefix2 = "DEDB0020C04302B0F8BDC046", + whiteBoxLegendaryCheckPrefix1 = "00F0FEF8002070BD", whiteBoxLegendaryCheckPrefix2 = "64F62EF970BD0000"; + + static { + setupAllowedItems(); + } + + private static void setupAllowedItems() { + allowedItems = new ItemList(Items.revealGlass); + // Key items + version exclusives + allowedItems.banRange(Items.explorerKit, 76); + allowedItems.banRange(Items.dataCard01, 32); + allowedItems.banRange(Items.xtransceiverMale, 18); + allowedItems.banSingles(Items.libertyPass, Items.propCase, Items.dragonSkull, Items.lightStone, Items.darkStone); + // Unknown blank items or version exclusives + allowedItems.banRange(Items.tea, 3); + allowedItems.banRange(Items.unused120, 14); + // TMs & HMs - tms cant be held in gen5 + allowedItems.tmRange(Items.tm01, 92); + allowedItems.tmRange(Items.tm93, 3); + allowedItems.banRange(Items.tm01, 100); + allowedItems.banRange(Items.tm93, 3); + // Battle Launcher exclusives + allowedItems.banRange(Items.direHit2, 24); + + // non-bad items + // ban specific pokemon hold items, berries, apricorns, mail + nonBadItemsBW2 = allowedItems.copy(); + + nonBadItemsBW2.banSingles(Items.oddKeystone, Items.griseousOrb, Items.soulDew, Items.lightBall, + Items.oranBerry, Items.quickPowder, Items.passOrb); + nonBadItemsBW2.banRange(Items.growthMulch, 4); // mulch + nonBadItemsBW2.banRange(Items.adamantOrb, 2); // orbs + nonBadItemsBW2.banRange(Items.mail1, 12); // mails + nonBadItemsBW2.banRange(Items.figyBerry, 25); // berries without useful battle effects + nonBadItemsBW2.banRange(Items.luckyPunch, 4); // pokemon specific + nonBadItemsBW2.banRange(Items.redScarf, 5); // contest scarves + + // Ban the shards in BW1; even the maniac only gives you $200 for them, and they serve no other purpose. + nonBadItemsBW1 = nonBadItemsBW2.copy(); + nonBadItemsBW1.banRange(Items.redShard, 4); + + regularShopItems = new ArrayList<>(); + + regularShopItems.addAll(IntStream.rangeClosed(Items.ultraBall, Items.pokeBall).boxed().collect(Collectors.toList())); + regularShopItems.addAll(IntStream.rangeClosed(Items.potion, Items.revive).boxed().collect(Collectors.toList())); + regularShopItems.addAll(IntStream.rangeClosed(Items.superRepel, Items.repel).boxed().collect(Collectors.toList())); + + opShopItems = new ArrayList<>(); + + // "Money items" etc + opShopItems.add(Items.lavaCookie); + opShopItems.add(Items.berryJuice); + opShopItems.add(Items.rareCandy); + opShopItems.add(Items.oldGateau); + opShopItems.addAll(IntStream.rangeClosed(Items.blueFlute, Items.shoalShell).boxed().collect(Collectors.toList())); + opShopItems.addAll(IntStream.rangeClosed(Items.tinyMushroom, Items.nugget).boxed().collect(Collectors.toList())); + opShopItems.add(Items.rareBone); + opShopItems.addAll(IntStream.rangeClosed(Items.lansatBerry, Items.rowapBerry).boxed().collect(Collectors.toList())); + opShopItems.add(Items.luckyEgg); + opShopItems.add(Items.prettyFeather); + opShopItems.addAll(IntStream.rangeClosed(Items.balmMushroom, Items.casteliacone).boxed().collect(Collectors.toList())); + } + + public static ItemList getNonBadItems(int romType) { + if (romType == Gen5Constants.Type_BW2) { + return nonBadItemsBW2; + } else { + return nonBadItemsBW1; + } + } + + public static final Map balancedItemPrices = Stream.of(new Integer[][] { + // Skip item index 0. All prices divided by 10 + {Items.masterBall, 300}, + {Items.ultraBall, 120}, + {Items.greatBall, 60}, + {Items.pokeBall, 20}, + {Items.safariBall, 50}, + {Items.netBall, 100}, + {Items.diveBall, 100}, + {Items.nestBall, 100}, + {Items.repeatBall, 100}, + {Items.timerBall, 100}, + {Items.luxuryBall, 100}, + {Items.premierBall, 20}, + {Items.duskBall, 100}, + {Items.healBall, 30}, + {Items.quickBall, 100}, + {Items.cherishBall, 20}, + {Items.potion, 30}, + {Items.antidote, 10}, + {Items.burnHeal, 25}, + {Items.iceHeal, 25}, + {Items.awakening, 25}, + {Items.paralyzeHeal, 20}, + {Items.fullRestore, 300}, + {Items.maxPotion, 250}, + {Items.hyperPotion, 120}, + {Items.superPotion, 70}, + {Items.fullHeal, 60}, + {Items.revive, 150}, + {Items.maxRevive, 400}, + {Items.freshWater, 40}, + {Items.sodaPop, 60}, + {Items.lemonade, 70}, + {Items.moomooMilk, 80}, + {Items.energyPowder, 40}, + {Items.energyRoot, 110}, + {Items.healPowder, 45}, + {Items.revivalHerb, 280}, + {Items.ether, 300}, + {Items.maxEther, 450}, + {Items.elixir, 1500}, + {Items.maxElixir, 1800}, + {Items.lavaCookie, 45}, + {Items.berryJuice, 10}, + {Items.sacredAsh, 1000}, + {Items.hpUp, 980}, + {Items.protein, 980}, + {Items.iron, 980}, + {Items.carbos, 980}, + {Items.calcium, 980}, + {Items.rareCandy, 1000}, + {Items.ppUp, 980}, + {Items.zinc, 980}, + {Items.ppMax, 2490}, + {Items.oldGateau, 45}, + {Items.guardSpec, 70}, + {Items.direHit, 65}, + {Items.xAttack, 50}, + {Items.xDefense, 55}, + {Items.xSpeed, 35}, + {Items.xAccuracy, 95}, + {Items.xSpAtk, 35}, + {Items.xSpDef, 35}, + {Items.pokeDoll, 100}, + {Items.fluffyTail, 100}, + {Items.blueFlute, 2}, + {Items.yellowFlute, 2}, + {Items.redFlute, 2}, + {Items.blackFlute, 2}, + {Items.whiteFlute, 2}, + {Items.shoalSalt, 2}, + {Items.shoalShell, 2}, + {Items.redShard, 40}, + {Items.blueShard, 40}, + {Items.yellowShard, 40}, + {Items.greenShard, 40}, + {Items.superRepel, 50}, + {Items.maxRepel, 70}, + {Items.escapeRope, 55}, + {Items.repel, 35}, + {Items.sunStone, 300}, + {Items.moonStone, 300}, + {Items.fireStone, 300}, + {Items.thunderStone, 300}, + {Items.waterStone, 300}, + {Items.leafStone, 300}, + {Items.tinyMushroom, 50}, + {Items.bigMushroom, 500}, + {Items.pearl, 140}, + {Items.bigPearl, 750}, + {Items.stardust, 200}, + {Items.starPiece, 980}, + {Items.nugget, 1000}, + {Items.heartScale, 500}, + {Items.honey, 50}, + {Items.growthMulch, 20}, + {Items.dampMulch, 20}, + {Items.stableMulch, 20}, + {Items.gooeyMulch, 20}, + {Items.rootFossil, 500}, + {Items.clawFossil, 500}, + {Items.helixFossil, 500}, + {Items.domeFossil, 500}, + {Items.oldAmber, 800}, + {Items.armorFossil, 500}, + {Items.skullFossil, 500}, + {Items.rareBone, 1000}, + {Items.shinyStone, 300}, + {Items.duskStone, 300}, + {Items.dawnStone, 300}, + {Items.ovalStone, 300}, + {Items.oddKeystone, 210}, + {Items.griseousOrb, 1000}, + {Items.tea, 0}, // unused in Gen 5 + {Items.unused114, 0}, + {Items.autograph, 0}, // unused in Gen 5 + {Items.douseDrive, 100}, + {Items.shockDrive, 100}, + {Items.burnDrive, 100}, + {Items.chillDrive, 100}, + {Items.unused120, 0}, + {Items.pokemonBox, 0}, // unused in Gen 5 + {Items.medicinePocket, 0}, // unused in Gen 5 + {Items.tmCase, 0}, // unused in Gen 5 + {Items.candyJar, 0}, // unused in Gen 5 + {Items.powerUpPocket, 0}, // unused in Gen 5 + {Items.clothingTrunk, 0}, // unused in Gen 5 + {Items.catchingPocket, 0}, // unused in Gen 5 + {Items.battlePocket, 0}, // unused in Gen 5 + {Items.unused129, 0}, + {Items.unused130, 0}, + {Items.unused131, 0}, + {Items.unused132, 0}, + {Items.unused133, 0}, + {Items.sweetHeart, 15}, + {Items.adamantOrb, 1000}, + {Items.lustrousOrb, 1000}, + {Items.mail1, 5}, + {Items.mail2, 5}, + {Items.mail3, 5}, + {Items.mail4, 5}, + {Items.mail5, 5}, + {Items.mail6, 5}, + {Items.mail7, 5}, + {Items.mail8, 5}, + {Items.mail9, 5}, + {Items.mail10, 5}, + {Items.mail11, 5}, + {Items.mail12, 5}, + {Items.cheriBerry, 20}, + {Items.chestoBerry, 25}, + {Items.pechaBerry, 10}, + {Items.rawstBerry, 25}, + {Items.aspearBerry, 25}, + {Items.leppaBerry, 300}, + {Items.oranBerry, 5}, + {Items.persimBerry, 20}, + {Items.lumBerry, 50}, + {Items.sitrusBerry, 50}, + {Items.figyBerry, 10}, + {Items.wikiBerry, 10}, + {Items.magoBerry, 10}, + {Items.aguavBerry, 10}, + {Items.iapapaBerry, 10}, + {Items.razzBerry, 50}, + {Items.blukBerry, 50}, + {Items.nanabBerry, 50}, + {Items.wepearBerry, 50}, + {Items.pinapBerry, 50}, + {Items.pomegBerry, 50}, + {Items.kelpsyBerry, 50}, + {Items.qualotBerry, 50}, + {Items.hondewBerry, 50}, + {Items.grepaBerry, 50}, + {Items.tamatoBerry, 50}, + {Items.cornnBerry, 50}, + {Items.magostBerry, 50}, + {Items.rabutaBerry, 50}, + {Items.nomelBerry, 50}, + {Items.spelonBerry, 50}, + {Items.pamtreBerry, 50}, + {Items.watmelBerry, 50}, + {Items.durinBerry, 50}, + {Items.belueBerry, 50}, + {Items.occaBerry, 100}, + {Items.passhoBerry, 100}, + {Items.wacanBerry, 100}, + {Items.rindoBerry, 100}, + {Items.yacheBerry, 100}, + {Items.chopleBerry, 100}, + {Items.kebiaBerry, 100}, + {Items.shucaBerry, 100}, + {Items.cobaBerry, 100}, + {Items.payapaBerry, 100}, + {Items.tangaBerry, 100}, + {Items.chartiBerry, 100}, + {Items.kasibBerry, 100}, + {Items.habanBerry, 100}, + {Items.colburBerry, 100}, + {Items.babiriBerry, 100}, + {Items.chilanBerry, 100}, + {Items.liechiBerry, 100}, + {Items.ganlonBerry, 100}, + {Items.salacBerry, 100}, + {Items.petayaBerry, 100}, + {Items.apicotBerry, 100}, + {Items.lansatBerry, 100}, + {Items.starfBerry, 100}, + {Items.enigmaBerry, 100}, + {Items.micleBerry, 100}, + {Items.custapBerry, 100}, + {Items.jabocaBerry, 100}, + {Items.rowapBerry, 100}, + {Items.brightPowder, 300}, + {Items.whiteHerb, 100}, + {Items.machoBrace, 300}, + {Items.expShare, 600}, + {Items.quickClaw, 450}, + {Items.sootheBell, 100}, + {Items.mentalHerb, 100}, + {Items.choiceBand, 1000}, + {Items.kingsRock, 500}, + {Items.silverPowder, 200}, + {Items.amuletCoin, 1500}, + {Items.cleanseTag, 100}, + {Items.soulDew, 20}, + {Items.deepSeaTooth, 300}, + {Items.deepSeaScale, 300}, + {Items.smokeBall, 20}, + {Items.everstone, 20}, + {Items.focusBand, 300}, + {Items.luckyEgg, 1000}, + {Items.scopeLens, 500}, + {Items.metalCoat, 300}, + {Items.leftovers, 1000}, + {Items.dragonScale, 300}, + {Items.lightBall, 10}, + {Items.softSand, 200}, + {Items.hardStone, 200}, + {Items.miracleSeed, 200}, + {Items.blackGlasses, 200}, + {Items.blackBelt, 200}, + {Items.magnet, 200}, + {Items.mysticWater, 200}, + {Items.sharpBeak, 200}, + {Items.poisonBarb, 200}, + {Items.neverMeltIce, 200}, + {Items.spellTag, 200}, + {Items.twistedSpoon, 200}, + {Items.charcoal, 200}, + {Items.dragonFang, 200}, + {Items.silkScarf, 200}, + {Items.upgrade, 300}, + {Items.shellBell, 600}, + {Items.seaIncense, 200}, + {Items.laxIncense, 300}, + {Items.luckyPunch, 1}, + {Items.metalPowder, 1}, + {Items.thickClub, 50}, + {Items.leek, 20}, + {Items.redScarf, 10}, + {Items.blueScarf, 10}, + {Items.pinkScarf, 10}, + {Items.greenScarf, 10}, + {Items.yellowScarf, 10}, + {Items.wideLens, 150}, + {Items.muscleBand, 200}, + {Items.wiseGlasses, 200}, + {Items.expertBelt, 600}, + {Items.lightClay, 150}, + {Items.lifeOrb, 1000}, + {Items.powerHerb, 100}, + {Items.toxicOrb, 150}, + {Items.flameOrb, 150}, + {Items.quickPowder, 1}, + {Items.focusSash, 200}, + {Items.zoomLens, 150}, + {Items.metronome, 300}, + {Items.ironBall, 100}, + {Items.laggingTail, 100}, + {Items.destinyKnot, 150}, + {Items.blackSludge, 500}, + {Items.icyRock, 20}, + {Items.smoothRock, 20}, + {Items.heatRock, 20}, + {Items.dampRock, 20}, + {Items.gripClaw, 150}, + {Items.choiceScarf, 1000}, + {Items.stickyBarb, 150}, + {Items.powerBracer, 300}, + {Items.powerBelt, 300}, + {Items.powerLens, 300}, + {Items.powerBand, 300}, + {Items.powerAnklet, 300}, + {Items.powerWeight, 300}, + {Items.shedShell, 50}, + {Items.bigRoot, 150}, + {Items.choiceSpecs, 1000}, + {Items.flamePlate, 200}, + {Items.splashPlate, 200}, + {Items.zapPlate, 200}, + {Items.meadowPlate, 200}, + {Items.iciclePlate, 200}, + {Items.fistPlate, 200}, + {Items.toxicPlate, 200}, + {Items.earthPlate, 200}, + {Items.skyPlate, 200}, + {Items.mindPlate, 200}, + {Items.insectPlate, 200}, + {Items.stonePlate, 200}, + {Items.spookyPlate, 200}, + {Items.dracoPlate, 200}, + {Items.dreadPlate, 200}, + {Items.ironPlate, 200}, + {Items.oddIncense, 200}, + {Items.rockIncense, 200}, + {Items.fullIncense, 100}, + {Items.waveIncense, 200}, + {Items.roseIncense, 200}, + {Items.luckIncense, 1500}, + {Items.pureIncense, 100}, + {Items.protector, 300}, + {Items.electirizer, 300}, + {Items.magmarizer, 300}, + {Items.dubiousDisc, 300}, + {Items.reaperCloth, 300}, + {Items.razorClaw, 500}, + {Items.razorFang, 500}, + {Items.tm01, 1000}, + {Items.tm02, 1000}, + {Items.tm03, 1000}, + {Items.tm04, 1000}, + {Items.tm05, 1000}, + {Items.tm06, 1000}, + {Items.tm07, 2000}, + {Items.tm08, 1000}, + {Items.tm09, 1000}, + {Items.tm10, 1000}, + {Items.tm11, 2000}, + {Items.tm12, 1000}, + {Items.tm13, 1000}, + {Items.tm14, 2000}, + {Items.tm15, 2000}, + {Items.tm16, 2000}, + {Items.tm17, 1000}, + {Items.tm18, 2000}, + {Items.tm19, 1000}, + {Items.tm20, 2000}, + {Items.tm21, 1000}, + {Items.tm22, 1000}, + {Items.tm23, 1000}, + {Items.tm24, 1000}, + {Items.tm25, 2000}, + {Items.tm26, 1000}, + {Items.tm27, 1000}, + {Items.tm28, 1000}, + {Items.tm29, 1000}, + {Items.tm30, 1000}, + {Items.tm31, 1000}, + {Items.tm32, 1000}, + {Items.tm33, 2000}, + {Items.tm34, 1000}, + {Items.tm35, 1000}, + {Items.tm36, 1000}, + {Items.tm37, 2000}, + {Items.tm38, 2000}, + {Items.tm39, 1000}, + {Items.tm40, 1000}, + {Items.tm41, 1000}, + {Items.tm42, 1000}, + {Items.tm43, 1000}, + {Items.tm44, 1000}, + {Items.tm45, 1000}, + {Items.tm46, 1000}, + {Items.tm47, 1000}, + {Items.tm48, 1000}, + {Items.tm49, 1000}, + {Items.tm50, 1000}, + {Items.tm51, 1000}, + {Items.tm52, 1000}, + {Items.tm53, 1000}, + {Items.tm54, 1000}, + {Items.tm55, 1000}, + {Items.tm56, 1000}, + {Items.tm57, 1000}, + {Items.tm58, 1000}, + {Items.tm59, 1000}, + {Items.tm60, 1000}, + {Items.tm61, 1000}, + {Items.tm62, 1000}, + {Items.tm63, 1000}, + {Items.tm64, 1000}, + {Items.tm65, 1000}, + {Items.tm66, 1000}, + {Items.tm67, 1000}, + {Items.tm68, 2000}, + {Items.tm69, 1000}, + {Items.tm70, 1000}, + {Items.tm71, 1000}, + {Items.tm72, 1000}, + {Items.tm73, 1000}, + {Items.tm74, 1000}, + {Items.tm75, 1000}, + {Items.tm76, 1000}, + {Items.tm77, 1000}, + {Items.tm78, 1000}, + {Items.tm79, 1000}, + {Items.tm80, 1000}, + {Items.tm81, 1000}, + {Items.tm82, 1000}, + {Items.tm83, 1000}, + {Items.tm84, 1000}, + {Items.tm85, 1000}, + {Items.tm86, 1000}, + {Items.tm87, 1000}, + {Items.tm88, 1000}, + {Items.tm89, 1000}, + {Items.tm90, 1000}, + {Items.tm91, 1000}, + {Items.tm92, 1000}, + {Items.hm01, 0}, + {Items.hm02, 0}, + {Items.hm03, 0}, + {Items.hm04, 0}, + {Items.hm05, 0}, + {Items.hm06, 0}, + {Items.hm07, 0}, // unused in Gen 5 + {Items.hm08, 0}, // unused in Gen 5 + {Items.explorerKit, 0}, + {Items.lootSack, 0}, + {Items.ruleBook, 0}, + {Items.pokeRadar, 0}, + {Items.pointCard, 0}, + {Items.journal, 0}, + {Items.sealCase, 0}, + {Items.fashionCase, 0}, + {Items.sealBag, 0}, + {Items.palPad, 0}, + {Items.worksKey, 0}, + {Items.oldCharm, 0}, + {Items.galacticKey, 0}, + {Items.redChain, 0}, + {Items.townMap, 0}, + {Items.vsSeeker, 0}, + {Items.coinCase, 0}, + {Items.oldRod, 0}, + {Items.goodRod, 0}, + {Items.superRod, 0}, + {Items.sprayduck, 0}, + {Items.poffinCase, 0}, + {Items.bike, 0}, + {Items.suiteKey, 0}, + {Items.oaksLetter, 0}, + {Items.lunarWing, 0}, + {Items.memberCard, 0}, + {Items.azureFlute, 0}, + {Items.ssTicketJohto, 0}, + {Items.contestPass, 0}, + {Items.magmaStone, 0}, + {Items.parcelSinnoh, 0}, + {Items.coupon1, 0}, + {Items.coupon2, 0}, + {Items.coupon3, 0}, + {Items.storageKeySinnoh, 0}, + {Items.secretPotion, 0}, + {Items.vsRecorder, 0}, + {Items.gracidea, 0}, + {Items.secretKeySinnoh, 0}, + {Items.apricornBox, 0}, + {Items.unownReport, 0}, + {Items.berryPots, 0}, + {Items.dowsingMachine, 0}, + {Items.blueCard, 0}, + {Items.slowpokeTail, 0}, + {Items.clearBell, 0}, + {Items.cardKeyJohto, 0}, + {Items.basementKeyJohto, 0}, + {Items.squirtBottle, 0}, + {Items.redScale, 0}, + {Items.lostItem, 0}, + {Items.pass, 0}, + {Items.machinePart, 0}, + {Items.silverWing, 0}, + {Items.rainbowWing, 0}, + {Items.mysteryEgg, 0}, + {Items.redApricorn, 2}, + {Items.blueApricorn, 2}, + {Items.yellowApricorn, 2}, + {Items.greenApricorn, 2}, + {Items.pinkApricorn, 2}, + {Items.whiteApricorn, 2}, + {Items.blackApricorn, 2}, + {Items.fastBall, 30}, + {Items.levelBall, 30}, + {Items.lureBall, 30}, + {Items.heavyBall, 30}, + {Items.loveBall, 30}, + {Items.friendBall, 30}, + {Items.moonBall, 30}, + {Items.sportBall, 30}, + {Items.parkBall, 0}, + {Items.photoAlbum, 0}, + {Items.gbSounds, 0}, + {Items.tidalBell, 0}, + {Items.rageCandyBar, 1500}, + {Items.dataCard01, 0}, + {Items.dataCard02, 0}, + {Items.dataCard03, 0}, + {Items.dataCard04, 0}, + {Items.dataCard05, 0}, + {Items.dataCard06, 0}, + {Items.dataCard07, 0}, + {Items.dataCard08, 0}, + {Items.dataCard09, 0}, + {Items.dataCard10, 0}, + {Items.dataCard11, 0}, + {Items.dataCard12, 0}, + {Items.dataCard13, 0}, + {Items.dataCard14, 0}, + {Items.dataCard15, 0}, + {Items.dataCard16, 0}, + {Items.dataCard17, 0}, + {Items.dataCard18, 0}, + {Items.dataCard19, 0}, + {Items.dataCard20, 0}, + {Items.dataCard21, 0}, + {Items.dataCard22, 0}, + {Items.dataCard23, 0}, + {Items.dataCard24, 0}, + {Items.dataCard25, 0}, + {Items.dataCard26, 0}, + {Items.dataCard27, 0}, + {Items.jadeOrb, 0}, + {Items.lockCapsule, 0}, + {Items.redOrb, 0}, + {Items.blueOrb, 0}, + {Items.enigmaStone, 0}, + {Items.prismScale, 300}, + {Items.eviolite, 1000}, + {Items.floatStone, 100}, + {Items.rockyHelmet, 600}, + {Items.airBalloon, 100}, + {Items.redCard, 100}, + {Items.ringTarget, 100}, + {Items.bindingBand, 200}, + {Items.absorbBulb, 100}, + {Items.cellBattery, 100}, + {Items.ejectButton, 100}, + {Items.fireGem, 100}, + {Items.waterGem, 100}, + {Items.electricGem, 100}, + {Items.grassGem, 100}, + {Items.iceGem, 100}, + {Items.fightingGem, 100}, + {Items.poisonGem, 100}, + {Items.groundGem, 100}, + {Items.flyingGem, 100}, + {Items.psychicGem, 100}, + {Items.bugGem, 100}, + {Items.rockGem, 100}, + {Items.ghostGem, 100}, + {Items.dragonGem, 100}, + {Items.darkGem, 100}, + {Items.steelGem, 100}, + {Items.normalGem, 100}, + {Items.healthFeather, 300}, + {Items.muscleFeather, 300}, + {Items.resistFeather, 300}, + {Items.geniusFeather, 300}, + {Items.cleverFeather, 300}, + {Items.swiftFeather, 300}, + {Items.prettyFeather, 20}, + {Items.coverFossil, 500}, + {Items.plumeFossil, 500}, + {Items.libertyPass, 0}, + {Items.passOrb, 20}, + {Items.dreamBall, 100}, + {Items.pokeToy, 100}, + {Items.propCase, 0}, + {Items.dragonSkull, 0}, + {Items.balmMushroom, 0}, + {Items.bigNugget, 0}, + {Items.pearlString, 0}, + {Items.cometShard, 0}, + {Items.relicCopper, 0}, + {Items.relicSilver, 0}, + {Items.relicGold, 0}, + {Items.relicVase, 0}, + {Items.relicBand, 0}, + {Items.relicStatue, 0}, + {Items.relicCrown, 0}, + {Items.casteliacone, 45}, + {Items.direHit2, 0}, + {Items.xSpeed2, 0}, + {Items.xSpAtk2, 0}, + {Items.xSpDef2, 0}, + {Items.xDefense2, 0}, + {Items.xAttack2, 0}, + {Items.xAccuracy2, 0}, + {Items.xSpeed3, 0}, + {Items.xSpAtk3, 0}, + {Items.xSpDef3, 0}, + {Items.xDefense3, 0}, + {Items.xAttack3, 0}, + {Items.xAccuracy3, 0}, + {Items.xSpeed6, 0}, + {Items.xSpAtk6, 0}, + {Items.xSpDef6, 0}, + {Items.xDefense6, 0}, + {Items.xAttack6, 0}, + {Items.xAccuracy6, 0}, + {Items.abilityUrge, 0}, + {Items.itemDrop, 0}, + {Items.itemUrge, 0}, + {Items.resetUrge, 0}, + {Items.direHit3, 0}, + {Items.lightStone, 0}, + {Items.darkStone, 0}, + {Items.tm93, 1000}, + {Items.tm94, 1000}, + {Items.tm95, 1000}, + {Items.xtransceiverMale, 0}, + {Items.unused622, 0}, + {Items.gram1, 0}, + {Items.gram2, 0}, + {Items.gram3, 0}, + {Items.xtransceiverFemale, 0}, + {Items.medalBox, 0}, + {Items.dNASplicersFuse, 0}, + {Items.dNASplicersSeparate, 0}, + {Items.permit, 0}, + {Items.ovalCharm, 0}, + {Items.shinyCharm, 0}, + {Items.plasmaCard, 0}, + {Items.grubbyHanky, 0}, + {Items.colressMachine, 0}, + {Items.droppedItemCurtis, 0}, + {Items.droppedItemYancy, 0}, + {Items.revealGlass, 0} + }).collect(Collectors.toMap(kv -> kv[0], kv -> kv[1])); + + /* @formatter:off */ + @SuppressWarnings("unused") + private static final int[][] habitatListEntries = { + { 104, 105 }, // Route 4 + { 124 }, // Route 15 + { 134 }, // Route 21 + { 84, 85, 86 }, // Clay Tunnel + { 23, 24, 25, 26 }, // Twist Mountain + { 97 }, // Village Bridge + { 27, 28, 29, 30 }, // Dragonspiral Tower + { 81, 82, 83 }, // Relic Passage + { 106 }, // Route 5* + { 125 }, // Route 16* + { 98 }, // Marvelous Bridge + { 123 }, // Abundant Shrine + { 132 }, // Undella Town + { 107 }, // Route 6 + { 43 }, // Undella Bay + { 102, 103 }, // Wellspring Cave + { 95 }, // Nature Preserve + { 127 }, // Route 18 + { 32, 33, 34, 35, 36 }, // Giant Chasm + { 111 }, // Route 7 + { 31, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 }, // Victory Road + { 12, 13, 14, 15, 16, 17, 18, 19 }, // Relic Castle + { 0 }, // Striation City + { 128 }, // Route 19 + { 3 }, // Aspertia City + { 116 }, // Route 8* + { 44, 45 }, // Floccesy Ranch + { 61, 62, 63, 64, 65, 66, 67, 68, 69, 70 }, // Strange House + { 129 }, // Route 20 + { 4 }, // Virbank City + { 37, 38, 39, 40, 41 }, // Castelia Sewers + { 118 }, // Route 9 + { 46, 47 }, // Virbank Complex + { 42 }, // P2 Laboratory + { 1 }, // Castelia City + { 8, 9 }, // Pinwheel Forest + { 5 }, // Humilau City + { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 }, // Reversal Mountain + { 6, 7 }, // Dreamyard + { 112, 113, 114, 115 }, // Celestial Tower + { 130 }, // Route 22 + { 10, 11 }, // Desert Resort + { 119 }, // Route 11 + { 133 }, // Route 17 + { 99 }, // Route 1 + { 131 }, // Route 23 + { 2 }, // Icirrus City* + { 120 }, // Route 12 + { 100 }, // Route 2 + { 108, 109 }, // Mistralton Cave + { 121 }, // Route 13 + { 101 }, // Route 3 + { 117 }, // Moor of Icirrus* + { 96 }, // Driftveil Drawbridge + { 93, 94 }, // Seaside Cave + { 126 }, // Lostlorn Forest + { 122 }, // Route 14 + { 20, 21, 22 }, // Chargestone Cave + }; + + private static final int[] bw1WildFileToAreaMap = { + 2, + 6, + 8, + 18, 18, + 19, 19, + 20, 20, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, // lol + 22, + 23, 23, 23, + 24, 24, 24, 24, + 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 27, 27, 27, 27, + 29, + 36, + 57, + 59, + 60, + 38, + 39, + 40, + 30, 30, + 41, + 42, + 43, + 31, 31, 31, + 44, + 33, 33, 33, 33, + 45, + 34, + 46, + 32, 32, 32, + 47, 47, + 48, + 49, + 50, + 51, + 35, + 52, + 53, + 37, + 55, + 12, + 54, + }; + + private static final int[] bw2WildFileToAreaMap = { + 2, + 4, + 8, + 59, + 61, + 63, + 19, 19, + 20, 20, + 21, 21, + 22, 22, 22, 22, 22, 22, 22, 22, + 24, 24, 24, + 25, 25, 25, 25, + 26, 26, 26, 26, + 76, + 27, 27, 27, 27, 27, + 70, 70, 70, 70, 70, + 29, + 35, + 71, 71, + 72, 72, + 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, + 77, 77, 77, + 79, 79, 79, 79, 79, 79, 79, 79, 79, + 78, 78, + -1, // Nature Preserve (not on map) + 55, + 57, + 58, + 37, + 38, + 39, + 30, 30, + 40, 40, + 41, + 42, + 31, 31, 31, + 43, + 32, 32, 32, 32, + 44, + 33, + 45, + 46, + 47, + 48, + 49, + 34, + 50, + 51, + 36, + 53, + 66, + 67, + 69, + 75, + 12, + 52, + 68, + }; + + public static void tagTrainersBW(List trs) { + // We use different Gym IDs to cheat the system for the 3 n00bs + // Chili, Cress, and Cilan + // Cilan can be GYM1, then Chili is GYM9 and Cress GYM10 + // Also their *trainers* are GYM11 lol + + // Gym Trainers + tag(trs, "GYM11", 0x09, 0x0A); + tag(trs, "GYM2", 0x56, 0x57, 0x58); + tag(trs, "GYM3", 0xC4, 0xC6, 0xC7, 0xC8); + tag(trs, "GYM4", 0x42, 0x43, 0x44, 0x45); + tag(trs, "GYM5", 0xC9, 0xCA, 0xCB, 0x5F, 0xA8); + tag(trs, "GYM6", 0x7D, 0x7F, 0x80, 0x46, 0x47); + tag(trs, "GYM7", 0xD7, 0xD8, 0xD9, 0xD4, 0xD5, 0xD6); + tag(trs, "GYM8", 0x109, 0x10A, 0x10F, 0x10E, 0x110, 0x10B, 0x113, 0x112); + + // Gym Leaders + tag(trs, 0x0C, "GYM1-LEADER"); // Cilan + tag(trs, 0x0B, "GYM9-LEADER"); // Chili + tag(trs, 0x0D, "GYM10-LEADER"); // Cress + tag(trs, 0x15, "GYM2-LEADER"); // Lenora + tag(trs, 0x16, "GYM3-LEADER"); // Burgh + tag(trs, 0x17, "GYM4-LEADER"); // Elesa + tag(trs, 0x18, "GYM5-LEADER"); // Clay + tag(trs, 0x19, "GYM6-LEADER"); // Skyla + tag(trs, 0x83, "GYM7-LEADER"); // Brycen + tag(trs, 0x84, "GYM8-LEADER"); // Iris or Drayden + tag(trs, 0x85, "GYM8-LEADER"); // Iris or Drayden + + // Elite 4 + tag(trs, 0xE4, "ELITE1"); // Shauntal + tag(trs, 0xE6, "ELITE2"); // Grimsley + tag(trs, 0xE7, "ELITE3"); // Caitlin + tag(trs, 0xE5, "ELITE4"); // Marshal + + // Elite 4 R2 + tag(trs, 0x233, "ELITE1"); // Shauntal + tag(trs, 0x235, "ELITE2"); // Grimsley + tag(trs, 0x236, "ELITE3"); // Caitlin + tag(trs, 0x234, "ELITE4"); // Marshal + tag(trs, 0x197, "CHAMPION"); // Alder + + // Ubers? + tag(trs, 0x21E, "UBER"); // Game Freak Guy + tag(trs, 0x237, "UBER"); // Cynthia + tag(trs, 0xE8, "UBER"); // Ghetsis + tag(trs, 0x24A, "UBER"); // N-White + tag(trs, 0x24B, "UBER"); // N-Black + + // Rival - Cheren + tagRivalBW(trs, "RIVAL1", 0x35); + tagRivalBW(trs, "RIVAL2", 0x11F); + tagRivalBW(trs, "RIVAL3", 0x38); // used for 3rd battle AND tag battle + tagRivalBW(trs, "RIVAL4", 0x193); + tagRivalBW(trs, "RIVAL5", 0x5A); // 5th battle & 2nd tag battle + tagRivalBW(trs, "RIVAL6", 0x21B); + tagRivalBW(trs, "RIVAL7", 0x24C); + tagRivalBW(trs, "RIVAL8", 0x24F); + + // Rival - Bianca + tagRivalBW(trs, "FRIEND1", 0x3B); + tagRivalBW(trs, "FRIEND2", 0x1F2); + tagRivalBW(trs, "FRIEND3", 0x1FB); + tagRivalBW(trs, "FRIEND4", 0x1EB); + tagRivalBW(trs, "FRIEND5", 0x1EE); + tagRivalBW(trs, "FRIEND6", 0x252); + + // N + tag(trs, "NOTSTRONG", 64); + tag(trs, "STRONG", 65, 89, 218); + } + + public static void tagTrainersBW2(List trs) { + // Use GYM9/10/11 for the retired Chili/Cress/Cilan. + // Lenora doesn't have a team, or she'd be 12. + // Likewise for Brycen + + // Some trainers have TWO teams because of Challenge Mode + // I believe this is limited to Gym Leaders, E4, Champ... + // The "Challenge Mode" teams have levels at similar to regular, + // but have the normal boost applied too. + + // Gym Trainers + tag(trs, "GYM1", 0xab, 0xac); + tag(trs, "GYM2", 0xb2, 0xb3); + tag(trs, "GYM3", 0x2de, 0x2df, 0x2e0, 0x2e1); + // GYM4: old gym site included to give the city a theme + tag(trs, "GYM4", 0x26d, 0x94, 0xcf, 0xd0, 0xd1); // 0x94 might be 0x324 + tag(trs, "GYM5", 0x13f, 0x140, 0x141, 0x142, 0x143, 0x144, 0x145); + tag(trs, "GYM6", 0x95, 0x96, 0x97, 0x98, 0x14c); + tag(trs, "GYM7", 0x17d, 0x17e, 0x17f, 0x180, 0x181); + tag(trs, "GYM8", 0x15e, 0x15f, 0x160, 0x161, 0x162, 0x163); + + // Gym Leaders + // Order: Normal, Challenge Mode + // All the challenge mode teams are near the end of the ROM + // which makes things a bit easier. + tag(trs, "GYM1-LEADER", 0x9c, 0x2fc); // Cheren + tag(trs, "GYM2-LEADER", 0x9d, 0x2fd); // Roxie + tag(trs, "GYM3-LEADER", 0x9a, 0x2fe); // Burgh + tag(trs, "GYM4-LEADER", 0x99, 0x2ff); // Elesa + tag(trs, "GYM5-LEADER", 0x9e, 0x300); // Clay + tag(trs, "GYM6-LEADER", 0x9b, 0x301); // Skyla + tag(trs, "GYM7-LEADER", 0x9f, 0x302); // Drayden + tag(trs, "GYM8-LEADER", 0xa0, 0x303); // Marlon + + // Elite 4 / Champion + // Order: Normal, Challenge Mode, Rematch, Rematch Challenge Mode + tag(trs, "ELITE1", 0x26, 0x304, 0x8f, 0x309); + tag(trs, "ELITE2", 0x28, 0x305, 0x91, 0x30a); + tag(trs, "ELITE3", 0x29, 0x307, 0x92, 0x30c); + tag(trs, "ELITE4", 0x27, 0x306, 0x90, 0x30b); + tag(trs, "CHAMPION", 0x155, 0x308, 0x218, 0x30d); + + // Rival - Hugh + tagRivalBW(trs, "RIVAL1", 0xa1); // Start + tagRivalBW(trs, "RIVAL2", 0xa6); // Floccessy Ranch + tagRivalBW(trs, "RIVAL3", 0x24c); // Tag Battles in the sewers + tagRivalBW(trs, "RIVAL4", 0x170); // Tag Battle on the Plasma Frigate + tagRivalBW(trs, "RIVAL5", 0x17a); // Undella Town 1st visit + tagRivalBW(trs, "RIVAL6", 0x2bd); // Lacunosa Town Tag Battle + tagRivalBW(trs, "RIVAL7", 0x31a); // 2nd Plasma Frigate Tag Battle + tagRivalBW(trs, "RIVAL8", 0x2ac); // Victory Road + tagRivalBW(trs, "RIVAL9", 0x2b5); // Undella Town Post-E4 + tagRivalBW(trs, "RIVAL10", 0x2b8); // Driftveil Post-Undella-Battle + + // Tag Battle with Opposite Gender Hero + tagRivalBW(trs, "FRIEND1", 0x168); + tagRivalBW(trs, "FRIEND1", 0x16b); + + // Tag/PWT Battles with Cheren + tag(trs, "GYM1", 0x173, 0x278, 0x32E); + + // The Restaurant Brothers + tag(trs, "GYM9-LEADER", 0x1f0); // Cilan + tag(trs, "GYM10-LEADER", 0x1ee); // Chili + tag(trs, "GYM11-LEADER", 0x1ef); // Cress + + // Themed Trainers + tag(trs, "THEMED:ZINZOLIN-STRONG", 0x2c0, 0x248, 0x15b, 0x1f1); + tag(trs, "THEMED:COLRESS-STRONG", 0x166, 0x158, 0x32d, 0x32f); + tag(trs, "THEMED:SHADOW1", 0x247, 0x15c, 0x2af); + tag(trs, "THEMED:SHADOW2", 0x1f2, 0x2b0); + tag(trs, "THEMED:SHADOW3", 0x1f3, 0x2b1); + + // Uber-Trainers + // There are *fourteen* ubers of 17 allowed (incl. the champion) + // It's a rather stacked game... + tag(trs, 0x246, "UBER"); // Alder + tag(trs, 0x1c8, "UBER"); // Cynthia + tag(trs, 0xca, "UBER"); // Benga/BlackTower + tag(trs, 0xc9, "UBER"); // Benga/WhiteTreehollow + tag(trs, 0x5, "UBER"); // N/Zekrom + tag(trs, 0x6, "UBER"); // N/Reshiram + tag(trs, 0x30e, "UBER"); // N/Spring + tag(trs, 0x30f, "UBER"); // N/Summer + tag(trs, 0x310, "UBER"); // N/Autumn + tag(trs, 0x311, "UBER"); // N/Winter + tag(trs, 0x159, "UBER"); // Ghetsis + tag(trs, 0x8c, "UBER"); // Game Freak Guy + tag(trs, 0x24f, "UBER"); // Game Freak Leftovers Guy + + } + + private static void tagRivalBW(List allTrainers, String tag, int offset) { + allTrainers.get(offset - 1).tag = tag + "-0"; + allTrainers.get(offset).tag = tag + "-1"; + allTrainers.get(offset + 1).tag = tag + "-2"; + + } + + private static void tag(List allTrainers, int number, String tag) { + if (allTrainers.size() > (number - 1)) { + allTrainers.get(number - 1).tag = tag; + } + } + + private static void tag(List allTrainers, String tag, int... numbers) { + for (int num : numbers) { + if (allTrainers.size() > (num - 1)) { + allTrainers.get(num - 1).tag = tag; + } + } + } + + public static void setMultiBattleStatusBW(List trs) { + // 62 + 63: Multi Battle with Team Plasma Grunts in Wellspring Cave w/ Cheren + // 401 + 402: Double Battle with Preschooler Sarah and Preschooler Billy + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.ALWAYS, 62, 63, 401, 402); + } + + public static void setMultiBattleStatusBW2(List trs, boolean isBlack2) { + // 342 + 356: Multi Battle with Team Plasma Grunts in Castelia Sewers w/ Hugh + // 347 + 797: Multi Battle with Team Plasma Zinzolin and Team Plasma Grunt on Plasma Frigate w/ Hugh + // 374 + 375: Multi Battle with Team Plasma Grunts on Plasma Frigate w/ Cheren + // 376 + 377: Multi Battle with Team Plasma Grunts on Plasma Frigate w/ Hugh + // 494 + 495 + 496: Cilan, Chili, and Cress all participate in a Multi Battle + // 614 + 615: Double Battle with Veteran Claude and Veteran Cecile + // 643 + 644: Double Battle with Veteran Sinan and Veteran Rosaline + // 704 + 705: Multi Battle with Team Plasma Zinzolin and Team Plasma Grunt in Lacunosa Town w/ Hugh + // 798 + 799: Multi Battle with Team Plasma Grunts on Plasma Frigate w/ Hugh + // 807 + 809: Double Battle with Team Plasma Grunts on Plasma Frigate + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.ALWAYS, 342, 347, 356, 374, 375, 376, 377, 494, + 495, 496, 614, 615, 643, 644, 704, 705, 797, 798, 799, 807, 809 + ); + + // 513/788 + 522: Potential Double Battle with Backpacker Kiyo (513 in B2, 788 in W2) and Hiker Markus + // 519/786 + 520/787: Potential Double Battle with Ace Trainer Ray (519 in W2, 786 in B2) and Ace Trainer Cora (520 in B2, 787 in W2) + // 602 + 603: Potential Double Battle with Ace Trainer Webster and Ace Trainer Shanta + // 790 + 791: Potential Double Battle with Nursery Aide Rosalyn and Preschooler Ike + // 792 + 793: Potential Double Battle with Youngster Henley and Lass Helia + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.POTENTIAL, 513, 522, 602, 603, 788, 790, 791, 792, 793); + + if (isBlack2) { + // 789 + 521: Double Battle with Backpacker Kumiko and Hiker Jared + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.ALWAYS, 521, 789); + + // 786 + 520: Potential Double Batlte with Ace Trainer Ray and Ace Trainer Cora + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.POTENTIAL, 520, 786); + } else { + // 514 + 521: Potential Double Battle with Backpacker Kumiko and Hiker Jared + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.POTENTIAL, 514, 521); + + // 519 + 787: Double Battle with Ace Trainer Ray and Ace Trainer Cora + setMultiBattleStatus(trs, Trainer.MultiBattleStatus.ALWAYS, 519, 787); + } + } + + private static void setMultiBattleStatus(List allTrainers, Trainer.MultiBattleStatus status, int... numbers) { + for (int num : numbers) { + if (allTrainers.size() > (num - 1)) { + allTrainers.get(num - 1).multiBattleStatus = status; + } + } + } + +} diff --git a/src/com/pkrandom/constants/Gen6Constants.java b/src/com/pkrandom/constants/Gen6Constants.java new file mode 100644 index 0000000..8a4be9d --- /dev/null +++ b/src/com/pkrandom/constants/Gen6Constants.java @@ -0,0 +1,2171 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Gen6Constants.java - Constants for X/Y/Omega Ruby/Alpha Sapphire --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.pokemon.*; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class Gen6Constants { + + public static final int Type_XY = N3DSConstants.Type_XY; + public static final int Type_ORAS = N3DSConstants.Type_ORAS; + + public static final int pokemonCount = 721; + private static final int xyFormeCount = 77, orasFormeCount = 104; + private static final int orasformeMovesetOffset = 35; + + public static final List actuallyCosmeticForms = Arrays.asList( + Species.Gen6Formes.cherrimCosmetic1, + Species.Gen6Formes.keldeoCosmetic1, + Species.Gen6Formes.furfrouCosmetic1, Species.Gen6Formes.furfrouCosmetic2, + Species.Gen6Formes.furfrouCosmetic3, Species.Gen6Formes.furfrouCosmetic4, + Species.Gen6Formes.furfrouCosmetic5, Species.Gen6Formes.furfrouCosmetic6, + Species.Gen6Formes.furfrouCosmetic7, Species.Gen6Formes.furfrouCosmetic8, + Species.Gen6Formes.furfrouCosmetic9, + Species.Gen6Formes.pumpkabooCosmetic1, Species.Gen6Formes.pumpkabooCosmetic2, + Species.Gen6Formes.pumpkabooCosmetic3, + Species.Gen6Formes.gourgeistCosmetic1, Species.Gen6Formes.gourgeistCosmetic2, + Species.Gen6Formes.gourgeistCosmetic3, + Species.Gen6Formes.floetteCosmetic1, Species.Gen6Formes.floetteCosmetic2, + Species.Gen6Formes.floetteCosmetic3, Species.Gen6Formes.floetteCosmetic4, + Species.Gen6Formes.pikachuCosmetic1, Species.Gen6Formes.pikachuCosmetic2, + Species.Gen6Formes.pikachuCosmetic3, Species.Gen6Formes.pikachuCosmetic4, + Species.Gen6Formes.pikachuCosmetic5, Species.Gen6Formes.pikachuCosmetic6 // Cosplay Pikachu + ); + + public static final String criesTablePrefixXY = "60000A006B000A0082000A003D010A00"; + + public static final String introPokemonModelOffsetXY = "01000400020002000200000003000000"; + public static final String introInitialCryOffset1XY = "3AFEFFEB000055E31400D40507005001"; + public static final String introInitialCryOffset2XY = "0800A0E110FEFFEB000057E31550C405"; + public static final String introInitialCryOffset3XY = "0020E0E30310A0E1E4FDFFEB0000A0E3"; + public static final String introRepeatedCryOffsetXY = "1080BDE800002041000000008D001000"; + + public static final Map> speciesToMegaStoneXY = setupSpeciesToMegaStone(Type_XY); + public static final Map> speciesToMegaStoneORAS = setupSpeciesToMegaStone(Type_ORAS); + + public static final Map formeSuffixes = setupFormeSuffixes(); + public static final Map dummyFormeSuffixes = setupDummyFormeSuffixes(); + public static final Map> formeSuffixesByBaseForme = setupFormeSuffixesByBaseForme(); + + public static String getFormeSuffixByBaseForme(int baseForme, int formNum) { + return formeSuffixesByBaseForme.getOrDefault(baseForme,dummyFormeSuffixes).getOrDefault(formNum,""); + } + + private static final List xyIrregularFormes = Arrays.asList( + Species.Gen6Formes.castformF, Species.Gen6Formes.castformW, Species.Gen6Formes.castformI, + Species.Gen6Formes.darmanitanZ, + Species.Gen6Formes.meloettaP, + Species.Gen6Formes.kyuremW, + Species.Gen6Formes.kyuremB, + Species.Gen6Formes.gengarMega, + Species.Gen6Formes.gardevoirMega, + Species.Gen6Formes.ampharosMega, + Species.Gen6Formes.venusaurMega, + Species.Gen6Formes.charizardMegaX, Species.Gen6Formes.charizardMegaY, + Species.Gen6Formes.mewtwoMegaX, Species.Gen6Formes.mewtwoMegaY, + Species.Gen6Formes.blazikenMega, + Species.Gen6Formes.medichamMega, + Species.Gen6Formes.houndoomMega, + Species.Gen6Formes.aggronMega, + Species.Gen6Formes.banetteMega, + Species.Gen6Formes.tyranitarMega, + Species.Gen6Formes.scizorMega, + Species.Gen6Formes.pinsirMega, + Species.Gen6Formes.aerodactylMega, + Species.Gen6Formes.lucarioMega, + Species.Gen6Formes.abomasnowMega, + Species.Gen6Formes.aegislashB, + Species.Gen6Formes.blastoiseMega, + Species.Gen6Formes.kangaskhanMega, + Species.Gen6Formes.gyaradosMega, + Species.Gen6Formes.absolMega, + Species.Gen6Formes.alakazamMega, + Species.Gen6Formes.heracrossMega, + Species.Gen6Formes.mawileMega, + Species.Gen6Formes.manectricMega, + Species.Gen6Formes.garchompMega, + Species.Gen6Formes.latiosMega, + Species.Gen6Formes.latiasMega + ); + + private static final List orasIrregularFormes = Arrays.asList( + Species.Gen6Formes.castformF, Species.Gen6Formes.castformW, Species.Gen6Formes.castformI, + Species.Gen6Formes.darmanitanZ, + Species.Gen6Formes.meloettaP, + Species.Gen6Formes.kyuremW, + Species.Gen6Formes.kyuremB, + Species.Gen6Formes.gengarMega, + Species.Gen6Formes.gardevoirMega, + Species.Gen6Formes.ampharosMega, + Species.Gen6Formes.venusaurMega, + Species.Gen6Formes.charizardMegaX, Species.Gen6Formes.charizardMegaY, + Species.Gen6Formes.mewtwoMegaX, Species.Gen6Formes.mewtwoMegaY, + Species.Gen6Formes.blazikenMega, + Species.Gen6Formes.medichamMega, + Species.Gen6Formes.houndoomMega, + Species.Gen6Formes.aggronMega, + Species.Gen6Formes.banetteMega, + Species.Gen6Formes.tyranitarMega, + Species.Gen6Formes.scizorMega, + Species.Gen6Formes.pinsirMega, + Species.Gen6Formes.aerodactylMega, + Species.Gen6Formes.lucarioMega, + Species.Gen6Formes.abomasnowMega, + Species.Gen6Formes.aegislashB, + Species.Gen6Formes.blastoiseMega, + Species.Gen6Formes.kangaskhanMega, + Species.Gen6Formes.gyaradosMega, + Species.Gen6Formes.absolMega, + Species.Gen6Formes.alakazamMega, + Species.Gen6Formes.heracrossMega, + Species.Gen6Formes.mawileMega, + Species.Gen6Formes.manectricMega, + Species.Gen6Formes.garchompMega, + Species.Gen6Formes.latiosMega, + Species.Gen6Formes.latiasMega, + Species.Gen6Formes.swampertMega, + Species.Gen6Formes.sceptileMega, + Species.Gen6Formes.sableyeMega, + Species.Gen6Formes.altariaMega, + Species.Gen6Formes.galladeMega, + Species.Gen6Formes.audinoMega, + Species.Gen6Formes.sharpedoMega, + Species.Gen6Formes.slowbroMega, + Species.Gen6Formes.steelixMega, + Species.Gen6Formes.pidgeotMega, + Species.Gen6Formes.glalieMega, + Species.Gen6Formes.diancieMega, + Species.Gen6Formes.metagrossMega, + Species.Gen6Formes.kyogreP, + Species.Gen6Formes.groudonP, + Species.Gen6Formes.rayquazaMega, + Species.Gen6Formes.cameruptMega, + Species.Gen6Formes.lopunnyMega, + Species.Gen6Formes.salamenceMega, + Species.Gen6Formes.beedrillMega + ); + + private static final int moveCountXY = 617, moveCountORAS = 621; + private static final int highestAbilityIndexXY = Abilities.auraBreak, highestAbilityIndexORAS = Abilities.deltaStream; + + public static final List uselessAbilities = Arrays.asList(Abilities.forecast, Abilities.multitype, + Abilities.flowerGift, Abilities.zenMode, Abilities.stanceChange); + + public static final MoveCategory[] moveCategoryIndices = { MoveCategory.STATUS, MoveCategory.PHYSICAL, + MoveCategory.SPECIAL }; + + public static byte moveCategoryToByte(MoveCategory cat) { + switch (cat) { + case PHYSICAL: + return 1; + case SPECIAL: + return 2; + case STATUS: + default: + return 0; + } + } + + public static final int noDamageTargetTrappingEffect = 106, noDamageFieldTrappingEffect = 354, + damageAdjacentFoesTrappingEffect = 373; + + public static final int noDamageStatusQuality = 1, noDamageStatChangeQuality = 2, damageStatusQuality = 4, + noDamageStatusAndStatChangeQuality = 5, damageTargetDebuffQuality = 6, damageUserBuffQuality = 7, + damageAbsorbQuality = 8; + + public static List bannedMoves = Collections.singletonList(Moves.hyperspaceFury); + + public static final Type[] typeTable = constructTypeTable(); + + // Copied from pk3DS. "Dark Grass Held Item" should probably be renamed + public static final int bsHPOffset = 0, bsAttackOffset = 1, bsDefenseOffset = 2, bsSpeedOffset = 3, + bsSpAtkOffset = 4, bsSpDefOffset = 5, bsPrimaryTypeOffset = 6, bsSecondaryTypeOffset = 7, + bsCatchRateOffset = 8, bsCommonHeldItemOffset = 12, bsRareHeldItemOffset = 14, bsDarkGrassHeldItemOffset = 16, + bsGenderOffset = 18, bsGrowthCurveOffset = 21, bsAbility1Offset = 24, bsAbility2Offset = 25, + bsAbility3Offset = 26, bsFormeOffset = 28, bsFormeSpriteOffset = 30, bsFormeCountOffset = 32, + bsTMHMCompatOffset = 40, bsSpecialMTCompatOffset = 56, bsMTCompatOffset = 64; + + private static final int bsSizeXY = 0x40; + private static final int bsSizeORAS = 0x50; + + public static final int evolutionMethodCount = 34; + + public static final int staticPokemonSize = 0xC; + private static final int staticPokemonCountXY = 0xD; + private static final int staticPokemonCountORAS = 0x3B; + + private static final int giftPokemonSizeXY = 0x18; + private static final int giftPokemonSizeORAS = 0x24; + private static final int giftPokemonCountXY = 0x13; + private static final int giftPokemonCountORAS = 0x25; + + public static final String tmDataPrefix = "D400AE02AF02B002"; + public static final int tmCount = 100, tmBlockOneCount = 92, tmBlockTwoCount = 3, tmBlockThreeCount = 5, + tmBlockOneOffset = Items.tm01, tmBlockTwoOffset = Items.tm93, tmBlockThreeOffset = Items.tm96, hmBlockOneCount = 5, + rockSmashOffsetORAS = 10, diveOffsetORAS = 28; + private static final int tmBlockTwoStartingOffsetXY = 97, tmBlockTwoStartingOffsetORAS = 98, + hmCountXY = 5, hmCountORAS = 7; + public static final int hiddenItemCountORAS = 170; + public static final String hiddenItemsPrefixORAS = "A100A200A300A400A5001400010053004A0084000900"; + public static final String itemPalettesPrefix = "6F7461746500FF920A063F"; + private static final String shopItemsLocatorXY = "0400110004000300", shopItemsLocatorORAS = "04001100120004000300"; + + public static final int tutorMoveCount = 60; + public static final String tutorsLocator = "C2015701A20012024401BA01"; + public static final String tutorsShopPrefix = "8A02000030000000"; + + public static final int[] tutorSize = new int[]{15, 17, 16, 15}; + + private static final String ingameTradesPrefixXY = "BA0A02015E000100BC0A150069000100"; + private static final String ingameTradesPrefixORAS = "810B7A0097000A00000047006B000A00"; + + public static final int ingameTradeSize = 0x24; + + public static final String friendshipValueForEvoLocator = "DC0050E3BC00002A"; + + public static final String perfectOddsBranchLocator = "050000BA000050E3"; + + public static final String[] fastestTextPrefixes = new String[]{"1080BDE80000A0E31080BDE8F0412DE9", "485080E59C4040E24C50C0E5EC009FE5"}; + + private static final List mainGameShopsXY = Arrays.asList( + 10,11,12,13,16,17,20,21,24,25 + ); + + private static final List mainGameShopsORAS = Arrays.asList( + 10, 11, 13, 14, 16, 17, 18, 19, 20, 21 + ); + + private static final List shopNamesXY = Arrays.asList( + "Primary 0 Badges", + "Primary 1 Badges", + "Primary 2 Badges", + "Primary 3 Badges", + "Primary 4 Badges", + "Primary 5 Badges", + "Primary 6 Badges", + "Primary 7 Badges", + "Primary 8 Badges", + "Unused", + "Lumiose Herboriste", + "Lumiose Poké Ball Boutique", + "Lumiose Stone Emporium", + "Coumarine Incenses", + "Aquacorde Poké Ball", + "Aquacorde Potion", + "Lumiose North Secondary", + "Cyllage Secondary", + "Shalour Secondary (TMs)", + "Lumiose South Secondary (TMs)", + "Laverre Secondary", + "Snowbelle Secondary", + "Kiloude Secondary (TMs)", + "Anistar Secondary (TMs)", + "Santalune Secondary", + "Coumarine Secondary"); + + private static final List shopNamesORAS = Arrays.asList( + "Primary 0 Badges (After Pokédex)", + "Primary 1 Badges", + "Primary 2 Badges", + "Primary 3 Badges", + "Primary 4 Badges", + "Primary 5 Badges", + "Primary 6 Badges", + "Primary 7 Badges", + "Primary 8 Badges", + "Primary 0 Badges (Before Pokédex)", + "Slateport Incenses", + "Slateport Vitamins", + "Slateport TMs", + "Rustboro Secondary", + "Slateport Secondary", + "Mauville Secondary (TMs)", + "Verdanturf Secondary", + "Fallarbor Secondary", + "Lavaridge Herbs", + "Lilycove Dept. Store 2F Left", + "Lilycove Dept. Store 3F Left", + "Lilycove Dept. Store 3F Right", + "Lilycove Dept. Store 4F Left (TMs)", + "Lilycove Dept. Store 4F Right (TMs)"); + + + public static final List evolutionItems = Arrays.asList(Items.sunStone, Items.moonStone, Items.fireStone, + Items.thunderStone, Items.waterStone, Items.leafStone, Items.shinyStone, Items.duskStone, Items.dawnStone, + Items.ovalStone, Items.kingsRock, Items.deepSeaTooth, Items.deepSeaScale, Items.metalCoat, Items.dragonScale, + Items.upgrade, Items.protector, Items.electirizer, Items.magmarizer, Items.dubiousDisc, Items.reaperCloth, + Items.razorClaw, Items.razorFang, Items.prismScale, Items.whippedDream, Items.sachet); + + private static final List requiredFieldTMsXY = Arrays.asList( + 1, 9, 40, 19, 65, 73, 69, 74, 81, 57, 61, 97, 95, 71, 79, 30, 31, 36, 53, 29, 22, 3, 2, 80, 26); + + private static final List requiredFieldTMsORAS = Arrays.asList( + 37, 32, 62, 11, 86, 29, 59, 43, 53, 69, 6, 2, 13, 18, 22, 61, 30, 97, 7, 90, 26, 55, 34, 35, 64, 65, 66, + 74, 79, 80, 81, 84, 89, 91, 93, 95); + + public static final List fieldMovesXY = Arrays.asList( + Moves.cut, Moves.fly, Moves.surf, Moves.strength, Moves.flash, Moves.dig, Moves.teleport, + Moves.waterfall, Moves.sweetScent, Moves.rockSmash); + public static final List fieldMovesORAS = Arrays.asList( + Moves.cut, Moves.fly, Moves.surf, Moves.strength, Moves.flash, Moves.dig, Moves.teleport, + Moves.waterfall, Moves.sweetScent, Moves.rockSmash, Moves.secretPower, Moves.dive); + + public static final int fallingEncounterOffset = 0xF4270, fallingEncounterCount = 55, fieldEncounterSize = 0x3C, + rustlingBushEncounterOffset = 0xF40CC, rustlingBushEncounterCount = 7; + public static final Map fallingEncounterNameMap = constructFallingEncounterNameMap(); + public static final Map rustlingBushEncounterNameMap = constructRustlingBushEncounterNameMap(); + public static final int perPokemonAreaDataLengthXY = 0xE8, perPokemonAreaDataLengthORAS = 0x2A0; + + private static final String saveLoadFormeReversionPrefixXY = "09EB000094E5141094E54A0B80E2", saveLoadFormeReversionPrefixORAS = "09EB000094E5141094E5120A80E2"; + public static final String afterBattleFormeReversionPrefix = "E4FFFFEA0000000000000000"; + public static final String ninjaskSpeciesPrefix = "241094E5B810D1E1", shedinjaSpeciesPrefix = "C2FFFFEB0040A0E10020A0E3"; + public static final String boxLegendaryFunctionPrefixXY = "14D08DE20900A0E1"; + public static final int boxLegendaryEncounterFileXY = 341, boxLegendaryLocalScriptOffsetXY = 0x6E0; + public static final int[] boxLegendaryCodeOffsetsXY = new int[]{ 144, 300, 584 }; + public static final int seaSpiritsDenEncounterFileXY = 351, seaSpiritsDenLocalScriptOffsetXY = 0x1C0; + public static final int[] seaSpiritsDenScriptOffsetsXY = new int[]{ 0x500, 0x508, 0x510 }; + public static final String rayquazaFunctionPrefixORAS = "0900A0E1F08FBDE8"; + public static final int[] rayquazaScriptOffsetsORAS = new int[]{ 3334, 14734 }, rayquazaCodeOffsetsORAS = new int[]{ 136, 292, 576 }; + public static final String nationalDexFunctionLocator = "080094E5010000E21080BDE8170F122F", xyGetDexFlagFunctionLocator = "000055E30100A0030A00000A", + orasGetHoennDexCaughtFunctionPrefix = "170F122F1CC15800"; + public static final int megastoneTableStartingOffsetORAS = 0xABA, megastoneTableEntrySizeORAS = 0x20, megastoneTableLengthORAS = 27; + + public static final String pickupTableLocator = "110012001A00"; + public static final int numberOfPickupItems = 29; + + public static final String xyRoamerFreeSpacePostfix = "540095E50220A0E30810A0E1", xyRoamerSpeciesLocator = "9040A0030400000A", + xyRoamerLevelPrefix = "B020DDE13F3BC1E3"; + + public static final String xyTrashEncountersTablePrefix = "4028100000"; + public static final int xyTrashEncounterDataLength = 16, xyTrashCanEncounterCount = 24, + pokemonVillageGarbadorOffset = 0, pokemonVillageGarbadorCount = 6, pokemonVillageBanetteOffset = 6, + pokemonVillageBanetteCount = 6, lostHotelGarbadorOffset = 12, lostHotelGarbadorCount = 3, + lostHotelTrubbishOffset = 15, lostHotelTrubbishCount = 3, lostHotelRotomOffset = 18, lostHotelRotomCount = 6; + + + public static List xyHardcodedTradeOffsets = Arrays.asList(1, 8); + public static List xyHardcodedTradeTexts = Arrays.asList(129, 349); + + public static final List consumableHeldItems = setupAllConsumableItems(); + + private static List setupAllConsumableItems() { + List list = new ArrayList<>(Gen5Constants.consumableHeldItems); + list.addAll(Arrays.asList(Items.weaknessPolicy, Items.luminousMoss, Items.snowball, Items.roseliBerry, + Items.keeBerry, Items.marangaBerry, Items.fairyGem)); + return list; + } + + public static final List allHeldItems = setupAllHeldItems(); + + private static List setupAllHeldItems() { + List list = new ArrayList<>(Gen5Constants.allHeldItems); + list.addAll(Arrays.asList(Items.weaknessPolicy, Items.snowball, Items.roseliBerry, Items.keeBerry, + Items.marangaBerry, Items.fairyGem)); + list.addAll(Arrays.asList(Items.assaultVest, Items.pixiePlate, Items.safetyGoggles)); + return list; + } + + public static final List generalPurposeConsumableItems = initializeGeneralPurposeConsumableItems(); + + private static List initializeGeneralPurposeConsumableItems() { + List list = new ArrayList<>(Gen5Constants.generalPurposeConsumableItems); + list.addAll(Arrays.asList(Items.weaknessPolicy, Items.luminousMoss, Items.snowball, Items.keeBerry, Items.marangaBerry)); + return Collections.unmodifiableList(list); + } + + public static final List generalPurposeItems = initializeGeneralPurposeItems(); + + private static List initializeGeneralPurposeItems() { + List list = new ArrayList<>(Gen5Constants.generalPurposeItems); + list.addAll(Arrays.asList(Items.safetyGoggles)); + return Collections.unmodifiableList(list); + } + + public static final Map weaknessReducingBerries = initializeWeaknessReducingBerries(); + + private static Map initializeWeaknessReducingBerries() { + Map map = new HashMap<>(Gen5Constants.weaknessReducingBerries); + map.put(Type.FAIRY, Items.roseliBerry); + return Collections.unmodifiableMap(map); + } + + public static final Map consumableTypeBoostingItems = initializeConsumableTypeBoostingItems(); + + private static Map initializeConsumableTypeBoostingItems() { + Map map = new HashMap<>(Gen5Constants.consumableTypeBoostingItems); + map.put(Type.FAIRY, Items.fairyGem); + return Collections.unmodifiableMap(map); + } + + public static final Map> typeBoostingItems = initializeTypeBoostingItems(); + + private static Map> initializeTypeBoostingItems() { + Map> map = new HashMap<>(Gen5Constants.typeBoostingItems); + map.put(Type.FAIRY, Arrays.asList(Items.pixiePlate)); + return Collections.unmodifiableMap(map); + } + + public static final Map> moveBoostingItems = initializeMoveBoostingItems(); + + private static Map> initializeMoveBoostingItems() { + Map> map = new HashMap<>(Gen5Constants.moveBoostingItems); + map.put(Moves.drainingKiss, Arrays.asList(Items.bigRoot)); + map.put(Moves.infestation, Arrays.asList(Items.gripClaw, Items.bindingBand)); + map.put(Moves.oblivionWing, Arrays.asList(Items.bigRoot)); + map.put(Moves.parabolicCharge, Arrays.asList(Items.bigRoot)); + return Collections.unmodifiableMap(map); + } + + public static final Map> abilityBoostingItems = initializeAbilityBoostingItems(); + + private static Map> initializeAbilityBoostingItems() { + Map> map = new HashMap<>(Gen5Constants.abilityBoostingItems); + // Weather from abilities changed in Gen VI, so these items become relevant. + map.put(Abilities.drizzle, Arrays.asList(Items.dampRock)); + map.put(Abilities.drought, Arrays.asList(Items.heatRock)); + map.put(Abilities.sandStream, Arrays.asList(Items.smoothRock)); + map.put(Abilities.snowWarning, Arrays.asList(Items.icyRock)); + return Collections.unmodifiableMap(map); + } + + // No new species boosting items in Gen VI + public static final Map> speciesBoostingItems = Gen5Constants.speciesBoostingItems; + + public static String getIngameTradesPrefix(int romType) { + if (romType == Type_XY) { + return ingameTradesPrefixXY; + } else { + return ingameTradesPrefixORAS; + } + } + + public static List getRequiredFieldTMs(int romType) { + if (romType == Type_XY) { + return requiredFieldTMsXY; + } else { + return requiredFieldTMsORAS; + } + } + + public static List getMainGameShops(int romType) { + if (romType == Type_XY) { + return mainGameShopsXY; + } else { + return mainGameShopsORAS; + } + } + + public static List getShopNames(int romType) { + if (romType == Type_XY) { + return shopNamesXY; + } else { + return shopNamesORAS; + } + } + + public static int getBsSize(int romType) { + if (romType == Type_XY) { + return bsSizeXY; + } else { + return bsSizeORAS; + } + } + + public static List getIrregularFormes(int romType) { + if (romType == Type_XY) { + return xyIrregularFormes; + } else if (romType == Type_ORAS) { + return orasIrregularFormes; + } + return new ArrayList<>(); + } + + public static int getFormeCount(int romType) { + if (romType == Type_XY) { + return xyFormeCount; + } else if (romType == Type_ORAS) { + return orasFormeCount; + } + return 0; + } + + public static int getFormeMovesetOffset(int romType) { + if (romType == Type_XY) { + return orasformeMovesetOffset; + } else if (romType == Type_ORAS) { + return orasformeMovesetOffset; + } + return 0; + } + + public static int getMoveCount(int romType) { + if (romType == Type_XY) { + return moveCountXY; + } else if (romType == Type_ORAS) { + return moveCountORAS; + } + return moveCountXY; + } + + public static int getTMBlockTwoStartingOffset(int romType) { + if (romType == Type_XY) { + return tmBlockTwoStartingOffsetXY; + } else if (romType == Type_ORAS) { + return tmBlockTwoStartingOffsetORAS; + } + return tmBlockTwoStartingOffsetXY; + } + + public static int getHMCount(int romType) { + if (romType == Type_XY) { + return hmCountXY; + } else if (romType == Type_ORAS) { + return hmCountORAS; + } + return hmCountXY; + } + + public static int getHighestAbilityIndex(int romType) { + if (romType == Type_XY) { + return highestAbilityIndexXY; + } else if (romType == Type_ORAS) { + return highestAbilityIndexORAS; + } + return highestAbilityIndexXY; + } + + public static int getStaticPokemonCount(int romType) { + if (romType == Type_XY) { + return staticPokemonCountXY; + } else if (romType == Type_ORAS) { + return staticPokemonCountORAS; + } + return staticPokemonCountXY; + } + + public static int getGiftPokemonCount(int romType) { + if (romType == Type_XY) { + return giftPokemonCountXY; + } else if (romType == Type_ORAS) { + return giftPokemonCountORAS; + } + return giftPokemonCountXY; + } + + public static int getGiftPokemonSize(int romType) { + if (romType == Type_XY) { + return giftPokemonSizeXY; + } else if (romType == Type_ORAS) { + return giftPokemonSizeORAS; + } + return giftPokemonSizeXY; + } + + public static String getShopItemsLocator(int romType) { + if (romType == Type_XY) { + return shopItemsLocatorXY; + } else if (romType == Type_ORAS) { + return shopItemsLocatorORAS; + } + return shopItemsLocatorXY; + } + + public static boolean isMegaStone(int itemIndex) { + // These values come from https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_(Generation_VI) + return (itemIndex >= Items.gengarite && itemIndex <= Items.latiosite) || + (itemIndex >= Items.swampertite && itemIndex <= Items.diancite) || + (itemIndex >= Items.cameruptite && itemIndex <= Items.beedrillite); + } + + private static Type[] constructTypeTable() { + Type[] table = new Type[256]; + table[0x00] = Type.NORMAL; + table[0x01] = Type.FIGHTING; + table[0x02] = Type.FLYING; + table[0x03] = Type.POISON; + table[0x04] = Type.GROUND; + table[0x05] = Type.ROCK; + table[0x06] = Type.BUG; + table[0x07] = Type.GHOST; + table[0x08] = Type.STEEL; + table[0x09] = Type.FIRE; + table[0x0A] = Type.WATER; + table[0x0B] = Type.GRASS; + table[0x0C] = Type.ELECTRIC; + table[0x0D] = Type.PSYCHIC; + table[0x0E] = Type.ICE; + table[0x0F] = Type.DRAGON; + table[0x10] = Type.DARK; + table[0x11] = Type.FAIRY; + return table; + } + + public static byte typeToByte(Type type) { + if (type == null) { + return 0x00; // normal? + } + switch (type) { + case NORMAL: + return 0x00; + case FIGHTING: + return 0x01; + case FLYING: + return 0x02; + case POISON: + return 0x03; + case GROUND: + return 0x04; + case ROCK: + return 0x05; + case BUG: + return 0x06; + case GHOST: + return 0x07; + case FIRE: + return 0x09; + case WATER: + return 0x0A; + case GRASS: + return 0x0B; + case ELECTRIC: + return 0x0C; + case PSYCHIC: + return 0x0D; + case ICE: + return 0x0E; + case DRAGON: + return 0x0F; + case STEEL: + return 0x08; + case DARK: + return 0x10; + case FAIRY: + return 0x11; + default: + return 0; // normal by default + } + } + + public static String getSaveLoadFormeReversionPrefix(int romType) { + if (romType == Type_XY) { + return saveLoadFormeReversionPrefixXY; + } else { + return saveLoadFormeReversionPrefixORAS; + } + } + + private static Map setupFormeSuffixes() { + Map formeSuffixes = new HashMap<>(); + formeSuffixes.put(Species.Gen6Formes.deoxysA,"-A"); + formeSuffixes.put(Species.Gen6Formes.deoxysD,"-D"); + formeSuffixes.put(Species.Gen6Formes.deoxysS,"-S"); + formeSuffixes.put(Species.Gen6Formes.wormadamS,"-S"); + formeSuffixes.put(Species.Gen6Formes.wormadamT,"-T"); + formeSuffixes.put(Species.Gen6Formes.shayminS,"-S"); + formeSuffixes.put(Species.Gen6Formes.giratinaO,"-O"); + formeSuffixes.put(Species.Gen6Formes.rotomH,"-H"); + formeSuffixes.put(Species.Gen6Formes.rotomW,"-W"); + formeSuffixes.put(Species.Gen6Formes.rotomFr,"-Fr"); + formeSuffixes.put(Species.Gen6Formes.rotomFa,"-Fa"); + formeSuffixes.put(Species.Gen6Formes.rotomM,"-M"); + formeSuffixes.put(Species.Gen6Formes.castformF,"-F"); + formeSuffixes.put(Species.Gen6Formes.castformW,"-W"); + formeSuffixes.put(Species.Gen6Formes.castformI,"-I"); + formeSuffixes.put(Species.Gen6Formes.basculinB,"-B"); + formeSuffixes.put(Species.Gen6Formes.darmanitanZ,"-Z"); + formeSuffixes.put(Species.Gen6Formes.meloettaP,"-P"); + formeSuffixes.put(Species.Gen6Formes.kyuremW,"-W"); + formeSuffixes.put(Species.Gen6Formes.kyuremB,"-B"); + formeSuffixes.put(Species.Gen6Formes.keldeoCosmetic1,"-R"); + formeSuffixes.put(Species.Gen6Formes.tornadusT,"-T"); + formeSuffixes.put(Species.Gen6Formes.thundurusT,"-T"); + formeSuffixes.put(Species.Gen6Formes.landorusT,"-T"); + formeSuffixes.put(Species.Gen6Formes.gengarMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.meowsticF,"-F"); + // 749 - 757 Furfrou + formeSuffixes.put(Species.Gen6Formes.gardevoirMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.ampharosMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.venusaurMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.charizardMegaX,"-Mega-X"); + formeSuffixes.put(Species.Gen6Formes.charizardMegaY,"-Mega-Y"); + formeSuffixes.put(Species.Gen6Formes.mewtwoMegaX,"-Mega-X"); + formeSuffixes.put(Species.Gen6Formes.mewtwoMegaY,"-Mega-Y"); + formeSuffixes.put(Species.Gen6Formes.blazikenMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.medichamMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.houndoomMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.aggronMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.banetteMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.tyranitarMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.scizorMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.pinsirMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.aerodactylMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.lucarioMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.abomasnowMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.aegislashB,"-B"); + formeSuffixes.put(Species.Gen6Formes.blastoiseMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.kangaskhanMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.gyaradosMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.absolMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.alakazamMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.heracrossMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.mawileMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.manectricMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.garchompMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.latiosMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.latiasMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.pumpkabooCosmetic1,"-M"); + formeSuffixes.put(Species.Gen6Formes.pumpkabooCosmetic2,"-L"); + formeSuffixes.put(Species.Gen6Formes.pumpkabooCosmetic3,"-XL"); + formeSuffixes.put(Species.Gen6Formes.gourgeistCosmetic1,"-M"); + formeSuffixes.put(Species.Gen6Formes.gourgeistCosmetic2,"-L"); + formeSuffixes.put(Species.Gen6Formes.gourgeistCosmetic3,"-XL"); + // 794 - 797 Floette + formeSuffixes.put(Species.Gen6Formes.floetteE,"-E"); + formeSuffixes.put(Species.Gen6Formes.swampertMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.sceptileMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.sableyeMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.altariaMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.galladeMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.audinoMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.sharpedoMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.slowbroMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.steelixMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.pidgeotMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.glalieMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.diancieMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.metagrossMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.kyogreP,"-P"); + formeSuffixes.put(Species.Gen6Formes.groudonP,"-P"); + formeSuffixes.put(Species.Gen6Formes.rayquazaMega,"-Mega"); + // 815 - 820 contest Pikachu + formeSuffixes.put(Species.Gen6Formes.hoopaU,"-U"); + formeSuffixes.put(Species.Gen6Formes.cameruptMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.lopunnyMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.salamenceMega,"-Mega"); + formeSuffixes.put(Species.Gen6Formes.beedrillMega,"-Mega"); + + return formeSuffixes; + } + + private static Map> setupFormeSuffixesByBaseForme() { + Map> map = new HashMap<>(); + + Map deoxysMap = new HashMap<>(); + deoxysMap.put(1,"-A"); + deoxysMap.put(2,"-D"); + deoxysMap.put(3,"-S"); + map.put(Species.deoxys, deoxysMap); + + Map wormadamMap = new HashMap<>(); + wormadamMap.put(1,"-S"); + wormadamMap.put(2,"-T"); + map.put(Species.wormadam, wormadamMap); + + Map shayminMap = new HashMap<>(); + shayminMap.put(1,"-S"); + map.put(Species.shaymin, shayminMap); + + Map giratinaMap = new HashMap<>(); + giratinaMap.put(1,"-O"); + map.put(Species.giratina, giratinaMap); + + Map rotomMap = new HashMap<>(); + rotomMap.put(1,"-H"); + rotomMap.put(2,"-W"); + rotomMap.put(3,"-Fr"); + rotomMap.put(4,"-Fa"); + rotomMap.put(5,"-M"); + map.put(Species.rotom, rotomMap); + + Map castformMap = new HashMap<>(); + castformMap.put(1,"-F"); + castformMap.put(2,"-W"); + castformMap.put(3,"-I"); + map.put(Species.castform, castformMap); + + Map basculinMap = new HashMap<>(); + basculinMap.put(1,"-B"); + map.put(Species.basculin, basculinMap); + + Map darmanitanMap = new HashMap<>(); + darmanitanMap.put(1,"-Z"); + map.put(Species.darmanitan, darmanitanMap); + + Map meloettaMap = new HashMap<>(); + meloettaMap.put(1,"-P"); + map.put(Species.meloetta, meloettaMap); + + Map kyuremMap = new HashMap<>(); + kyuremMap.put(1,"-W"); + kyuremMap.put(2,"-B"); + map.put(Species.kyurem, kyuremMap); + + Map tornadusMap = new HashMap<>(); + tornadusMap.put(1,"-T"); + map.put(Species.tornadus, tornadusMap); + + Map thundurusMap = new HashMap<>(); + thundurusMap.put(1,"-T"); + map.put(Species.thundurus, thundurusMap); + + Map landorusMap = new HashMap<>(); + landorusMap.put(1,"-T"); + map.put(Species.landorus, landorusMap); + + Map meowsticMap = new HashMap<>(); + meowsticMap.put(1,"-F"); + map.put(Species.meowstic, meowsticMap); + + Map aegislashMap = new HashMap<>(); + aegislashMap.put(1,"-B"); + map.put(Species.aegislash, aegislashMap); + + Map pumpkabooMap = new HashMap<>(); + pumpkabooMap.put(1,"-M"); + pumpkabooMap.put(2,"-L"); + pumpkabooMap.put(3,"-XL"); + map.put(Species.pumpkaboo, pumpkabooMap); + + Map gourgeistMap = new HashMap<>(); + gourgeistMap.put(1,"-M"); + gourgeistMap.put(2,"-L"); + gourgeistMap.put(3,"-XL"); + map.put(Species.gourgeist, gourgeistMap); + + Map floetteMap = new HashMap<>(); + floetteMap.put(5,"-E"); + map.put(Species.floette, floetteMap); + + Map kyogreMap = new HashMap<>(); + kyogreMap.put(1,"-P"); + map.put(Species.kyogre, kyogreMap); + + Map groudonMap = new HashMap<>(); + groudonMap.put(1,"-P"); + map.put(Species.groudon, groudonMap); + + Map rayquazaMap = new HashMap<>(); + rayquazaMap.put(1,"-Mega"); + map.put(Species.rayquaza, rayquazaMap); + + Map hoopaMap = new HashMap<>(); + hoopaMap.put(1,"-U"); + map.put(Species.hoopa, hoopaMap); + + for (Integer species: speciesToMegaStoneORAS.keySet()) { + Map megaMap = new HashMap<>(); + if (species == Species.charizard || species == Species.mewtwo) { + megaMap.put(1,"-Mega-X"); + megaMap.put(2,"-Mega-Y"); + } else { + megaMap.put(1,"-Mega"); + } + map.put(species,megaMap); + } + + return map; + } + + private static Map setupDummyFormeSuffixes() { + Map m = new HashMap<>(); + m.put(0,""); + return m; + } + + public static ItemList allowedItemsXY, allowedItemsORAS, nonBadItemsXY, nonBadItemsORAS; + public static List regularShopItems, opShopItems; + + static { + setupAllowedItems(); + } + + private static void setupAllowedItems() { + allowedItemsXY = new ItemList(Items.megaGlove); + // Key items + version exclusives + allowedItemsXY.banRange(Items.explorerKit, 76); + allowedItemsXY.banRange(Items.dataCard01, 32); + allowedItemsXY.banRange(Items.xtransceiverMale, 18); + allowedItemsXY.banSingles(Items.expShare, Items.libertyPass, Items.propCase, Items.dragonSkull, + Items.lightStone, Items.darkStone); + // Unknown blank items or version exclusives + allowedItemsXY.banRange(Items.tea, 3); + allowedItemsXY.banRange(Items.unused120, 14); + // TMs & HMs - tms cant be held in gen6 + allowedItemsXY.tmRange(Items.tm01, 92); + allowedItemsXY.tmRange(Items.tm93, 3); + allowedItemsXY.banRange(Items.tm01, 100); + allowedItemsXY.banRange(Items.tm93, 3); + // Battle Launcher exclusives + allowedItemsXY.banRange(Items.direHit2, 24); + + // Key items (Gen 6) + allowedItemsXY.banRange(Items.holoCasterMale,3); + allowedItemsXY.banSingles(Items.pokeFlute, Items.sprinklotad); + allowedItemsXY.banRange(Items.powerPlantPass,4); + allowedItemsXY.banRange(Items.elevatorKey,4); + allowedItemsXY.banRange(Items.lensCase,3); + allowedItemsXY.banRange(Items.lookerTicket,3); + allowedItemsXY.banRange(Items.megaCharm,2); + + // TMs (Gen 6) + allowedItemsXY.tmRange(Items.tm96,5); + allowedItemsXY.banRange(Items.tm96,5); + + allowedItemsORAS = allowedItemsXY.copy(Items.eonFlute); + // Key items and an HM + allowedItemsORAS.banRange(Items.machBike,34); + allowedItemsORAS.banRange(Items.prisonBottle,2); + allowedItemsORAS.banRange(Items.meteoriteThirdForm,5); + + // non-bad items + // ban specific pokemon hold items, berries, apricorns, mail + nonBadItemsXY = allowedItemsXY.copy(); + + nonBadItemsXY.banSingles(Items.oddKeystone, Items.griseousOrb, Items.soulDew, Items.lightBall, + Items.oranBerry, Items.quickPowder, Items.passOrb, Items.discountCoupon, Items.strangeSouvenir); + nonBadItemsXY.banRange(Items.growthMulch, 4); // mulch + nonBadItemsXY.banRange(Items.adamantOrb, 2); // orbs + nonBadItemsXY.banRange(Items.mail1, 12); // mails + nonBadItemsXY.banRange(Items.figyBerry, 25); // berries without useful battle effects + nonBadItemsXY.banRange(Items.luckyPunch, 4); // pokemon specific + nonBadItemsXY.banRange(Items.redScarf, 5); // contest scarves + nonBadItemsXY.banRange(Items.relicCopper,7); // relic items + nonBadItemsXY.banRange(Items.richMulch,4); // more mulch + nonBadItemsXY.banRange(Items.shoalSalt, 6); // Shoal items and Shards; they serve no purpose in XY + + nonBadItemsORAS = allowedItemsORAS.copy(); + + nonBadItemsORAS.banSingles(Items.oddKeystone, Items.griseousOrb, Items.soulDew, Items.lightBall, + Items.oranBerry, Items.quickPowder, Items.passOrb, Items.discountCoupon, Items.strangeSouvenir); + nonBadItemsORAS.banRange(Items.growthMulch, 4); // mulch + nonBadItemsORAS.banRange(Items.adamantOrb, 2); // orbs + nonBadItemsORAS.banRange(Items.mail1, 12); // mails + nonBadItemsORAS.banRange(Items.figyBerry, 25); // berries without useful battle effects + nonBadItemsORAS.banRange(Items.luckyPunch, 4); // pokemon specific + nonBadItemsORAS.banRange(Items.redScarf, 5); // contest scarves + nonBadItemsORAS.banRange(Items.relicCopper,7); // relic items + nonBadItemsORAS.banRange(Items.richMulch,4); // more mulch + + regularShopItems = new ArrayList<>(); + + regularShopItems.addAll(IntStream.rangeClosed(Items.ultraBall, Items.pokeBall).boxed().collect(Collectors.toList())); + regularShopItems.addAll(IntStream.rangeClosed(Items.potion, Items.revive).boxed().collect(Collectors.toList())); + regularShopItems.addAll(IntStream.rangeClosed(Items.superRepel, Items.repel).boxed().collect(Collectors.toList())); + + opShopItems = new ArrayList<>(); + + // "Money items" etc + opShopItems.add(Items.lavaCookie); + opShopItems.add(Items.berryJuice); + opShopItems.add(Items.rareCandy); + opShopItems.add(Items.oldGateau); + opShopItems.addAll(IntStream.rangeClosed(Items.blueFlute, Items.shoalShell).boxed().collect(Collectors.toList())); + opShopItems.addAll(IntStream.rangeClosed(Items.tinyMushroom, Items.nugget).boxed().collect(Collectors.toList())); + opShopItems.add(Items.rareBone); + opShopItems.addAll(IntStream.rangeClosed(Items.lansatBerry, Items.rowapBerry).boxed().collect(Collectors.toList())); + opShopItems.add(Items.luckyEgg); + opShopItems.add(Items.prettyFeather); + opShopItems.addAll(IntStream.rangeClosed(Items.balmMushroom, Items.casteliacone).boxed().collect(Collectors.toList())); + } + + public static ItemList getAllowedItems(int romType) { + if (romType == Type_XY) { + return allowedItemsXY; + } else { + return allowedItemsORAS; + } + } + + public static ItemList getNonBadItems(int romType) { + if (romType == Type_XY) { + return nonBadItemsXY; + } else { + return nonBadItemsORAS; + } + } + + public static final List uniqueNoSellItems = Arrays.asList(Items.gengarite, Items.gardevoirite, + Items.ampharosite, Items.venusaurite, Items.charizarditeX, Items.blastoisinite, Items.mewtwoniteX, + Items.mewtwoniteY, Items.blazikenite, Items.medichamite, Items.houndoominite, Items.aggronite, + Items.banettite, Items.tyranitarite, Items.scizorite, Items.pinsirite, Items.aerodactylite, + Items.lucarionite, Items.abomasite, Items.kangaskhanite, Items.gyaradosite, Items.absolite, + Items.charizarditeY, Items.alakazite, Items.heracronite, Items.mawilite, Items.manectite, Items.garchompite, + Items.latiasite, Items.latiosite, Items.swampertite, Items.sceptilite, Items.sablenite, Items.altarianite, + Items.galladite, Items.audinite, Items.metagrossite, Items.sharpedonite, Items.slowbronite, + Items.steelixite, Items.pidgeotite, Items.glalitite, Items.diancite, Items.cameruptite, Items.lopunnite, + Items.salamencite, Items.beedrillite); + + private static Map> setupSpeciesToMegaStone(int romType) { + Map> map = new TreeMap<>(); + + map.put(Species.venusaur, Collections.singletonList(Items.venusaurite)); + map.put(Species.charizard, Arrays.asList(Items.charizarditeX, Items.charizarditeY)); + map.put(Species.blastoise, Collections.singletonList(Items.blastoisinite)); + map.put(Species.alakazam, Collections.singletonList(Items.alakazite)); + map.put(Species.gengar, Collections.singletonList(Items.gengarite)); + map.put(Species.kangaskhan, Collections.singletonList(Items.kangaskhanite)); + map.put(Species.pinsir, Collections.singletonList(Items.pinsirite)); + map.put(Species.gyarados, Collections.singletonList(Items.gyaradosite)); + map.put(Species.aerodactyl, Collections.singletonList(Items.aerodactylite)); + map.put(Species.mewtwo, Arrays.asList(Items.mewtwoniteX, Items.mewtwoniteY)); + map.put(Species.ampharos, Collections.singletonList(Items.ampharosite)); + map.put(Species.scizor, Collections.singletonList(Items.scizorite)); + map.put(Species.heracross, Collections.singletonList(Items.heracronite)); + map.put(Species.houndoom, Collections.singletonList(Items.houndoominite)); + map.put(Species.tyranitar, Collections.singletonList(Items.tyranitarite)); + map.put(Species.blaziken, Collections.singletonList(Items.blazikenite)); + map.put(Species.gardevoir, Collections.singletonList(Items.gardevoirite)); + map.put(Species.mawile, Collections.singletonList(Items.mawilite)); + map.put(Species.aggron, Collections.singletonList(Items.aggronite)); + map.put(Species.medicham, Collections.singletonList(Items.medichamite)); + map.put(Species.manectric, Collections.singletonList(Items.manectite)); + map.put(Species.banette, Collections.singletonList(Items.banettite)); + map.put(Species.absol, Collections.singletonList(Items.absolite)); + map.put(Species.latias, Collections.singletonList(Items.latiasite)); + map.put(Species.latios, Collections.singletonList(Items.latiosite)); + map.put(Species.garchomp, Collections.singletonList(Items.garchompite)); + map.put(Species.lucario, Collections.singletonList(Items.lucarionite)); + map.put(Species.abomasnow, Collections.singletonList(Items.abomasite)); + + if (romType == Type_ORAS) { + map.put(Species.beedrill, Collections.singletonList(Items.beedrillite)); + map.put(Species.pidgeot, Collections.singletonList(Items.pidgeotite)); + map.put(Species.slowbro, Collections.singletonList(Items.slowbronite)); + map.put(Species.steelix, Collections.singletonList(Items.steelixite)); + map.put(Species.sceptile, Collections.singletonList(Items.sceptilite)); + map.put(Species.swampert, Collections.singletonList(Items.swampertite)); + map.put(Species.sableye, Collections.singletonList(Items.sablenite)); + map.put(Species.sharpedo, Collections.singletonList(Items.sharpedonite)); + map.put(Species.camerupt, Collections.singletonList(Items.cameruptite)); + map.put(Species.altaria, Collections.singletonList(Items.altarianite)); + map.put(Species.glalie, Collections.singletonList(Items.glalitite)); + map.put(Species.salamence, Collections.singletonList(Items.salamencite)); + map.put(Species.metagross, Collections.singletonList(Items.metagrossite)); + map.put(Species.lopunny, Collections.singletonList(Items.lopunnite)); + map.put(Species.gallade, Collections.singletonList(Items.galladite)); + map.put(Species.audino, Collections.singletonList(Items.audinite)); + map.put(Species.diancie, Collections.singletonList(Items.diancite)); + } + + return map; + } + + public static void tagTrainersXY(List trs) { + + // Gym Trainers + tag(trs,"GYM1", 39, 40, 48); + tag(trs,"GYM2",64, 63, 106, 105); + tag(trs,"GYM3",83, 84, 146, 147); + tag(trs,"GYM4", 121, 122, 123, 124); + tag(trs,"GYM5", 461, 462, 463, 464, 465, 466, 467, 468, 469, 28, 29, 30); + tag(trs,"GYM6", 245, 250, 248, 243); + tag(trs,"GYM7", 170, 171, 172, 365, 366); + tag(trs,"GYM8", 168, 169, 31, 32); + + // Gym Leaders + tag(trs,"GYM1-LEADER", 6); + tag(trs,"GYM2-LEADER",76); + tag(trs,"GYM3-LEADER",21); + tag(trs,"GYM4-LEADER", 22); + tag(trs,"GYM5-LEADER", 23); + tag(trs,"GYM6-LEADER", 24); + tag(trs,"GYM7-LEADER", 25); + tag(trs,"GYM8-LEADER", 26); + + tag(trs, 188, "NOTSTRONG"); // Successor Korrina + + // Elite 4 + tag(trs, 269, "ELITE1"); // Malva + tag(trs, 271, "ELITE2"); // Siebold + tag(trs, 187, "ELITE3"); // Wikstrom + tag(trs, 270, "ELITE4"); // Drasna + tag(trs, 276, "CHAMPION"); // Diantha + + tag(trs,"THEMED:LYSANDRE-LEADER", 303, 525, 526); + tag(trs,"STRONG", 174, 175, 304, 344, 345, 346, 347, 348, 349, 350, 351, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479); // Team Flare Admins lol + tag(trs,"STRONG", 324, 325, 438, 439, 573); // Tierno and Trevor + tag(trs,"STRONG", 327, 328); // Sycamore + + // Rival - Serena + tagRival(trs, "RIVAL1", 596); + tagRival(trs, "RIVAL2", 575); + tagRival(trs, "RIVAL3", 581); + tagRival(trs, "RIVAL4", 578); + tagRival(trs, "RIVAL5", 584); + tagRival(trs, "RIVAL6", 607); + tagRival(trs, "RIVAL7", 587); + tagRival(trs, "RIVAL8", 590); + tagRival(trs, "RIVAL9", 593); + tagRival(trs, "RIVAL10", 599); + + // Rival - Calem + tagRival(trs, "RIVAL1", 435); + tagRival(trs, "RIVAL2", 130); + tagRival(trs, "RIVAL3", 329); + tagRival(trs, "RIVAL4", 184); + tagRival(trs, "RIVAL5", 332); + tagRival(trs, "RIVAL6", 604); + tagRival(trs, "RIVAL7", 335); + tagRival(trs, "RIVAL8", 338); + tagRival(trs, "RIVAL9", 341); + tagRival(trs, "RIVAL10", 519); + + // Rival - Shauna + tagRival(trs, "FRIEND1", 137); + tagRival(trs, "FRIEND2", 321); + } + + public static void tagTrainersORAS(List trs) { + + // Gym Trainers & Leaders + tag(trs,"GYM1",562, 22, 667); + tag(trs,"GYM2",60, 56, 59); + tag(trs,"GYM3",34, 568, 614, 35); + tag(trs,"GYM4",81, 824, 83, 615, 823, 613, 85); + tag(trs,"GYM5",63, 64, 65, 66, 67, 68, 69); + tag(trs,"GYM6",115, 517, 516, 118, 730); + tag(trs,"GYM7",157, 158, 159, 226, 320, 225); + tag(trs,"GYM8",647, 342, 594, 646, 338, 339, 340, 341); // Includes Wallace in Delta Episode + + // Gym Leaders + tag(trs,"GYM1-LEADER", 561); + tag(trs,"GYM2-LEADER",563); + tag(trs,"GYM3-LEADER",567); + tag(trs,"GYM4-LEADER", 569); + tag(trs,"GYM5-LEADER", 570); + tag(trs,"GYM6-LEADER", 571); + tag(trs,"GYM7-LEADER", 552); + tag(trs,"GYM8-LEADER", 572, 943); + + // Elite 4 + tag(trs, "ELITE1", 553, 909); // Sidney + tag(trs, "ELITE2", 554, 910); // Phoebe + tag(trs, "ELITE3", 555, 911); // Glacia + tag(trs, "ELITE4", 556, 912); // Drake + tag(trs, "CHAMPION", 557, 913, 680, 942); // Steven (includes other appearances) + + tag(trs,"THEMED:MAXIE-LEADER", 235, 236, 271); + tag(trs,"THEMED:ARCHIE-LEADER",178, 231, 266); + tag(trs,"THEMED:MATT-STRONG",683, 684, 685, 686, 687); + tag(trs,"THEMED:SHELLY-STRONG",688,689,690); + tag(trs,"THEMED:TABITHA-STRONG",691,692,693); + tag(trs,"THEMED:COURTNEY-STRONG",694,695,696,697,698); + tag(trs, "THEMED:WALLY-STRONG", 518, 583, 944, 946); + + // Rival - Brendan + tagRival(trs, "RIVAL1", 1); + tagRival(trs, "RIVAL2", 289); + tagRival(trs, "RIVAL3", 674); + tagRival(trs, "RIVAL4", 292); + tagRival(trs, "RIVAL5", 527); + tagRival(trs, "RIVAL6", 699); + + // Rival - May + tagRival(trs, "RIVAL1", 4); + tagRival(trs, "RIVAL2", 295); + tagRival(trs, "RIVAL3", 677); + tagRival(trs, "RIVAL4", 298); + tagRival(trs, "RIVAL5", 530); + tagRival(trs, "RIVAL6", 906); + } + + private static void tagRival(List allTrainers, String tag, int offset) { + allTrainers.get(offset - 1).tag = tag + "-0"; + allTrainers.get(offset).tag = tag + "-1"; + allTrainers.get(offset + 1).tag = tag + "-2"; + + } + + private static void tag(List allTrainers, int number, String tag) { + if (allTrainers.size() > (number - 1)) { + allTrainers.get(number - 1).tag = tag; + } + } + + private static void tag(List allTrainers, String tag, int... numbers) { + for (int num : numbers) { + if (allTrainers.size() > (num - 1)) { + allTrainers.get(num - 1).tag = tag; + } + } + } + + public static void setMultiBattleStatusXY(List trs) { + // 108 + 111: Team Flare Grunts in Glittering Cave + // 348 + 350: Team Flare Celosia and Bryony fight in Poké Ball Factory + // 438 + 439: Tierno and Trevor fight on Route 7 + // 470 + 611, 472 + 610, 476 + 612: Team Flare Admin and Grunt fights in Team Flare Secret HQ + setMultiBattleStatus(trs, 108, 111, 348, 350, 438, 439, 470, 472, 476, 610, 611, 612); + } + + public static void setMultiBattleStatusORAS(List trs) { + // 683 + 904: Aqua Admin Matt and Team Aqua Grunt fight on the Southern Island + // 687 + 905: Aqua Admin Matt and Team Aqua Grunt fight at the Mossdeep Space Center + // 688 + 903: Aqua Admin Shelly and Team Aqua Grunt fight in Meteor Falls + // 691 + 902: Magma Admin Tabitha and Team Magma Grunt fight in Meteor Falls + // 694 + 900: Magma Admin Courtney and Team Magma Grunt fight on the Southern Island + // 698 + 901: Magma Admin Courtney and Team Magma Grunt fight at the Mossdeep Space Center + setMultiBattleStatus(trs, 683, 687, 688, 691, 694, 698, 900, 901, 902, 903, 904, 905); + } + + private static void setMultiBattleStatus(List allTrainers, int... numbers) { + for (int num : numbers) { + if (allTrainers.size() > (num - 1)) { + allTrainers.get(num - 1).multiBattleStatus = Trainer.MultiBattleStatus.ALWAYS; + } + } + } + + private static Map constructFallingEncounterNameMap() { + Map map = new TreeMap<>(); + map.put(0, "Glittering Cave Ceiling Encounter"); + map.put(4, "Reflection Cave Ceiling Encounter"); + map.put(20, "Victory Road Outside 2 Sky Encounter"); + map.put(24, "Victory Road Inside 2 Encounter"); + map.put(28, "Victory Road Outside 3 Sky Encounter"); + map.put(32, "Victory Road Inside 3 Ceiling Encounter"); + map.put(36, "Victory Road Outside 4 Sky Encounter"); + map.put(46, "Terminus Cave Ceiling Encounter"); + return map; + } + + private static Map constructRustlingBushEncounterNameMap() { + Map map = new TreeMap<>(); + map.put(0, "Route 6 Rustling Bush Encounter"); + map.put(3, "Route 18 Rustling Bush Encounter"); + return map; + } + + public static final Map balancedItemPrices = Stream.of(new Integer[][] { + // Skip item index 0. All prices divided by 10 + {Items.masterBall, 300}, + {Items.ultraBall, 120}, + {Items.greatBall, 60}, + {Items.pokeBall, 20}, + {Items.safariBall, 50}, + {Items.netBall, 100}, + {Items.diveBall, 100}, + {Items.nestBall, 100}, + {Items.repeatBall, 100}, + {Items.timerBall, 100}, + {Items.luxuryBall, 100}, + {Items.premierBall, 20}, + {Items.duskBall, 100}, + {Items.healBall, 30}, + {Items.quickBall, 100}, + {Items.cherishBall, 20}, + {Items.potion, 30}, + {Items.antidote, 10}, + {Items.burnHeal, 25}, + {Items.iceHeal, 25}, + {Items.awakening, 25}, + {Items.paralyzeHeal, 20}, + {Items.fullRestore, 300}, + {Items.maxPotion, 250}, + {Items.hyperPotion, 120}, + {Items.superPotion, 70}, + {Items.fullHeal, 60}, + {Items.revive, 150}, + {Items.maxRevive, 400}, + {Items.freshWater, 40}, + {Items.sodaPop, 60}, + {Items.lemonade, 70}, + {Items.moomooMilk, 80}, + {Items.energyPowder, 40}, + {Items.energyRoot, 110}, + {Items.healPowder, 45}, + {Items.revivalHerb, 280}, + {Items.ether, 300}, + {Items.maxEther, 450}, + {Items.elixir, 1500}, + {Items.maxElixir, 1800}, + {Items.lavaCookie, 45}, + {Items.berryJuice, 10}, + {Items.sacredAsh, 1000}, + {Items.hpUp, 980}, + {Items.protein, 980}, + {Items.iron, 980}, + {Items.carbos, 980}, + {Items.calcium, 980}, + {Items.rareCandy, 1000}, + {Items.ppUp, 980}, + {Items.zinc, 980}, + {Items.ppMax, 2490}, + {Items.oldGateau, 45}, + {Items.guardSpec, 70}, + {Items.direHit, 65}, + {Items.xAttack, 50}, + {Items.xDefense, 55}, + {Items.xSpeed, 35}, + {Items.xAccuracy, 95}, + {Items.xSpAtk, 35}, + {Items.xSpDef, 35}, + {Items.pokeDoll, 100}, + {Items.fluffyTail, 100}, + {Items.blueFlute, 2}, + {Items.yellowFlute, 2}, + {Items.redFlute, 2}, + {Items.blackFlute, 2}, + {Items.whiteFlute, 2}, + {Items.shoalSalt, 2}, + {Items.shoalShell, 2}, + {Items.redShard, 40}, + {Items.blueShard, 40}, + {Items.yellowShard, 40}, + {Items.greenShard, 40}, + {Items.superRepel, 50}, + {Items.maxRepel, 70}, + {Items.escapeRope, 55}, + {Items.repel, 35}, + {Items.sunStone, 300}, + {Items.moonStone, 300}, + {Items.fireStone, 300}, + {Items.thunderStone, 300}, + {Items.waterStone, 300}, + {Items.leafStone, 300}, + {Items.tinyMushroom, 50}, + {Items.bigMushroom, 500}, + {Items.pearl, 140}, + {Items.bigPearl, 750}, + {Items.stardust, 200}, + {Items.starPiece, 980}, + {Items.nugget, 1000}, + {Items.heartScale, 500}, + {Items.honey, 50}, + {Items.growthMulch, 20}, + {Items.dampMulch, 20}, + {Items.stableMulch, 20}, + {Items.gooeyMulch, 20}, + {Items.rootFossil, 500}, + {Items.clawFossil, 500}, + {Items.helixFossil, 500}, + {Items.domeFossil, 500}, + {Items.oldAmber, 800}, + {Items.armorFossil, 500}, + {Items.skullFossil, 500}, + {Items.rareBone, 1000}, + {Items.shinyStone, 300}, + {Items.duskStone, 300}, + {Items.dawnStone, 300}, + {Items.ovalStone, 300}, + {Items.oddKeystone, 210}, + {Items.griseousOrb, 1000}, + {Items.tea, 0}, // unused in Gen 6 + {Items.unused114, 0}, + {Items.autograph, 0}, + {Items.douseDrive, 100}, + {Items.shockDrive, 100}, + {Items.burnDrive, 100}, + {Items.chillDrive, 100}, + {Items.unused120, 0}, + {Items.pokemonBox, 0}, // unused in Gen 6 + {Items.medicinePocket, 0}, // unused in Gen 6 + {Items.tmCase, 0}, // unused in Gen 6 + {Items.candyJar, 0}, // unused in Gen 6 + {Items.powerUpPocket, 0}, // unused in Gen 6 + {Items.clothingTrunk, 0}, // unused in Gen 6 + {Items.catchingPocket, 0}, // unused in Gen 6 + {Items.battlePocket, 0}, // unused in Gen 6 + {Items.unused129, 0}, + {Items.unused130, 0}, + {Items.unused131, 0}, + {Items.unused132, 0}, + {Items.unused133, 0}, + {Items.sweetHeart, 15}, + {Items.adamantOrb, 1000}, + {Items.lustrousOrb, 1000}, + {Items.mail1, 5}, + {Items.mail2, 5}, + {Items.mail3, 5}, + {Items.mail4, 5}, + {Items.mail5, 5}, + {Items.mail6, 5}, + {Items.mail7, 5}, + {Items.mail8, 5}, + {Items.mail9, 5}, + {Items.mail10, 5}, + {Items.mail11, 5}, + {Items.mail12, 5}, + {Items.cheriBerry, 20}, + {Items.chestoBerry, 25}, + {Items.pechaBerry, 10}, + {Items.rawstBerry, 25}, + {Items.aspearBerry, 25}, + {Items.leppaBerry, 300}, + {Items.oranBerry, 5}, + {Items.persimBerry, 20}, + {Items.lumBerry, 50}, + {Items.sitrusBerry, 50}, + {Items.figyBerry, 10}, + {Items.wikiBerry, 10}, + {Items.magoBerry, 10}, + {Items.aguavBerry, 10}, + {Items.iapapaBerry, 10}, + {Items.razzBerry, 50}, + {Items.blukBerry, 50}, + {Items.nanabBerry, 50}, + {Items.wepearBerry, 50}, + {Items.pinapBerry, 50}, + {Items.pomegBerry, 50}, + {Items.kelpsyBerry, 50}, + {Items.qualotBerry, 50}, + {Items.hondewBerry, 50}, + {Items.grepaBerry, 50}, + {Items.tamatoBerry, 50}, + {Items.cornnBerry, 50}, + {Items.magostBerry, 50}, + {Items.rabutaBerry, 50}, + {Items.nomelBerry, 50}, + {Items.spelonBerry, 50}, + {Items.pamtreBerry, 50}, + {Items.watmelBerry, 50}, + {Items.durinBerry, 50}, + {Items.belueBerry, 50}, + {Items.occaBerry, 100}, + {Items.passhoBerry, 100}, + {Items.wacanBerry, 100}, + {Items.rindoBerry, 100}, + {Items.yacheBerry, 100}, + {Items.chopleBerry, 100}, + {Items.kebiaBerry, 100}, + {Items.shucaBerry, 100}, + {Items.cobaBerry, 100}, + {Items.payapaBerry, 100}, + {Items.tangaBerry, 100}, + {Items.chartiBerry, 100}, + {Items.kasibBerry, 100}, + {Items.habanBerry, 100}, + {Items.colburBerry, 100}, + {Items.babiriBerry, 100}, + {Items.chilanBerry, 100}, + {Items.liechiBerry, 100}, + {Items.ganlonBerry, 100}, + {Items.salacBerry, 100}, + {Items.petayaBerry, 100}, + {Items.apicotBerry, 100}, + {Items.lansatBerry, 100}, + {Items.starfBerry, 100}, + {Items.enigmaBerry, 100}, + {Items.micleBerry, 100}, + {Items.custapBerry, 100}, + {Items.jabocaBerry, 100}, + {Items.rowapBerry, 100}, + {Items.brightPowder, 300}, + {Items.whiteHerb, 100}, + {Items.machoBrace, 300}, + {Items.expShare, 0}, + {Items.quickClaw, 450}, + {Items.sootheBell, 100}, + {Items.mentalHerb, 100}, + {Items.choiceBand, 1000}, + {Items.kingsRock, 500}, + {Items.silverPowder, 200}, + {Items.amuletCoin, 1500}, + {Items.cleanseTag, 100}, + {Items.soulDew, 20}, + {Items.deepSeaTooth, 300}, + {Items.deepSeaScale, 300}, + {Items.smokeBall, 20}, + {Items.everstone, 20}, + {Items.focusBand, 300}, + {Items.luckyEgg, 1000}, + {Items.scopeLens, 500}, + {Items.metalCoat, 300}, + {Items.leftovers, 1000}, + {Items.dragonScale, 300}, + {Items.lightBall, 10}, + {Items.softSand, 200}, + {Items.hardStone, 200}, + {Items.miracleSeed, 200}, + {Items.blackGlasses, 200}, + {Items.blackBelt, 200}, + {Items.magnet, 200}, + {Items.mysticWater, 200}, + {Items.sharpBeak, 200}, + {Items.poisonBarb, 200}, + {Items.neverMeltIce, 200}, + {Items.spellTag, 200}, + {Items.twistedSpoon, 200}, + {Items.charcoal, 200}, + {Items.dragonFang, 200}, + {Items.silkScarf, 200}, + {Items.upgrade, 300}, + {Items.shellBell, 600}, + {Items.seaIncense, 200}, + {Items.laxIncense, 300}, + {Items.luckyPunch, 1}, + {Items.metalPowder, 1}, + {Items.thickClub, 50}, + {Items.leek, 20}, + {Items.redScarf, 10}, + {Items.blueScarf, 10}, + {Items.pinkScarf, 10}, + {Items.greenScarf, 10}, + {Items.yellowScarf, 10}, + {Items.wideLens, 150}, + {Items.muscleBand, 200}, + {Items.wiseGlasses, 200}, + {Items.expertBelt, 600}, + {Items.lightClay, 150}, + {Items.lifeOrb, 1000}, + {Items.powerHerb, 100}, + {Items.toxicOrb, 150}, + {Items.flameOrb, 150}, + {Items.quickPowder, 1}, + {Items.focusSash, 200}, + {Items.zoomLens, 150}, + {Items.metronome, 300}, + {Items.ironBall, 100}, + {Items.laggingTail, 100}, + {Items.destinyKnot, 150}, + {Items.blackSludge, 500}, + {Items.icyRock, 20}, + {Items.smoothRock, 20}, + {Items.heatRock, 20}, + {Items.dampRock, 20}, + {Items.gripClaw, 150}, + {Items.choiceScarf, 1000}, + {Items.stickyBarb, 150}, + {Items.powerBracer, 300}, + {Items.powerBelt, 300}, + {Items.powerLens, 300}, + {Items.powerBand, 300}, + {Items.powerAnklet, 300}, + {Items.powerWeight, 300}, + {Items.shedShell, 50}, + {Items.bigRoot, 150}, + {Items.choiceSpecs, 1000}, + {Items.flamePlate, 200}, + {Items.splashPlate, 200}, + {Items.zapPlate, 200}, + {Items.meadowPlate, 200}, + {Items.iciclePlate, 200}, + {Items.fistPlate, 200}, + {Items.toxicPlate, 200}, + {Items.earthPlate, 200}, + {Items.skyPlate, 200}, + {Items.mindPlate, 200}, + {Items.insectPlate, 200}, + {Items.stonePlate, 200}, + {Items.spookyPlate, 200}, + {Items.dracoPlate, 200}, + {Items.dreadPlate, 200}, + {Items.ironPlate, 200}, + {Items.oddIncense, 200}, + {Items.rockIncense, 200}, + {Items.fullIncense, 100}, + {Items.waveIncense, 200}, + {Items.roseIncense, 200}, + {Items.luckIncense, 1500}, + {Items.pureIncense, 100}, + {Items.protector, 300}, + {Items.electirizer, 300}, + {Items.magmarizer, 300}, + {Items.dubiousDisc, 300}, + {Items.reaperCloth, 300}, + {Items.razorClaw, 500}, + {Items.razorFang, 500}, + {Items.tm01, 1000}, + {Items.tm02, 1000}, + {Items.tm03, 1000}, + {Items.tm04, 1000}, + {Items.tm05, 1000}, + {Items.tm06, 1000}, + {Items.tm07, 2000}, + {Items.tm08, 1000}, + {Items.tm09, 1000}, + {Items.tm10, 1000}, + {Items.tm11, 2000}, + {Items.tm12, 1000}, + {Items.tm13, 1000}, + {Items.tm14, 2000}, + {Items.tm15, 2000}, + {Items.tm16, 2000}, + {Items.tm17, 1000}, + {Items.tm18, 2000}, + {Items.tm19, 1000}, + {Items.tm20, 2000}, + {Items.tm21, 1000}, + {Items.tm22, 1000}, + {Items.tm23, 1000}, + {Items.tm24, 1000}, + {Items.tm25, 2000}, + {Items.tm26, 1000}, + {Items.tm27, 1000}, + {Items.tm28, 1000}, + {Items.tm29, 1000}, + {Items.tm30, 1000}, + {Items.tm31, 1000}, + {Items.tm32, 1000}, + {Items.tm33, 2000}, + {Items.tm34, 1000}, + {Items.tm35, 1000}, + {Items.tm36, 1000}, + {Items.tm37, 2000}, + {Items.tm38, 2000}, + {Items.tm39, 1000}, + {Items.tm40, 1000}, + {Items.tm41, 1000}, + {Items.tm42, 1000}, + {Items.tm43, 1000}, + {Items.tm44, 1000}, + {Items.tm45, 1000}, + {Items.tm46, 1000}, + {Items.tm47, 1000}, + {Items.tm48, 1000}, + {Items.tm49, 1000}, + {Items.tm50, 1000}, + {Items.tm51, 1000}, + {Items.tm52, 1000}, + {Items.tm53, 1000}, + {Items.tm54, 1000}, + {Items.tm55, 1000}, + {Items.tm56, 1000}, + {Items.tm57, 1000}, + {Items.tm58, 1000}, + {Items.tm59, 1000}, + {Items.tm60, 1000}, + {Items.tm61, 1000}, + {Items.tm62, 1000}, + {Items.tm63, 1000}, + {Items.tm64, 1000}, + {Items.tm65, 1000}, + {Items.tm66, 1000}, + {Items.tm67, 1000}, + {Items.tm68, 2000}, + {Items.tm69, 1000}, + {Items.tm70, 1000}, + {Items.tm71, 1000}, + {Items.tm72, 1000}, + {Items.tm73, 1000}, + {Items.tm74, 1000}, + {Items.tm75, 1000}, + {Items.tm76, 1000}, + {Items.tm77, 1000}, + {Items.tm78, 1000}, + {Items.tm79, 1000}, + {Items.tm80, 1000}, + {Items.tm81, 1000}, + {Items.tm82, 1000}, + {Items.tm83, 1000}, + {Items.tm84, 1000}, + {Items.tm85, 1000}, + {Items.tm86, 1000}, + {Items.tm87, 1000}, + {Items.tm88, 1000}, + {Items.tm89, 1000}, + {Items.tm90, 1000}, + {Items.tm91, 1000}, + {Items.tm92, 1000}, + {Items.hm01, 0}, + {Items.hm02, 0}, + {Items.hm03, 0}, + {Items.hm04, 0}, + {Items.hm05, 0}, + {Items.hm06, 0}, + {Items.hm07, 0}, // unused in Gen 6 + {Items.hm08, 0}, // unused in Gen 6 + {Items.explorerKit, 0}, + {Items.lootSack, 0}, + {Items.ruleBook, 0}, + {Items.pokeRadar, 0}, + {Items.pointCard, 0}, + {Items.journal, 0}, + {Items.sealCase, 0}, + {Items.fashionCase, 0}, + {Items.sealBag, 0}, + {Items.palPad, 0}, + {Items.worksKey, 0}, + {Items.oldCharm, 0}, + {Items.galacticKey, 0}, + {Items.redChain, 0}, + {Items.townMap, 0}, + {Items.vsSeeker, 0}, + {Items.coinCase, 0}, + {Items.oldRod, 0}, + {Items.goodRod, 0}, + {Items.superRod, 0}, + {Items.sprayduck, 0}, + {Items.poffinCase, 0}, + {Items.bike, 0}, + {Items.suiteKey, 0}, + {Items.oaksLetter, 0}, + {Items.lunarWing, 0}, + {Items.memberCard, 0}, + {Items.azureFlute, 0}, + {Items.ssTicketJohto, 0}, + {Items.contestPass, 0}, + {Items.magmaStone, 0}, + {Items.parcelSinnoh, 0}, + {Items.coupon1, 0}, + {Items.coupon2, 0}, + {Items.coupon3, 0}, + {Items.storageKeySinnoh, 0}, + {Items.secretPotion, 0}, + {Items.vsRecorder, 0}, + {Items.gracidea, 0}, + {Items.secretKeySinnoh, 0}, + {Items.apricornBox, 0}, + {Items.unownReport, 0}, + {Items.berryPots, 0}, + {Items.dowsingMachine, 0}, + {Items.blueCard, 0}, + {Items.slowpokeTail, 0}, + {Items.clearBell, 0}, + {Items.cardKeyJohto, 0}, + {Items.basementKeyJohto, 0}, + {Items.squirtBottle, 0}, + {Items.redScale, 0}, + {Items.lostItem, 0}, + {Items.pass, 0}, + {Items.machinePart, 0}, + {Items.silverWing, 0}, + {Items.rainbowWing, 0}, + {Items.mysteryEgg, 0}, + {Items.redApricorn, 2}, + {Items.blueApricorn, 2}, + {Items.yellowApricorn, 2}, + {Items.greenApricorn, 2}, + {Items.pinkApricorn, 2}, + {Items.whiteApricorn, 2}, + {Items.blackApricorn, 2}, + {Items.fastBall, 30}, + {Items.levelBall, 30}, + {Items.lureBall, 30}, + {Items.heavyBall, 30}, + {Items.loveBall, 30}, + {Items.friendBall, 30}, + {Items.moonBall, 30}, + {Items.sportBall, 30}, + {Items.parkBall, 0}, + {Items.photoAlbum, 0}, + {Items.gbSounds, 0}, + {Items.tidalBell, 0}, + {Items.rageCandyBar, 15}, + {Items.dataCard01, 0}, + {Items.dataCard02, 0}, + {Items.dataCard03, 0}, + {Items.dataCard04, 0}, + {Items.dataCard05, 0}, + {Items.dataCard06, 0}, + {Items.dataCard07, 0}, + {Items.dataCard08, 0}, + {Items.dataCard09, 0}, + {Items.dataCard10, 0}, + {Items.dataCard11, 0}, + {Items.dataCard12, 0}, + {Items.dataCard13, 0}, + {Items.dataCard14, 0}, + {Items.dataCard15, 0}, + {Items.dataCard16, 0}, + {Items.dataCard17, 0}, + {Items.dataCard18, 0}, + {Items.dataCard19, 0}, + {Items.dataCard20, 0}, + {Items.dataCard21, 0}, + {Items.dataCard22, 0}, + {Items.dataCard23, 0}, + {Items.dataCard24, 0}, + {Items.dataCard25, 0}, + {Items.dataCard26, 0}, + {Items.dataCard27, 0}, + {Items.jadeOrb, 0}, + {Items.lockCapsule, 0}, + {Items.redOrb, 0}, + {Items.blueOrb, 0}, + {Items.enigmaStone, 0}, + {Items.prismScale, 300}, + {Items.eviolite, 1000}, + {Items.floatStone, 100}, + {Items.rockyHelmet, 600}, + {Items.airBalloon, 100}, + {Items.redCard, 100}, + {Items.ringTarget, 100}, + {Items.bindingBand, 200}, + {Items.absorbBulb, 100}, + {Items.cellBattery, 100}, + {Items.ejectButton, 100}, + {Items.fireGem, 100}, + {Items.waterGem, 100}, + {Items.electricGem, 100}, + {Items.grassGem, 100}, + {Items.iceGem, 100}, + {Items.fightingGem, 100}, + {Items.poisonGem, 100}, + {Items.groundGem, 100}, + {Items.flyingGem, 100}, + {Items.psychicGem, 100}, + {Items.bugGem, 100}, + {Items.rockGem, 100}, + {Items.ghostGem, 100}, + {Items.dragonGem, 100}, + {Items.darkGem, 100}, + {Items.steelGem, 100}, + {Items.normalGem, 100}, + {Items.healthFeather, 300}, + {Items.muscleFeather, 300}, + {Items.resistFeather, 300}, + {Items.geniusFeather, 300}, + {Items.cleverFeather, 300}, + {Items.swiftFeather, 300}, + {Items.prettyFeather, 20}, + {Items.coverFossil, 500}, + {Items.plumeFossil, 500}, + {Items.libertyPass, 0}, + {Items.passOrb, 20}, + {Items.dreamBall, 100}, + {Items.pokeToy, 100}, + {Items.propCase, 0}, + {Items.dragonSkull, 0}, + {Items.balmMushroom, 1250}, + {Items.bigNugget, 2000}, + {Items.pearlString, 1500}, + {Items.cometShard, 3000}, + {Items.relicCopper, 0}, + {Items.relicSilver, 0}, + {Items.relicGold, 0}, + {Items.relicVase, 0}, + {Items.relicBand, 0}, + {Items.relicStatue, 0}, + {Items.relicCrown, 0}, + {Items.casteliacone, 45}, + {Items.direHit2, 0}, + {Items.xSpeed2, 0}, + {Items.xSpAtk2, 0}, + {Items.xSpDef2, 0}, + {Items.xDefense2, 0}, + {Items.xAttack2, 0}, + {Items.xAccuracy2, 0}, + {Items.xSpeed3, 0}, + {Items.xSpAtk3, 0}, + {Items.xSpDef3, 0}, + {Items.xDefense3, 0}, + {Items.xAttack3, 0}, + {Items.xAccuracy3, 0}, + {Items.xSpeed6, 0}, + {Items.xSpAtk6, 0}, + {Items.xSpDef6, 0}, + {Items.xDefense6, 0}, + {Items.xAttack6, 0}, + {Items.xAccuracy6, 0}, + {Items.abilityUrge, 0}, + {Items.itemDrop, 0}, + {Items.itemUrge, 0}, + {Items.resetUrge, 0}, + {Items.direHit3, 0}, + {Items.lightStone, 0}, + {Items.darkStone, 0}, + {Items.tm93, 1000}, + {Items.tm94, 1000}, + {Items.tm95, 1000}, + {Items.xtransceiverMale, 0}, + {Items.unused622, 0}, + {Items.gram1, 0}, + {Items.gram2, 0}, + {Items.gram3, 0}, + {Items.xtransceiverFemale, 0}, + {Items.medalBox, 0}, + {Items.dNASplicersFuse, 0}, + {Items.dNASplicersSeparate, 0}, + {Items.permit, 0}, + {Items.ovalCharm, 0}, + {Items.shinyCharm, 0}, + {Items.plasmaCard, 0}, + {Items.grubbyHanky, 0}, + {Items.colressMachine, 0}, + {Items.droppedItemCurtis, 0}, + {Items.droppedItemYancy, 0}, + {Items.revealGlass, 0}, + {Items.weaknessPolicy, 200}, + {Items.assaultVest, 600}, + {Items.holoCasterMale, 0}, + {Items.profsLetter, 0}, + {Items.rollerSkates, 0}, + {Items.pixiePlate, 200}, + {Items.abilityCapsule, 500}, + {Items.whippedDream, 300}, + {Items.sachet, 300}, + {Items.luminousMoss, 20}, + {Items.snowball, 20}, + {Items.safetyGoggles, 300}, + {Items.pokeFlute, 0}, + {Items.richMulch, 20}, + {Items.surpriseMulch, 20}, + {Items.boostMulch, 20}, + {Items.amazeMulch, 20}, + {Items.gengarite, 1000}, + {Items.gardevoirite, 1000}, + {Items.ampharosite, 1000}, + {Items.venusaurite, 1000}, + {Items.charizarditeX, 1000}, + {Items.blastoisinite, 1000}, + {Items.mewtwoniteX, 2000}, + {Items.mewtwoniteY, 2000}, + {Items.blazikenite, 1000}, + {Items.medichamite, 500}, + {Items.houndoominite, 1000}, + {Items.aggronite, 1000}, + {Items.banettite, 500}, + {Items.tyranitarite, 2000}, + {Items.scizorite, 1000}, + {Items.pinsirite, 1000}, + {Items.aerodactylite, 1000}, + {Items.lucarionite, 1000}, + {Items.abomasite, 500}, + {Items.kangaskhanite, 500}, + {Items.gyaradosite, 1000}, + {Items.absolite, 500}, + {Items.charizarditeY, 1000}, + {Items.alakazite, 1000}, + {Items.heracronite, 1000}, + {Items.mawilite, 300}, + {Items.manectite, 500}, + {Items.garchompite, 2000}, + {Items.latiasite, 2000}, + {Items.latiosite, 2000}, + {Items.roseliBerry, 100}, + {Items.keeBerry, 100}, + {Items.marangaBerry, 100}, + {Items.sprinklotad, 0}, + {Items.tm96, 1000}, + {Items.tm97, 1000}, + {Items.tm98, 1000}, + {Items.tm99, 1000}, + {Items.tm100, 500}, + {Items.powerPlantPass, 0}, + {Items.megaRing, 0}, + {Items.intriguingStone, 0}, + {Items.commonStone, 0}, + {Items.discountCoupon, 2}, + {Items.elevatorKey, 0}, + {Items.tmvPass, 0}, + {Items.honorofKalos, 0}, + {Items.adventureGuide, 0}, + {Items.strangeSouvenir, 1}, + {Items.lensCase, 0}, + {Items.makeupBag, 0}, + {Items.travelTrunk, 0}, + {Items.lumioseGalette, 45}, + {Items.shalourSable, 45}, + {Items.jawFossil, 500}, + {Items.sailFossil, 500}, + {Items.lookerTicket, 0}, + {Items.bikeYellow, 0}, + {Items.holoCasterFemale, 0}, + {Items.fairyGem, 100}, + {Items.megaCharm, 0}, + {Items.megaGlove, 0}, + {Items.machBike, 0}, + {Items.acroBike, 0}, + {Items.wailmerPail, 0}, + {Items.devonParts, 0}, + {Items.sootSack, 0}, + {Items.basementKeyHoenn, 0}, + {Items.pokeblockKit, 0}, + {Items.letter, 0}, + {Items.eonTicket, 0}, + {Items.scanner, 0}, + {Items.goGoggles, 0}, + {Items.meteoriteFirstForm, 0}, + {Items.keytoRoom1, 0}, + {Items.keytoRoom2, 0}, + {Items.keytoRoom4, 0}, + {Items.keytoRoom6, 0}, + {Items.storageKeyHoenn, 0}, + {Items.devonScope, 0}, + {Items.ssTicketHoenn, 0}, + {Items.hm07ORAS, 0}, + {Items.devonScubaGear, 0}, + {Items.contestCostumeMale, 0}, + {Items.contestCostumeFemale, 0}, + {Items.magmaSuit, 0}, + {Items.aquaSuit, 0}, + {Items.pairOfTickets, 0}, + {Items.megaBracelet, 0}, + {Items.megaPendant, 0}, + {Items.megaGlasses, 0}, + {Items.megaAnchor, 0}, + {Items.megaStickpin, 0}, + {Items.megaTiara, 0}, + {Items.megaAnklet, 0}, + {Items.meteoriteSecondForm, 0}, + {Items.swampertite, 1000}, + {Items.sceptilite, 1000}, + {Items.sablenite, 300}, + {Items.altarianite, 500}, + {Items.galladite, 1000}, + {Items.audinite, 500}, + {Items.metagrossite, 2000}, + {Items.sharpedonite, 500}, + {Items.slowbronite, 500}, + {Items.steelixite, 1000}, + {Items.pidgeotite, 500}, + {Items.glalitite, 500}, + {Items.diancite, 2000}, + {Items.prisonBottle, 0}, + {Items.megaCuff, 0}, + {Items.cameruptite, 500}, + {Items.lopunnite, 500}, + {Items.salamencite, 2000}, + {Items.beedrillite, 300}, + {Items.meteoriteThirdForm, 0}, + {Items.meteoriteFinalForm, 0}, + {Items.keyStone, 0}, + {Items.meteoriteShard, 0}, + {Items.eonFlute, 0}, + }).collect(Collectors.toMap(kv -> kv[0], kv -> kv[1])); + + public static final int[] xyMapNumToPokedexIndex = { + 7, // Couriway Town + 8, // Ambrette Town + 13, // Cyllage City + 14, // Shalour City + 16, // Laverre City + 22, // Route 2 + 23, // Route 3 + 24, // Route 4 + 25, // Route 5 + 26, // Route 6 + 27, // Route 7 + 28, // Route 8 + 29, // Route 9 + 30, // Route 10 + 31, // Route 11 + 32, // Route 12 + 33, // Route 13 + 34, // Route 14 + 35, // Route 15 + 36, // Route 16 + 37, // Route 17 + 38, // Route 18 + 39, // Route 19 + 40, // Route 20 + 41, // Route 21 + 42, // Route 22 + 44, // Santalune Forest + 45, // Parfum Palace + 46, 46, // Glittering Cave + 47, 47, 47, 47, // Reflection Cave + 49, 49, 49, 49, 49, // Frost Cavern + 50, // Pokemon Village + 51, 51, 51, 51, 51, // Victory Road + 52, // Connecting Cave + 54, 54, 54, 54, 54, // Terminus Cave + 55, // Lost Hotel + 43, // Azure Bay + 46, 46, 46, 46, // Glittering Cave (ceiling) + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, // Reflection Cave (ceiling) + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, // Victory Road (ceiling and sky) + 54, 54, 54, 54, 54, 54, 54, 54, 54, // Terminus Cave (ceiling) + 26, 26, 26, // Route 6 (rustling bush) + 38, 38, 38, 38 // Route 18 (rustling bush) + }; + + public static final int[] orasMapNumToPokedexIndex = { + 2, // Dewford Town + 6, // Pacifidlog Town + 7, // Petalburg City + 8, // Slateport City + 12, // Lilycove City + 13, // Mossdeep City + 14, // Sootopolis City + 15, // Ever Grande City + 17, // Route 101 + 18, // Route 102 + 19, // Route 103 + 20, // Route 104 (North Section) + 21, // Route 104 (South Section) + 22, // Route 105 + 23, // Route 106 + 24, // Route 107 + 26, // Route 108 + 27, // Route 109 + 28, // Route 110 + 30, // Route 111 (Desert) + 32, // Route 111 (South Section) + 33, // Route 112 (North Section) + 34, // Route 112 (South Section) + 35, // Route 113 + 36, // Route 114 + 37, // Route 115 + 38, // Route 116 + 39, // Route 117 + 40, // Route 118 + 41, 41, // Route 119 + 43, 43, // Route 120 + 45, // Route 121 + 46, // Route 122 + 47, // Route 123 + 48, // Route 124 + 50, // Route 125 + 51, // Route 126 + 53, // Route 127 + 55, // Route 128 + 57, // Route 129 + 59, // Route 130 + 61, // Route 131 + 62, // Route 132 + 63, // Route 133 + 64, // Route 134 + 25, // Route 107 (Underwater) + 49, // Route 124 (Underwater) + 52, // Route 126 (Underwater) + 56, // Route 128 (Underwater) + 58, // Route 129 (Underwater) + 60, // Route 130 (Underwater) + 69, 69, 69, 69, // Meteor Falls + 73, // Rusturf Tunnel + 74, 74, 74, // Granite Cave + 78, // Petalburg Woods + 80, // Jagged Pass + 81, // Fiery Path + 82, 82, 82, 82, 82, 82, // Mt. Pyre + -1, // Team Aqua Hideout + 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, // Seafloor Cavern + 102, 102, 102, 102, 102, // Cave of Origin + 114, 114, 114, 114, // Victory Road + 119, 119, 119, 119, 119, 119, 119, // Shoal Cave + 130, // New Mauville + 136, 136, 136, 136, // Sea Mauville + -1, // Sealed Chamber + -1, -1, -1, -1, // Scorched Slab + -1, // Team Magma Hideout + 150, // Sky Pillar + -1, -1, -1, -1, -1, -1, -1, -1, // Mirage Forest + -1, -1, -1, -1, -1, -1, -1, -1, // Mirage Island + -1, // Mirage Mountain + 159, // Battle Resort + 65, 65, 65, 65, // Safari Zone + 102, // Cave of Origin + -1, -1, -1, -1, -1, -1, -1, // Mirage Mountain + -1, -1, -1, -1, -1, -1, -1, -1, // Mirage Cave + -1, // Mt. Pyre (unused) + -1 // Sootopolis City (unused) + }; +} diff --git a/src/com/pkrandom/constants/Gen7Constants.java b/src/com/pkrandom/constants/Gen7Constants.java new file mode 100644 index 0000000..2c1d9a1 --- /dev/null +++ b/src/com/pkrandom/constants/Gen7Constants.java @@ -0,0 +1,2243 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Gen7Constants.java - Constants for Sun/Moon/Ultra Sun/Ultra Moon --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.pokemon.ItemList; +import com.pkrandom.pokemon.MoveCategory; +import com.pkrandom.pokemon.Trainer; +import com.pkrandom.pokemon.Type; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class Gen7Constants { + + public static final int Type_SM = N3DSConstants.Type_SM; + public static final int Type_USUM = N3DSConstants.Type_USUM; + + private static final int pokemonCountSM = 802, pokemonCountUSUM = 807; + private static final int formeCountSM = 158, formeCountUSUM = 168; + private static final int moveCountSM = 719, moveCountUSUM = 728; + private static final int highestAbilityIndexSM = Abilities.prismArmor, highestAbilityIndexUSUM = Abilities.neuroforce; + + public static final int bsHPOffset = 0, bsAttackOffset = 1, bsDefenseOffset = 2, bsSpeedOffset = 3, + bsSpAtkOffset = 4, bsSpDefOffset = 5, bsPrimaryTypeOffset = 6, bsSecondaryTypeOffset = 7, + bsCatchRateOffset = 8, bsCommonHeldItemOffset = 12, bsRareHeldItemOffset = 14, bsDarkGrassHeldItemOffset = 16, + bsGenderOffset = 18, bsGrowthCurveOffset = 21, bsAbility1Offset = 24, bsAbility2Offset = 25, + bsAbility3Offset = 26, bsCallRateOffset = 27, bsFormeOffset = 28, bsFormeSpriteOffset = 30, + bsFormeCountOffset = 32, bsTMHMCompatOffset = 40, bsSpecialMTCompatOffset = 56, bsMTCompatOffset = 60; + + public static final int bsSize = 0x54; + + public static final int evolutionMethodCount = 42; + + private static List speciesWithAlolanForms = Arrays.asList( + Species.rattata, Species.raticate, Species.raichu, Species.sandshrew, Species.sandslash, Species.vulpix, + Species.ninetales, Species.diglett, Species.dugtrio, Species.meowth, Species.persian, Species.geodude, + Species.graveler, Species.golem, Species.grimer, Species.muk, Species.exeggutor, Species.marowak + ); + + private static final Map dummyFormeSuffixes = setupDummyFormeSuffixes(); + private static final Map> formeSuffixesByBaseForme = setupFormeSuffixesByBaseForme(); + + public static String getFormeSuffixByBaseForme(int baseForme, int formNum) { + return formeSuffixesByBaseForme.getOrDefault(baseForme,dummyFormeSuffixes).getOrDefault(formNum,""); + } + + public static List getIrregularFormes(int romType) { + if (romType == Type_SM) { + return irregularFormesSM; + } else if (romType == Type_USUM) { + return irregularFormesUSUM; + } + return irregularFormesSM; + } + + public static final List irregularFormesSM = Arrays.asList( + Species.SMFormes.castformF, Species.SMFormes.castformW, Species.SMFormes.castformI, + Species.SMFormes.darmanitanZ, + Species.SMFormes.meloettaP, + Species.SMFormes.kyuremW, + Species.SMFormes.kyuremB, + Species.SMFormes.gengarMega, + Species.SMFormes.gardevoirMega, + Species.SMFormes.ampharosMega, + Species.SMFormes.venusaurMega, + Species.SMFormes.charizardMegaX, Species.SMFormes.charizardMegaY, + Species.SMFormes.mewtwoMegaX, Species.SMFormes.mewtwoMegaY, + Species.SMFormes.blazikenMega, + Species.SMFormes.medichamMega, + Species.SMFormes.houndoomMega, + Species.SMFormes.aggronMega, + Species.SMFormes.banetteMega, + Species.SMFormes.tyranitarMega, + Species.SMFormes.scizorMega, + Species.SMFormes.pinsirMega, + Species.SMFormes.aerodactylMega, + Species.SMFormes.lucarioMega, + Species.SMFormes.abomasnowMega, + Species.SMFormes.aegislashB, + Species.SMFormes.blastoiseMega, + Species.SMFormes.kangaskhanMega, + Species.SMFormes.gyaradosMega, + Species.SMFormes.absolMega, + Species.SMFormes.alakazamMega, + Species.SMFormes.heracrossMega, + Species.SMFormes.mawileMega, + Species.SMFormes.manectricMega, + Species.SMFormes.garchompMega, + Species.SMFormes.latiosMega, + Species.SMFormes.latiasMega, + Species.SMFormes.swampertMega, + Species.SMFormes.sceptileMega, + Species.SMFormes.sableyeMega, + Species.SMFormes.altariaMega, + Species.SMFormes.galladeMega, + Species.SMFormes.audinoMega, + Species.SMFormes.sharpedoMega, + Species.SMFormes.slowbroMega, + Species.SMFormes.steelixMega, + Species.SMFormes.pidgeotMega, + Species.SMFormes.glalieMega, + Species.SMFormes.diancieMega, + Species.SMFormes.metagrossMega, + Species.SMFormes.kyogreP, + Species.SMFormes.groudonP, + Species.SMFormes.rayquazaMega, + Species.SMFormes.cameruptMega, + Species.SMFormes.lopunnyMega, + Species.SMFormes.salamenceMega, + Species.SMFormes.beedrillMega, + Species.SMFormes.wishiwashiS, + Species.SMFormes.greninjaA, + Species.SMFormes.zygardeC, + Species.SMFormes.miniorC + ); + + public static final List irregularFormesUSUM = Arrays.asList( + Species.USUMFormes.castformF, Species.USUMFormes.castformW, Species.USUMFormes.castformI, + Species.USUMFormes.darmanitanZ, + Species.USUMFormes.meloettaP, + Species.USUMFormes.kyuremW, + Species.USUMFormes.kyuremB, + Species.USUMFormes.gengarMega, + Species.USUMFormes.gardevoirMega, + Species.USUMFormes.ampharosMega, + Species.USUMFormes.venusaurMega, + Species.USUMFormes.charizardMegaX, Species.USUMFormes.charizardMegaY, + Species.USUMFormes.mewtwoMegaX, Species.USUMFormes.mewtwoMegaY, + Species.USUMFormes.blazikenMega, + Species.USUMFormes.medichamMega, + Species.USUMFormes.houndoomMega, + Species.USUMFormes.aggronMega, + Species.USUMFormes.banetteMega, + Species.USUMFormes.tyranitarMega, + Species.USUMFormes.scizorMega, + Species.USUMFormes.pinsirMega, + Species.USUMFormes.aerodactylMega, + Species.USUMFormes.lucarioMega, + Species.USUMFormes.abomasnowMega, + Species.USUMFormes.aegislashB, + Species.USUMFormes.blastoiseMega, + Species.USUMFormes.kangaskhanMega, + Species.USUMFormes.gyaradosMega, + Species.USUMFormes.absolMega, + Species.USUMFormes.alakazamMega, + Species.USUMFormes.heracrossMega, + Species.USUMFormes.mawileMega, + Species.USUMFormes.manectricMega, + Species.USUMFormes.garchompMega, + Species.USUMFormes.latiosMega, + Species.USUMFormes.latiasMega, + Species.USUMFormes.swampertMega, + Species.USUMFormes.sceptileMega, + Species.USUMFormes.sableyeMega, + Species.USUMFormes.altariaMega, + Species.USUMFormes.galladeMega, + Species.USUMFormes.audinoMega, + Species.USUMFormes.sharpedoMega, + Species.USUMFormes.slowbroMega, + Species.USUMFormes.steelixMega, + Species.USUMFormes.pidgeotMega, + Species.USUMFormes.glalieMega, + Species.USUMFormes.diancieMega, + Species.USUMFormes.metagrossMega, + Species.USUMFormes.kyogreP, + Species.USUMFormes.groudonP, + Species.USUMFormes.rayquazaMega, + Species.USUMFormes.cameruptMega, + Species.USUMFormes.lopunnyMega, + Species.USUMFormes.salamenceMega, + Species.USUMFormes.beedrillMega, + Species.USUMFormes.wishiwashiS, + Species.USUMFormes.greninjaA, + Species.USUMFormes.zygardeC, + Species.USUMFormes.miniorC, + Species.USUMFormes.necrozmaDM, + Species.USUMFormes.necrozmaDW, + Species.USUMFormes.necrozmaU + ); + + public static final MoveCategory[] moveCategoryIndices = { MoveCategory.STATUS, MoveCategory.PHYSICAL, + MoveCategory.SPECIAL }; + + public static byte moveCategoryToByte(MoveCategory cat) { + switch (cat) { + case PHYSICAL: + return 1; + case SPECIAL: + return 2; + case STATUS: + default: + return 0; + } + } + + public static final int noDamageTargetTrappingEffect = 106, noDamageFieldTrappingEffect = 354, + damageAdjacentFoesTrappingEffect = 373, damageTargetTrappingEffect = 384; + + public static final int noDamageStatusQuality = 1, noDamageStatChangeQuality = 2, damageStatusQuality = 4, + noDamageStatusAndStatChangeQuality = 5, damageTargetDebuffQuality = 6, damageUserBuffQuality = 7, + damageAbsorbQuality = 8; + + public static List bannedMoves = Arrays.asList(Moves.darkVoid, Moves.hyperspaceFury); + + public static final Type[] typeTable = constructTypeTable(); + + private static final String tmDataPrefixSM = "034003410342034303", tmDataPrefixUSUM = "03BC03BD03BE03BF03"; + public static final int tmCount = 100, tmBlockOneCount = 92, tmBlockTwoCount = 3, tmBlockThreeCount = 5, + tmBlockOneOffset = Items.tm01, tmBlockTwoOffset = Items.tm93, tmBlockThreeOffset = Items.tm96; + public static final String itemPalettesPrefix = "070000000000000000010100"; + + public static final int shopItemsOffsetSM = 0x50A8; + public static final int shopItemsOffsetUSUM = 0x50BC; + + public static final int tutorsOffset = 0x54DE; + public static final String tutorsPrefix = "5F6F6E5F6F6666FF"; + public static final int tutorMoveCount = 67; + + public static final String[] fastestTextPrefixes = new String[]{"1080BDE80E000500F0412DE9", "34019FE50060A0E3"}; + + private static final List mainGameShopsSM = Arrays.asList( + 8, 9, 10, 11, 14, 15, 17, 20, 21, 22, 23 + ); + + private static final List mainGameShopsUSUM = Arrays.asList( + 8, 9, 10, 11, 14, 15, 17, 20, 21, 22, 23, 24, 25, 26, 27 + ); + + public static final List evolutionItems = Arrays.asList(Items.sunStone, Items.moonStone, Items.fireStone, + Items.thunderStone, Items.waterStone, Items.leafStone, Items.shinyStone, Items.duskStone, Items.dawnStone, + Items.ovalStone, Items.kingsRock, Items.deepSeaTooth, Items.deepSeaScale, Items.metalCoat, Items.dragonScale, + Items.upgrade, Items.protector, Items.electirizer, Items.magmarizer, Items.dubiousDisc, Items.reaperCloth, + Items.razorClaw, Items.razorFang, Items.prismScale, Items.whippedDream, Items.sachet, Items.iceStone); + + private static final List relevantEncounterFilesSM = setupRelevantEncounterFiles(Type_SM); + private static final List relevantEncounterFilesUSUM = setupRelevantEncounterFiles(Type_USUM); + + public static final List heldZCrystals = Arrays.asList( + Items.normaliumZHeld, // Normal + Items.fightiniumZHeld, // Fighting + Items.flyiniumZHeld, // Flying + Items.poisoniumZHeld, // Poison + Items.groundiumZHeld, // Ground + Items.rockiumZHeld, // Rock + Items.buginiumZHeld, // Bug + Items.ghostiumZHeld, // Ghost + Items.steeliumZHeld, // Steel + Items.firiumZHeld, // Fire + Items.wateriumZHeld, // Water + Items.grassiumZHeld, // Grass + Items.electriumZHeld, // Electric + Items.psychiumZHeld, // Psychic + Items.iciumZHeld, // Ice + Items.dragoniumZHeld, // Dragon + Items.darkiniumZHeld, // Dark + Items.fairiumZHeld // Fairy + ); + + public static final Map> abilityVariations = setupAbilityVariations(); + + private static Map> setupAbilityVariations() { + Map> map = new HashMap<>(); + map.put(Abilities.insomnia, Arrays.asList(Abilities.insomnia, Abilities.vitalSpirit)); + map.put(Abilities.clearBody, Arrays.asList(Abilities.clearBody, Abilities.whiteSmoke, Abilities.fullMetalBody)); + map.put(Abilities.hugePower, Arrays.asList(Abilities.hugePower, Abilities.purePower)); + map.put(Abilities.battleArmor, Arrays.asList(Abilities.battleArmor, Abilities.shellArmor)); + map.put(Abilities.cloudNine, Arrays.asList(Abilities.cloudNine, Abilities.airLock)); + map.put(Abilities.filter, Arrays.asList(Abilities.filter, Abilities.solidRock, Abilities.prismArmor)); + map.put(Abilities.roughSkin, Arrays.asList(Abilities.roughSkin, Abilities.ironBarbs)); + map.put(Abilities.moldBreaker, Arrays.asList(Abilities.moldBreaker, Abilities.turboblaze, Abilities.teravolt)); + map.put(Abilities.wimpOut, Arrays.asList(Abilities.wimpOut, Abilities.emergencyExit)); + map.put(Abilities.queenlyMajesty, Arrays.asList(Abilities.queenlyMajesty, Abilities.dazzling)); + map.put(Abilities.gooey, Arrays.asList(Abilities.gooey, Abilities.tanglingHair)); + map.put(Abilities.receiver, Arrays.asList(Abilities.receiver, Abilities.powerOfAlchemy)); + map.put(Abilities.multiscale, Arrays.asList(Abilities.multiscale, Abilities.shadowShield)); + + return map; + } + + public static final List uselessAbilities = Arrays.asList(Abilities.forecast, Abilities.multitype, + Abilities.flowerGift, Abilities.zenMode, Abilities.stanceChange, Abilities.shieldsDown, Abilities.schooling, + Abilities.disguise, Abilities.battleBond, Abilities.powerConstruct, Abilities.rksSystem); + + private static final String saveLoadFormeReversionPrefixSM = "00EB040094E50C1094E5F70E80E2", saveLoadFormeReversionPrefixUSUM = "00EB040094E50C1094E5030B80E2EE0F80E2"; + public static final String afterBattleFormeReversionPrefix = "0055E10B00001A0010A0E30700A0E1"; + + public static final String ninjaskSpeciesPrefix = "11FF2FE11CD08DE2F080BDE8", shedinjaPrefix = "A0E194FDFFEB0040A0E1"; + + public static final String beastLusaminePokemonBoostsPrefix = "1D14FFFF"; + public static final int beastLusamineTrainerIndex = 157; + + public static final String miniorWildEncounterPatchPrefix = "032C42E2062052E2"; + + public static final int zygardeAssemblyScriptFile = 45; + public static final String zygardeAssemblyFormePrefix = "BC21CDE1B801CDE1", zygardeAssemblySpeciesPrefix = "FBEB4CD08DE20400A0E1F08FBDE8"; + + public static final String friendshipValueForEvoLocator = "DC0050E3F700002A"; + + public static final String perfectOddsBranchLocator = "050000BA000050E3"; + + public static int getPokemonCount(int romType) { + if (romType == Type_SM) { + return pokemonCountSM; + } else if (romType == Type_USUM) { + return pokemonCountUSUM; + } + return pokemonCountSM; + } + + public static List getRegularShopItems(int romType) { + if (romType == Type_SM) { + return regularShopItemsSM; + } else { + return regularShopItemsUSUM; + } + } + + public static final List consumableHeldItems = setupAllConsumableItems(); + + private static List setupAllConsumableItems() { + List list = new ArrayList<>(Gen6Constants.consumableHeldItems); + list.addAll(Arrays.asList(Items.adrenalineOrb, Items.electricSeed, Items.psychicSeed, Items.mistySeed, Items.grassySeed)); + return list; + } + + public static final List allHeldItems = setupAllHeldItems(); + + private static List setupAllHeldItems() { + // We intentionally do not include Z Crystals in this list. Adding Z-Crystals to random trainers should + // probably require its own setting if desired. + List list = new ArrayList<>(Gen6Constants.allHeldItems); + list.addAll(Arrays.asList(Items.adrenalineOrb, Items.electricSeed, Items.psychicSeed, Items.mistySeed, Items.grassySeed)); + list.addAll(Arrays.asList(Items.terrainExtender, Items.protectivePads)); + return list; + } + + public static final List generalPurposeConsumableItems = initializeGeneralPurposeConsumableItems(); + + private static List initializeGeneralPurposeConsumableItems() { + List list = new ArrayList<>(Gen6Constants.generalPurposeConsumableItems); + // These berries are worth the risk of causing confusion because they heal for half max HP. + list.addAll(Arrays.asList(Items.figyBerry, Items.wikiBerry, Items.magoBerry, + Items.aguavBerry, Items.iapapaBerry, Items.adrenalineOrb)); + return Collections.unmodifiableList(list); + } + + public static final List generalPurposeItems = initializeGeneralPurposeItems(); + + private static List initializeGeneralPurposeItems() { + List list = new ArrayList<>(Gen6Constants.generalPurposeItems); + list.addAll(Arrays.asList(Items.protectivePads)); + return Collections.unmodifiableList(list); + } + + public static final Map> moveBoostingItems = initializeMoveBoostingItems(); + + private static Map> initializeMoveBoostingItems() { + Map> map = new HashMap<>(Gen6Constants.moveBoostingItems); + map.put(Moves.electricTerrain, Arrays.asList(Items.terrainExtender)); + map.put(Moves.grassyTerrain, Arrays.asList(Items.terrainExtender)); + map.put(Moves.mistyTerrain, Arrays.asList(Items.terrainExtender)); + map.put(Moves.psychicTerrain, Arrays.asList(Items.terrainExtender)); + map.put(Moves.strengthSap, Arrays.asList(Items.bigRoot)); + return Collections.unmodifiableMap(map); + } + public static final Map> abilityBoostingItems = initializeAbilityBoostingItems(); + + private static Map> initializeAbilityBoostingItems() { + Map> map = new HashMap<>(Gen6Constants.abilityBoostingItems); + map.put(Abilities.electricSurge, Arrays.asList(Items.terrainExtender)); + map.put(Abilities.grassySurge, Arrays.asList(Items.terrainExtender)); + map.put(Abilities.mistySurge, Arrays.asList(Items.terrainExtender)); + map.put(Abilities.psychicSurge, Arrays.asList(Items.terrainExtender)); + return Collections.unmodifiableMap(map); + } + + public static final Map consumableAbilityBoostingItems = initializeConsumableAbilityBoostingItems(); + + private static Map initializeConsumableAbilityBoostingItems() { + Map map = new HashMap<>(); + map.put(Abilities.electricSurge, Items.electricSeed); + map.put(Abilities.grassySurge, Items.grassySeed); + map.put(Abilities.mistySurge, Items.mistySeed); + map.put(Abilities.psychicSurge, Items.psychicSeed); + return Collections.unmodifiableMap(map); + } + + // None of these have new entries in Gen VII. + public static final Map consumableTypeBoostingItems = Gen6Constants.consumableTypeBoostingItems; + public static final Map> speciesBoostingItems = Gen6Constants.speciesBoostingItems; + public static final Map> typeBoostingItems = Gen6Constants.typeBoostingItems; + public static final Map weaknessReducingBerries = Gen6Constants.weaknessReducingBerries; + + public static boolean isZCrystal(int itemIndex) { + // From https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_(Generation_VII) + return (itemIndex >= Items.normaliumZHeld && itemIndex <= Items.pikaniumZHeld) || + (itemIndex >= Items.decidiumZHeld && itemIndex <= Items.pikashuniumZBag) || + (itemIndex >= Items.solganiumZBag && itemIndex <= Items.kommoniumZBag); + + } + + public static List getShopNames(int romType) { + List shopNames = new ArrayList<>(); + shopNames.add("Primary 0 Trials"); + shopNames.add("Primary 1 Trials"); + shopNames.add("Primary 2 Trials"); + shopNames.add("Primary 3 Trials"); + shopNames.add("Primary 4 Trials"); + shopNames.add("Primary 5 Trials"); + shopNames.add("Primary 6 Trials"); + shopNames.add("Primary 7 Trials"); + shopNames.add("Konikoni City Incenses"); + shopNames.add("Konikoni City Herbs"); + shopNames.add("Hau'oli City Secondary"); + shopNames.add("Route 2 Secondary"); + shopNames.add("Heahea City Secondary (TMs)"); + shopNames.add("Royal Avenue Secondary (TMs)"); + shopNames.add("Route 8 Secondary"); + shopNames.add("Paniola Town Secondary"); + shopNames.add("Malie City Secondary (TMs)"); + shopNames.add("Mount Hokulani Secondary"); + shopNames.add("Seafolk Village Secondary (TMs)"); + shopNames.add("Konikoni City TMs"); + shopNames.add("Konikoni City Stones"); + shopNames.add("Thrifty Megamart, Center-Left"); + shopNames.add("Thrifty Megamart, Center-Right"); + shopNames.add("Thrifty Megamart, Right"); + if (romType == Type_USUM) { + shopNames.add("Route 5 Secondary"); + shopNames.add("Konikoni City Secondary"); + shopNames.add("Tapu Village Secondary"); + shopNames.add("Mount Lanakila Secondary"); + } + return shopNames; + } + + public static List getMainGameShops(int romType) { + if (romType == Type_SM) { + return mainGameShopsSM; + } else { + return mainGameShopsUSUM; + } + } + + public static int getShopItemsOffset(int romType) { + if (romType == Type_SM) { + return shopItemsOffsetSM; + } else { + return shopItemsOffsetUSUM; + } + } + + public static int getFormeCount(int romType) { + if (romType == Type_SM) { + return formeCountSM; + } else { + return formeCountUSUM; + } + } + + public static int getMoveCount(int romType) { + if (romType == Type_SM) { + return moveCountSM; + } else if (romType == Type_USUM) { + return moveCountUSUM; + } + return moveCountSM; + } + + public static String getTmDataPrefix(int romType) { + if (romType == Type_SM) { + return tmDataPrefixSM; + } else if (romType == Type_USUM) { + return tmDataPrefixUSUM; + } + return tmDataPrefixSM; + } + + public static int getHighestAbilityIndex(int romType) { + if (romType == Type_SM) { + return highestAbilityIndexSM; + } else if (romType == Type_USUM) { + return highestAbilityIndexUSUM; + } + return highestAbilityIndexSM; + } + + public static List getRelevantEncounterFiles(int romType) { + if (romType == Type_SM) { + return relevantEncounterFilesSM; + } else { + return relevantEncounterFilesUSUM; + } + } + + public static String getSaveLoadFormeReversionPrefix(int romType) { + if (romType == Type_SM) { + return saveLoadFormeReversionPrefixSM; + } else { + return saveLoadFormeReversionPrefixUSUM; + } + } + + private static Map> setupFormeSuffixesByBaseForme() { + Map> map = new HashMap<>(); + + Map deoxysMap = new HashMap<>(); + deoxysMap.put(1,"-A"); + deoxysMap.put(2,"-D"); + deoxysMap.put(3,"-S"); + map.put(Species.deoxys, deoxysMap); + + Map wormadamMap = new HashMap<>(); + wormadamMap.put(1,"-S"); + wormadamMap.put(2,"-T"); + map.put(Species.wormadam, wormadamMap); + + Map shayminMap = new HashMap<>(); + shayminMap.put(1,"-S"); + map.put(Species.shaymin, shayminMap); + + Map giratinaMap = new HashMap<>(); + giratinaMap.put(1,"-O"); + map.put(Species.giratina, giratinaMap); + + Map rotomMap = new HashMap<>(); + rotomMap.put(1,"-H"); + rotomMap.put(2,"-W"); + rotomMap.put(3,"-Fr"); + rotomMap.put(4,"-Fa"); + rotomMap.put(5,"-M"); + map.put(Species.rotom, rotomMap); + + Map castformMap = new HashMap<>(); + castformMap.put(1,"-F"); + castformMap.put(2,"-W"); + castformMap.put(3,"-I"); + map.put(Species.castform, castformMap); + + Map basculinMap = new HashMap<>(); + basculinMap.put(1,"-B"); + map.put(Species.basculin, basculinMap); + + Map darmanitanMap = new HashMap<>(); + darmanitanMap.put(1,"-Z"); + map.put(Species.darmanitan, darmanitanMap); + + Map meloettaMap = new HashMap<>(); + meloettaMap.put(1,"-P"); + map.put(Species.meloetta, meloettaMap); + + Map kyuremMap = new HashMap<>(); + kyuremMap.put(1,"-W"); + kyuremMap.put(2,"-B"); + map.put(Species.kyurem, kyuremMap); + + Map tornadusMap = new HashMap<>(); + tornadusMap.put(1,"-T"); + map.put(Species.tornadus, tornadusMap); + + Map thundurusMap = new HashMap<>(); + thundurusMap.put(1,"-T"); + map.put(Species.thundurus, thundurusMap); + + Map landorusMap = new HashMap<>(); + landorusMap.put(1,"-T"); + map.put(Species.landorus, landorusMap); + + Map meowsticMap = new HashMap<>(); + meowsticMap.put(1,"-F"); + map.put(Species.meowstic, meowsticMap); + + Map aegislashMap = new HashMap<>(); + aegislashMap.put(1,"-B"); + map.put(Species.aegislash, aegislashMap); + + Map pumpkabooMap = new HashMap<>(); + pumpkabooMap.put(1,"-M"); + pumpkabooMap.put(2,"-L"); + pumpkabooMap.put(3,"-XL"); + map.put(Species.pumpkaboo, pumpkabooMap); + + Map gourgeistMap = new HashMap<>(); + gourgeistMap.put(1,"-M"); + gourgeistMap.put(2,"-L"); + gourgeistMap.put(3,"-XL"); + map.put(Species.gourgeist, gourgeistMap); + + Map floetteMap = new HashMap<>(); + floetteMap.put(5,"-E"); + map.put(Species.floette, floetteMap); + + Map kyogreMap = new HashMap<>(); + kyogreMap.put(1,"-P"); + map.put(Species.kyogre, kyogreMap); + + Map groudonMap = new HashMap<>(); + groudonMap.put(1,"-P"); + map.put(Species.groudon, groudonMap); + + Map rayquazaMap = new HashMap<>(); + rayquazaMap.put(1,"-Mega"); + map.put(Species.rayquaza, rayquazaMap); + + Map hoopaMap = new HashMap<>(); + hoopaMap.put(1,"-U"); + map.put(Species.hoopa, hoopaMap); + + for (Integer species: Gen6Constants.speciesToMegaStoneORAS.keySet()) { + Map megaMap = new HashMap<>(); + if (species == Species.charizard || species == Species.mewtwo) { + megaMap.put(1,"-Mega-X"); + megaMap.put(2,"-Mega-Y"); + } else { + megaMap.put(1,"-Mega"); + } + map.put(species,megaMap); + } + + Map wishiwashiMap = new HashMap<>(); + wishiwashiMap.put(1,"-S"); + map.put(Species.wishiwashi, wishiwashiMap); + + Map oricorioMap = new HashMap<>(); + oricorioMap.put(1,"-E"); + oricorioMap.put(2,"-P"); + oricorioMap.put(3,"-G"); + map.put(Species.oricorio, oricorioMap); + + Map lycanrocMap = new HashMap<>(); + lycanrocMap.put(1,"-M"); + lycanrocMap.put(2,"-D"); + map.put(Species.lycanroc, lycanrocMap); + + for (int species: speciesWithAlolanForms) { + Map alolanMap = new HashMap<>(); + alolanMap.put(1,"-A"); + map.put(species, alolanMap); + } + + Map greninjaMap = new HashMap<>(); + greninjaMap.put(2,"-A"); + map.put(Species.greninja, greninjaMap); + + Map zygardeMap = new HashMap<>(); + zygardeMap.put(1,"-10"); + zygardeMap.put(4,"-C"); + map.put(Species.zygarde, zygardeMap); + + Map miniorMap = new HashMap<>(); + miniorMap.put(7,"-C"); + map.put(Species.minior, miniorMap); + + Map necrozmaMap = new HashMap<>(); + necrozmaMap.put(1,"-DM"); + necrozmaMap.put(2,"-DW"); + necrozmaMap.put(3,"-U"); + map.put(Species.necrozma, necrozmaMap); + + return map; + } + + private static Map setupDummyFormeSuffixes() { + Map m = new HashMap<>(); + m.put(0,""); + return m; + } + + private static List actuallyCosmeticFormsSM = Arrays.asList( + Species.SMFormes.cherrimCosmetic1, + Species.SMFormes.shellosCosmetic1, + Species.SMFormes.gastrodonCosmetic1, + Species.SMFormes.keldeoCosmetic1, + Species.SMFormes.furfrouCosmetic1, Species.SMFormes.furfrouCosmetic2, + Species.SMFormes.furfrouCosmetic3, Species.SMFormes.furfrouCosmetic4, + Species.SMFormes.furfrouCosmetic5, Species.SMFormes.furfrouCosmetic6, + Species.SMFormes.furfrouCosmetic7, Species.SMFormes.furfrouCosmetic8, + Species.SMFormes.furfrouCosmetic9, + Species.SMFormes.pumpkabooCosmetic1, Species.SMFormes.pumpkabooCosmetic2, + Species.SMFormes.pumpkabooCosmetic3, + Species.SMFormes.gourgeistCosmetic1, Species.SMFormes.gourgeistCosmetic2, + Species.SMFormes.gourgeistCosmetic3, + Species.SMFormes.floetteCosmetic1, Species.SMFormes.floetteCosmetic2, + Species.SMFormes.floetteCosmetic3, Species.SMFormes.floetteCosmetic4, + Species.SMFormes.raticateACosmetic1, + Species.SMFormes.mimikyuCosmetic1, Species.SMFormes.mimikyuCosmetic2, Species.SMFormes.mimikyuCosmetic3, + Species.SMFormes.gumshoosCosmetic1, + Species.SMFormes.vikavoltCosmetic1, + Species.SMFormes.lurantisCosmetic1, + Species.SMFormes.salazzleCosmetic1, + Species.SMFormes.kommoOCosmetic1, + Species.SMFormes.greninjaCosmetic1, + Species.SMFormes.zygarde10Cosmetic1, Species.SMFormes.zygardeCosmetic1, + Species.SMFormes.miniorCosmetic1, Species.SMFormes.miniorCosmetic2, Species.SMFormes.miniorCosmetic3, + Species.SMFormes.miniorCosmetic4, Species.SMFormes.miniorCosmetic5, Species.SMFormes.miniorCosmetic6, + Species.SMFormes.miniorCCosmetic1, Species.SMFormes.miniorCCosmetic2, Species.SMFormes.miniorCCosmetic3, + Species.SMFormes.miniorCCosmetic4, Species.SMFormes.miniorCCosmetic5, Species.SMFormes.miniorCCosmetic6, + Species.SMFormes.magearnaCosmetic1, + Species.SMFormes.pikachuCosmetic1, Species.SMFormes.pikachuCosmetic2, Species.SMFormes.pikachuCosmetic3, + Species.SMFormes.pikachuCosmetic4, Species.SMFormes.pikachuCosmetic5, Species.SMFormes.pikachuCosmetic6 // Pikachu With Funny Hats + ); + + private static List actuallyCosmeticFormsUSUM = Arrays.asList( + Species.USUMFormes.cherrimCosmetic1, + Species.USUMFormes.shellosCosmetic1, + Species.USUMFormes.gastrodonCosmetic1, + Species.USUMFormes.keldeoCosmetic1, + Species.USUMFormes.furfrouCosmetic1, Species.USUMFormes.furfrouCosmetic2, + Species.USUMFormes.furfrouCosmetic3, Species.USUMFormes.furfrouCosmetic4, + Species.USUMFormes.furfrouCosmetic5, Species.USUMFormes.furfrouCosmetic6, + Species.USUMFormes.furfrouCosmetic7, Species.USUMFormes.furfrouCosmetic8, + Species.USUMFormes.furfrouCosmetic9, + Species.USUMFormes.pumpkabooCosmetic1, Species.USUMFormes.pumpkabooCosmetic2, + Species.USUMFormes.pumpkabooCosmetic3, + Species.USUMFormes.gourgeistCosmetic1, Species.USUMFormes.gourgeistCosmetic2, + Species.USUMFormes.gourgeistCosmetic3, + Species.USUMFormes.floetteCosmetic1, Species.USUMFormes.floetteCosmetic2, + Species.USUMFormes.floetteCosmetic3, Species.USUMFormes.floetteCosmetic4, + Species.USUMFormes.raticateACosmetic1, + Species.USUMFormes.marowakACosmetic1, + Species.USUMFormes.mimikyuCosmetic1, Species.USUMFormes.mimikyuCosmetic2, Species.USUMFormes.mimikyuCosmetic3, + Species.USUMFormes.gumshoosCosmetic1, + Species.USUMFormes.vikavoltCosmetic1, + Species.USUMFormes.lurantisCosmetic1, + Species.USUMFormes.salazzleCosmetic1, + Species.USUMFormes.kommoOCosmetic1, + Species.USUMFormes.araquanidCosmetic1, + Species.USUMFormes.togedemaruCosmetic1, + Species.USUMFormes.ribombeeCosmetic1, + Species.USUMFormes.greninjaCosmetic1, + Species.USUMFormes.zygarde10Cosmetic1, Species.USUMFormes.zygardeCosmetic1, + Species.USUMFormes.miniorCosmetic1, Species.USUMFormes.miniorCosmetic2, Species.USUMFormes.miniorCosmetic3, + Species.USUMFormes.miniorCosmetic4, Species.USUMFormes.miniorCosmetic5, Species.USUMFormes.miniorCosmetic6, + Species.USUMFormes.miniorCCosmetic1, Species.USUMFormes.miniorCCosmetic2, Species.USUMFormes.miniorCCosmetic3, + Species.USUMFormes.miniorCCosmetic4, Species.USUMFormes.miniorCCosmetic5, Species.USUMFormes.miniorCCosmetic6, + Species.USUMFormes.magearnaCosmetic1, + Species.USUMFormes.pikachuCosmetic1, Species.USUMFormes.pikachuCosmetic2, Species.USUMFormes.pikachuCosmetic3, + Species.USUMFormes.pikachuCosmetic4, Species.USUMFormes.pikachuCosmetic5, Species.USUMFormes.pikachuCosmetic6, + Species.USUMFormes.pikachuCosmetic7, // Pikachu With Funny Hats + Species.USUMFormes.rockruffCosmetic1 + ); + + public static List getActuallyCosmeticForms(int romType) { + if (romType == Type_SM) { + return actuallyCosmeticFormsSM; + } else { + return actuallyCosmeticFormsUSUM; + } + } + + private static List ignoreFormsSM = Arrays.asList( + Species.SMFormes.cherrimCosmetic1, + Species.SMFormes.greninjaCosmetic1, + Species.SMFormes.zygarde10Cosmetic1, + Species.SMFormes.zygardeCosmetic1, + Species.SMFormes.miniorCosmetic1, + Species.SMFormes.miniorCosmetic2, + Species.SMFormes.miniorCosmetic3, + Species.SMFormes.miniorCosmetic4, + Species.SMFormes.miniorCosmetic5, + Species.SMFormes.miniorCosmetic6, + Species.SMFormes.mimikyuCosmetic1, + Species.SMFormes.mimikyuCosmetic3 + ); + + private static List ignoreFormsUSUM = Arrays.asList( + Species.USUMFormes.cherrimCosmetic1, + Species.USUMFormes.greninjaCosmetic1, + Species.USUMFormes.zygarde10Cosmetic1, + Species.USUMFormes.zygardeCosmetic1, + Species.USUMFormes.miniorCosmetic1, + Species.USUMFormes.miniorCosmetic2, + Species.USUMFormes.miniorCosmetic3, + Species.USUMFormes.miniorCosmetic4, + Species.USUMFormes.miniorCosmetic5, + Species.USUMFormes.miniorCosmetic6, + Species.USUMFormes.mimikyuCosmetic1, + Species.USUMFormes.mimikyuCosmetic3, + Species.USUMFormes.rockruffCosmetic1 + ); + + public static List getIgnoreForms(int romType) { + if (romType == Type_SM) { + return ignoreFormsSM; + } else { + return ignoreFormsUSUM; + } + } + + private static Map altFormesWithCosmeticFormsSM = setupAltFormesWithCosmeticForms(Type_SM); + private static Map altFormesWithCosmeticFormsUSUM = setupAltFormesWithCosmeticForms(Type_USUM); + + public static Map getAltFormesWithCosmeticForms(int romType) { + if (romType == Type_SM) { + return altFormesWithCosmeticFormsSM; + } else { + return altFormesWithCosmeticFormsUSUM; + } + } + + private static Map setupAltFormesWithCosmeticForms(int romType) { + Map map = new HashMap<>(); + if (romType == Type_SM) { + map.put(Species.SMFormes.raticateA,1); // 1 form (Totem) + map.put(Species.SMFormes.zygarde10,1); // 1 form (Power Construct) + map.put(Species.SMFormes.miniorC,6); // 6 forms (colors) + } else { + map.put(Species.USUMFormes.raticateA,1); // 1 form (Totem) + map.put(Species.USUMFormes.marowakA,1); // 1 form (Totem) + map.put(Species.USUMFormes.zygarde10,1); // 1 form (Power Construct) + map.put(Species.USUMFormes.miniorC,6); // 6 forms (colors) + } + + return map; + } + + private static Type[] constructTypeTable() { + Type[] table = new Type[256]; + table[0x00] = Type.NORMAL; + table[0x01] = Type.FIGHTING; + table[0x02] = Type.FLYING; + table[0x03] = Type.POISON; + table[0x04] = Type.GROUND; + table[0x05] = Type.ROCK; + table[0x06] = Type.BUG; + table[0x07] = Type.GHOST; + table[0x08] = Type.STEEL; + table[0x09] = Type.FIRE; + table[0x0A] = Type.WATER; + table[0x0B] = Type.GRASS; + table[0x0C] = Type.ELECTRIC; + table[0x0D] = Type.PSYCHIC; + table[0x0E] = Type.ICE; + table[0x0F] = Type.DRAGON; + table[0x10] = Type.DARK; + table[0x11] = Type.FAIRY; + return table; + } + + public static byte typeToByte(Type type) { + if (type == null) { + return 0x00; // normal? + } + switch (type) { + case NORMAL: + return 0x00; + case FIGHTING: + return 0x01; + case FLYING: + return 0x02; + case POISON: + return 0x03; + case GROUND: + return 0x04; + case ROCK: + return 0x05; + case BUG: + return 0x06; + case GHOST: + return 0x07; + case FIRE: + return 0x09; + case WATER: + return 0x0A; + case GRASS: + return 0x0B; + case ELECTRIC: + return 0x0C; + case PSYCHIC: + return 0x0D; + case ICE: + return 0x0E; + case DRAGON: + return 0x0F; + case STEEL: + return 0x08; + case DARK: + return 0x10; + case FAIRY: + return 0x11; + default: + return 0; // normal by default + } + } + + private static List setupRelevantEncounterFiles(int romType) { + int fileCount = romType == Type_SM ? 2761 : 3696; + List list = new ArrayList<>(); + + for (int i = 0; i < fileCount; i++) { + if (((i - 9) % 11 == 0) || (i % 11 == 0)) { + list.add(true); + } else { + list.add(false); + } + } + + return list; + } + + + public static Map> getHardcodedTradeTextOffsets(int romType) { + Map> hardcodedTradeTextOffsets = new HashMap<>(); + if (romType == Gen7Constants.Type_USUM) { + // For some reason, the Route 2 trade is hardcoded in USUM but not in SM + hardcodedTradeTextOffsets.put(0, Arrays.asList(20, 21, 22)); + } + hardcodedTradeTextOffsets.put(1, Arrays.asList(26, 28, 30)); + hardcodedTradeTextOffsets.put(2, Arrays.asList(32, 33, 34, 36)); + hardcodedTradeTextOffsets.put(3, Arrays.asList(38, 39, 40, 42)); + hardcodedTradeTextOffsets.put(4, Arrays.asList(44, 45, 46, 48)); + hardcodedTradeTextOffsets.put(5, Arrays.asList(50, 51, 52, 54)); + hardcodedTradeTextOffsets.put(6, Arrays.asList(56, 57, 58, 60)); + return hardcodedTradeTextOffsets; + } + + public static ItemList allowedItemsSM, allowedItemsUSUM, nonBadItems; + public static List regularShopItemsSM, regularShopItemsUSUM, opShopItems; + + static { + setupAllowedItems(); + } + + private static void setupAllowedItems() { + allowedItemsSM = new ItemList(Items.fairyMemory); + // Key items + version exclusives + allowedItemsSM.banRange(Items.explorerKit, 76); + allowedItemsSM.banRange(Items.dataCard01, 32); + allowedItemsSM.banRange(Items.xtransceiverMale, 18); + allowedItemsSM.banSingles(Items.expShare, Items.libertyPass, Items.propCase, Items.dragonSkull, + Items.lightStone, Items.darkStone); + // Unknown blank items or version exclusives + allowedItemsSM.banRange(Items.tea, 3); + allowedItemsSM.banRange(Items.unused120, 14); + // TMs & HMs - tms cant be held in gen7 + allowedItemsSM.tmRange(Items.tm01, 92); + allowedItemsSM.tmRange(Items.tm93, 3); + allowedItemsSM.banRange(Items.tm01, 100); + allowedItemsSM.banRange(Items.tm93, 3); + // Battle Launcher exclusives + allowedItemsSM.banRange(Items.direHit2, 24); + + // Key items (Gen 6) + allowedItemsSM.banRange(Items.holoCasterMale,3); + allowedItemsSM.banSingles(Items.pokeFlute, Items.sprinklotad); + allowedItemsSM.banRange(Items.powerPlantPass,4); + allowedItemsSM.banRange(Items.elevatorKey,4); + allowedItemsSM.banRange(Items.lensCase,3); + allowedItemsSM.banRange(Items.lookerTicket,3); + allowedItemsSM.banRange(Items.megaCharm,2); + + // TMs (Gen 6) + allowedItemsSM.tmRange(Items.tm96,5); + allowedItemsSM.banRange(Items.tm96,5); + + // Key items and an HM + allowedItemsSM.banRange(Items.machBike,34); + allowedItemsSM.banRange(Items.prisonBottle,2); + allowedItemsSM.banRange(Items.meteoriteThirdForm,5); + + // Z-Crystals + allowedItemsSM.banRange(Items.normaliumZHeld,19); + allowedItemsSM.banRange(Items.decidiumZHeld,39); + + // Key Items (Gen 7) + allowedItemsSM.banSingles(Items.zRing, Items.sparklingStone, Items.zygardeCube, Items.ridePager, + Items.sunFlute, Items.moonFlute, Items.enigmaticCard); + allowedItemsSM.banRange(Items.forageBag,3); + + // Unused + allowedItemsSM.banSingles(Items.unused848, Items.unused859); + allowedItemsSM.banRange(Items.unused837,4); + allowedItemsSM.banRange(Items.silverRazzBerry,18); + allowedItemsSM.banRange(Items.stretchySpring,19); + + allowedItemsUSUM = allowedItemsSM.copy(Items.rotoCatch); + + // Z-Crystals + allowedItemsUSUM.banRange(Items.solganiumZBag,12); + + // Key Items + allowedItemsUSUM.banRange(Items.zPowerRing,16); + + // ROTO LOTO + allowedItemsUSUM.banRange(Items.rotoHatch,11); + + // non-bad items + // ban specific pokemon hold items, berries, apricorns, mail + nonBadItems = allowedItemsSM.copy(); + + nonBadItems.banSingles(Items.oddKeystone, Items.griseousOrb, Items.soulDew, Items.lightBall, + Items.oranBerry, Items.quickPowder, Items.passOrb, Items.discountCoupon, Items.strangeSouvenir, + Items.festivalTicket); + nonBadItems.banRange(Items.growthMulch, 4); // mulch + nonBadItems.banRange(Items.adamantOrb, 2); // orbs + nonBadItems.banRange(Items.mail1, 12); // mails + nonBadItems.banRange(Items.figyBerry, 25); // berries without useful battle effects + nonBadItems.banRange(Items.luckyPunch, 4); // pokemon specific + nonBadItems.banRange(Items.redScarf, 5); // contest scarves + nonBadItems.banRange(Items.richMulch,4); // more mulch + nonBadItems.banRange(Items.gengarite, 30); // Mega Stones, part 1 + nonBadItems.banRange(Items.swampertite, 13); // Mega Stones, part 2 + nonBadItems.banRange(Items.cameruptite, 4); // Mega Stones, part 3 + nonBadItems.banRange(Items.fightingMemory,17); // Memories + nonBadItems.banRange(Items.relicCopper,7); // relic items + nonBadItems.banSingles(Items.shoalSalt, Items.shoalShell); // Shoal items; have no purpose and sell for $10. + nonBadItems.banRange(Items.blueFlute, 5); // Flutes; have no purpose and sell for $10. + + regularShopItemsSM = new ArrayList<>(); + + regularShopItemsSM.addAll(IntStream.rangeClosed(Items.ultraBall, Items.pokeBall).boxed().collect(Collectors.toList())); + regularShopItemsSM.addAll(IntStream.rangeClosed(Items.potion, Items.revive).boxed().collect(Collectors.toList())); + regularShopItemsSM.addAll(IntStream.rangeClosed(Items.superRepel, Items.repel).boxed().collect(Collectors.toList())); + regularShopItemsSM.add(Items.honey); + regularShopItemsSM.add(Items.adrenalineOrb); + + regularShopItemsUSUM = new ArrayList<>(regularShopItemsSM); + regularShopItemsUSUM.add(Items.pokeToy); + + opShopItems = new ArrayList<>(); + + // "Money items" etc + opShopItems.add(Items.lavaCookie); + opShopItems.add(Items.berryJuice); + opShopItems.add(Items.rareCandy); + opShopItems.add(Items.oldGateau); + opShopItems.addAll(IntStream.rangeClosed(Items.tinyMushroom, Items.nugget).boxed().collect(Collectors.toList())); + opShopItems.add(Items.rareBone); + opShopItems.addAll(IntStream.rangeClosed(Items.lansatBerry, Items.rowapBerry).boxed().collect(Collectors.toList())); + opShopItems.add(Items.luckyEgg); + opShopItems.add(Items.prettyFeather); + opShopItems.addAll(IntStream.rangeClosed(Items.balmMushroom, Items.casteliacone).boxed().collect(Collectors.toList())); + } + + public static ItemList getAllowedItems(int romType) { + if (romType == Type_SM) { + return allowedItemsSM; + } else { + return allowedItemsUSUM; + } + } + + private static final List requiredFieldTMsSM = Arrays.asList( + 80, 49, 5, 83, 64, 62, 100, 31, 46, 88, 57, 41, 59, 73, 53, 61, 28, 39, 55, 86, 30, 93, 81, 84, 74, 85, 72, + 3, 3, 13, 36, 91, 79, 24, 97, 50, 99, 35, 2, 26, 6, 6 + ); + + private static final List requiredFieldTMsUSUM = Arrays.asList( + 49, 5, 83, 64, 23, 100, 79, 24, 31, 46, 88, 41, 59, 32, 53, 61, 28, 39, 57, 86, 30, 62, 81, 80, 74, 85, 73, + 72, 3, 3, 84, 13, 36, 91, 55, 97, 50, 93, 93, 99, 35, 2, 26, 6, 6 + ); + + public static List getRequiredFieldTMs(int romType) { + if (romType == Type_SM) { + return requiredFieldTMsSM.stream().distinct().collect(Collectors.toList()); + } else { + return requiredFieldTMsUSUM.stream().distinct().collect(Collectors.toList()); + } + } + + public static void tagTrainersSM(List trs) { + + tag(trs,"ELITE1", 23, 152, 349); // Hala + tag(trs,"ELITE2",90, 153, 351); // Olivia + tag(trs,"ELITE3", 154, 403); // Nanu + tag(trs,"ELITE4", 155, 359); // Hapu + tag(trs,"ELITE5", 149, 350); // Acerola + tag(trs,"ELITE6", 156, 352); // Kahili + + tag(trs,"RIVAL2-0", 129); + tag(trs,"RIVAL2-1", 413); + tag(trs,"RIVAL2-2", 414); + tagRival(trs,"RIVAL3",477); + + tagRival(trs,"FRIEND1", 6); + tagRival(trs,"FRIEND2", 9); + tagRival(trs,"FRIEND3", 12); + tagRival(trs,"FRIEND4", 76); + tagRival(trs,"FRIEND5", 82); + tagRival(trs,"FRIEND6", 438); + tagRival(trs,"FRIEND7", 217); + tagRival(trs,"FRIEND8", 220); + tagRival(trs,"FRIEND9", 447); + tagRival(trs,"FRIEND10", 450); + tagRival(trs,"FRIEND11", 482); + tagRival(trs,"FRIEND12", 356); + + tag(trs,"THEMED:GLADION-STRONG", 79, 185, 239, 240, 415, 416, 417, 418, 419, 441); + tag(trs,"THEMED:ILIMA-STRONG", 52, 215, 216, 396); + tag(trs,"THEMED:LANA-STRONG", 144); + tag(trs,"THEMED:KIAWE-STRONG", 398); + tag(trs,"THEMED:MALLOW-STRONG", 146); + tag(trs,"THEMED:SOPHOCLES-STRONG", 405); + tag(trs,"THEMED:MOLAYNE-STRONG", 167, 481); + tag(trs,"THEMED:MINA-STRONG", 435, 467); + tag(trs,"THEMED:PLUMERIA-STRONG", 89, 238, 401); + tag(trs,"THEMED:SINA-STRONG", 75); + tag(trs,"THEMED:DEXIO-STRONG", 74, 412); + tag(trs,"THEMED:FABA-STRONG",132, 241, 360, 410); + tag(trs,"THEMED:GUZMA-LEADER", 138, 235, 236, 400); + tag(trs,"THEMED:LUSAMINE-LEADER", 131, 158); + } + + public static void tagTrainersUSUM(List trs) { + + tag(trs,"ELITE1", 23, 650); // Hala + tag(trs,"ELITE2", 90, 153, 351); // Olivia + tag(trs,"ELITE3", 154, 508); // Nanu + tag(trs,"ELITE4", 359, 497); // Hapu + tag(trs,"ELITE5", 489, 490); // Big Mo + tag(trs,"ELITE6", 149, 350); // Acerola + tag(trs,"ELITE7", 156, 352); // Kahili + + tagRival(trs,"RIVAL2", 477); // Kukui + + // Hau + tagRival(trs,"FRIEND1", 491); + tagRival(trs,"FRIEND2", 9); + tagRival(trs,"FRIEND3", 12); + tagRival(trs,"FRIEND4", 76); + tagRival(trs,"FRIEND5", 82); + tagRival(trs,"FRIEND6", 438); + tagRival(trs,"FRIEND7", 217); + tagRival(trs,"FRIEND8", 220); + tagRival(trs,"FRIEND9", 447); + tagRival(trs,"FRIEND10", 450); + tagRival(trs,"FRIEND11", 494); + tagRival(trs,"FRIEND12", 356); + + tag(trs,"THEMED:GLADION-STRONG", 79, 185, 239, 240, 415, 416, 417, 418, 419, 441); + tag(trs,"THEMED:ILIMA-STRONG", 52, 215, 216, 396, 502); + tag(trs,"THEMED:LANA-STRONG", 144, 503); + tag(trs,"THEMED:KIAWE-STRONG", 398, 504); + tag(trs,"THEMED:MALLOW-STRONG", 146, 505); + tag(trs,"THEMED:SOPHOCLES-STRONG", 405, 506); + tag(trs,"THEMED:MINA-STRONG", 507); + tag(trs,"THEMED:PLUMERIA-STRONG", 89, 238, 401); + tag(trs,"THEMED:SINA-STRONG", 75); + tag(trs,"THEMED:DEXIO-STRONG", 74, 412, 623); + tag(trs,"THEMED:FABA-STRONG", 132, 241, 410, 561); + tag(trs,"THEMED:SOLIERA-STRONG", 498, 499, 648, 651); + tag(trs,"THEMED:DULSE-STRONG", 500, 501, 649, 652); + tag(trs,"THEMED:GUZMA-LEADER", 138, 235, 236, 558, 647); + tag(trs,"THEMED:LUSAMINE-LEADER", 131, 644); + + tag(trs,"UBER", 541, 542, 543, 580, 572, 573, 559, 560, 562, 645); // RR Episode + } + + private static void tagRival(List allTrainers, String tag, int offset) { + allTrainers.get(offset - 1).tag = tag + "-0"; + allTrainers.get(offset).tag = tag + "-1"; + allTrainers.get(offset + 1).tag = tag + "-2"; + + } + + private static void tag(List allTrainers, int number, String tag) { + if (allTrainers.size() > (number - 1)) { + allTrainers.get(number - 1).tag = tag; + } + } + + private static void tag(List allTrainers, String tag, int... numbers) { + for (int num : numbers) { + if (allTrainers.size() > (num - 1)) { + allTrainers.get(num - 1).tag = tag; + } + } + } + + public static void setMultiBattleStatusSM(List trs) { + // All Double Battles in Gen 7 are internally treated as a Multi Battle + // 92 + 93: Rising Star Duo Justin and Lauren + // 97 + 98: Twins Isa and Nico + // 134 + 136: Aether Foundation Employees in Secret Lab B w/ Hau + // 141 + 227: Team Skull Grunts on Route 17 + // 241 + 442: Faba and Aether Foundation Employee w/ Hau + // 262 + 265: Ace Duo Aimee and Kent + // 270 + 299: Swimmers Jake and Yumi + // 278 + 280: Honeymooners Noriko and Devin + // 303 + 307: Veteran Duo Tsunekazu and Nobuko + // 315 + 316: Team Skull Grunts in Po Town + // 331 + 332: Karate Family Guy and Samuel + // 371 + 372: Twins Harper and Sarah + // 373 + 374: Swimmer Girls Ashlyn and Kylie + // 375 + 376: Golf Buddies Tara and Tina + // 421 + 422: Athletic Siblings Alyssa and Sho + // 425 + 426: Punk Pair Lane and Yoko + // 429 + 430: Punk Pair Troy and Marie + // 443 + 444: Team Skull Grunts in Diglett's Tunnel w/ Hau + // 453 + 454: Aether Foundation Employees w/ Hau + // 455 + 456: Aether Foundation Employees w/ Gladion + setMultiBattleStatus(trs, 92, 93, 97, 98, 134, 136, 141, 227, 241, 262, 265, 270, 278, 280, 299, 303, + 307, 315, 316, 331, 332, 371, 372, 373, 374, 375, 376, 421, 422, 425, 426, 429, 430, 442, 443, 444, 453, + 454, 455, 456 + ); + } + + public static void setMultiBattleStatusUSUM(List trs) { + // All Double Battles in Gen 7 are internally treated as a Multi Battle + // 92 + 93: Rising Star Duo Justin and Lauren + // 97 + 98: Twins Isa and Nico + // 134 + 136: Aether Foundation Employees in Secret Lab B w/ Hau + // 141 + 227: Team Skull Grunts on Route 17 + // 178 + 511: Capoeira Couple Cara and Douglas + // 241 + 442: Faba and Aether Foundation Employee w/ Hau + // 262 + 265: Ace Duo Aimee and Kent + // 270 + 299: Swimmers Jake and Yumi + // 278 + 280: Honeymooners Noriko and Devin + // 303 + 307: Veteran Duo Tsunekazu and Nobuko + // 315 + 316: Team Skull Grunts in Po Town + // 331 + 332: Karate Family Guy and Samuel + // 371 + 372: Twins Harper and Sarah + // 373 + 374: Swimmer Girls Ashlyn and Kylie + // 375 + 376: Golf Buddies Tara and Tina + // 421 + 422: Athletic Siblings Alyssa and Sho + // 425 + 426: Punk Pair Lane and Yoko + // 429 + 430: Punk Pair Troy and Marie + // 443 + 444: Team Skull Grunts in Diglett's Tunnel w/ Hau + // 453 + 454: Aether Foundation Employees w/ Hau + // 455 + 456: Aether Foundation Employees w/ Gladion + // 514 + 521: Tourist Couple Yuriko and Landon + // 515 + 534: Tourist Couple Steve and Reika + // 529 + 530: Dancing Family Jen and Fumiko + // 554 + 561: Aether Foundation Employee and Faba w/ Lillie + // 557 + 578: GAME FREAK Iwao and Morimoto + // 586 + 595: Team Rainbow Rocket Grunts w/ Guzma + // 613 + 626: Master & Apprentice Kaimana and Breon + // 617 + 618: Sparring Partners Allon and Eimar + // 619 + 620: Sparring Partners Craig and Jason + setMultiBattleStatus(trs, 92, 93, 97, 98, 134, 136, 141, 178, 227, 241, 262, 265, 270, 278, 280, 299, + 303, 307, 315, 316, 331, 332, 371, 372, 373, 374, 375, 376, 421, 422, 425, 426, 429, 430, 442, 443, 444, + 453, 454, 455, 456, 511, 514, 515, 521, 529, 530, 534, 544, 557, 561, 578, 586, 595, 613, 617, 618, 619, + 620, 626 + ); + } + + private static void setMultiBattleStatus(List allTrainers, int... numbers) { + for (int num : numbers) { + if (allTrainers.size() > (num - 1)) { + allTrainers.get(num - 1).multiBattleStatus = Trainer.MultiBattleStatus.ALWAYS; + } + } + } + + public static void setForcedRivalStarterPositionsUSUM(List allTrainers) { + + // Hau 3 + allTrainers.get(12 - 1).forceStarterPosition = 0; + allTrainers.get(13 - 1).forceStarterPosition = 0; + allTrainers.get(14 - 1).forceStarterPosition = 0; + + // Hau 6 + allTrainers.get(217 - 1).forceStarterPosition = 0; + allTrainers.get(218 - 1).forceStarterPosition = 0; + allTrainers.get(219 - 1).forceStarterPosition = 0; + } + + public static final Map balancedItemPrices = Stream.of(new Integer[][] { + // Skip item index 0. All prices divided by 10 + {Items.masterBall, 300}, + {Items.ultraBall, 80}, + {Items.greatBall, 60}, + {Items.pokeBall, 20}, + {Items.safariBall, 50}, + {Items.netBall, 100}, + {Items.diveBall, 100}, + {Items.nestBall, 100}, + {Items.repeatBall, 100}, + {Items.timerBall, 100}, + {Items.luxuryBall, 100}, + {Items.premierBall, 20}, + {Items.duskBall, 100}, + {Items.healBall, 30}, + {Items.quickBall, 100}, + {Items.cherishBall, 20}, + {Items.potion, 20}, + {Items.antidote, 20}, + {Items.burnHeal, 30}, + {Items.iceHeal, 10}, + {Items.awakening, 10}, + {Items.paralyzeHeal, 30}, + {Items.fullRestore, 300}, + {Items.maxPotion, 250}, + {Items.hyperPotion, 150}, + {Items.superPotion, 70}, + {Items.fullHeal, 40}, + {Items.revive, 200}, + {Items.maxRevive, 400}, + {Items.freshWater, 20}, + {Items.sodaPop, 30}, + {Items.lemonade, 40}, + {Items.moomooMilk, 60}, + {Items.energyPowder, 50}, + {Items.energyRoot, 120}, + {Items.healPowder, 30}, + {Items.revivalHerb, 280}, + {Items.ether, 300}, + {Items.maxEther, 450}, + {Items.elixir, 1500}, + {Items.maxElixir, 1800}, + {Items.lavaCookie, 35}, + {Items.berryJuice, 20}, + {Items.sacredAsh, 500}, + {Items.hpUp, 1000}, + {Items.protein, 1000}, + {Items.iron, 1000}, + {Items.carbos, 1000}, + {Items.calcium, 1000}, + {Items.rareCandy, 1000}, + {Items.ppUp, 1000}, + {Items.zinc, 1000}, + {Items.ppMax, 2500}, + {Items.oldGateau, 35}, + {Items.guardSpec, 150}, + {Items.direHit, 100}, + {Items.xAttack, 100}, + {Items.xDefense, 200}, + {Items.xSpeed, 100}, + {Items.xAccuracy, 100}, + {Items.xSpAtk, 100}, + {Items.xSpDef, 200}, + {Items.pokeDoll, 10}, + {Items.fluffyTail, 10}, + {Items.blueFlute, 2}, + {Items.yellowFlute, 2}, + {Items.redFlute, 2}, + {Items.blackFlute, 2}, + {Items.whiteFlute, 2}, + {Items.shoalSalt, 2}, + {Items.shoalShell, 2}, + {Items.redShard, 100}, + {Items.blueShard, 100}, + {Items.yellowShard, 100}, + {Items.greenShard, 100}, + {Items.superRepel, 70}, + {Items.maxRepel, 90}, + {Items.escapeRope, 100}, + {Items.repel, 40}, + {Items.sunStone, 300}, + {Items.moonStone, 300}, + {Items.fireStone, 300}, + {Items.thunderStone, 300}, + {Items.waterStone, 300}, + {Items.leafStone, 300}, + {Items.tinyMushroom, 50}, + {Items.bigMushroom, 500}, + {Items.pearl, 200}, + {Items.bigPearl, 800}, + {Items.stardust, 300}, + {Items.starPiece, 1200}, + {Items.nugget, 1000}, + {Items.heartScale, 500}, + {Items.honey, 30}, + {Items.growthMulch, 20}, + {Items.dampMulch, 20}, + {Items.stableMulch, 20}, + {Items.gooeyMulch, 20}, + {Items.rootFossil, 700}, + {Items.clawFossil, 700}, + {Items.helixFossil, 700}, + {Items.domeFossil, 700}, + {Items.oldAmber, 1000}, + {Items.armorFossil, 700}, + {Items.skullFossil, 700}, + {Items.rareBone, 500}, + {Items.shinyStone, 300}, + {Items.duskStone, 300}, + {Items.dawnStone, 300}, + {Items.ovalStone, 200}, + {Items.oddKeystone, 210}, + {Items.griseousOrb, 1000}, + {Items.tea, 0}, // unused in Gen 7 + {Items.unused114, 0}, + {Items.autograph, 0}, + {Items.douseDrive, 100}, + {Items.shockDrive, 100}, + {Items.burnDrive, 100}, + {Items.chillDrive, 100}, + {Items.unused120, 0}, + {Items.pokemonBox, 0}, // unused in Gen 7 + {Items.medicinePocket, 0}, // unused in Gen 7 + {Items.tmCase, 0}, // unused in Gen 7 + {Items.candyJar, 0}, // unused in Gen 7 + {Items.powerUpPocket, 0}, // unused in Gen 7 + {Items.clothingTrunk, 0}, // unused in Gen 7 + {Items.catchingPocket, 0}, // unused in Gen 7 + {Items.battlePocket, 0}, // unused in Gen 7 + {Items.unused129, 0}, + {Items.unused130, 0}, + {Items.unused131, 0}, + {Items.unused132, 0}, + {Items.unused133, 0}, + {Items.sweetHeart, 15}, + {Items.adamantOrb, 1000}, + {Items.lustrousOrb, 1000}, + {Items.mail1, 5}, + {Items.mail2, 5}, + {Items.mail3, 5}, + {Items.mail4, 5}, + {Items.mail5, 5}, + {Items.mail6, 5}, + {Items.mail7, 5}, + {Items.mail8, 5}, + {Items.mail9, 5}, + {Items.mail10, 5}, + {Items.mail11, 5}, + {Items.mail12, 5}, + {Items.cheriBerry, 20}, + {Items.chestoBerry, 25}, + {Items.pechaBerry, 10}, + {Items.rawstBerry, 25}, + {Items.aspearBerry, 25}, + {Items.leppaBerry, 300}, + {Items.oranBerry, 5}, + {Items.persimBerry, 20}, + {Items.lumBerry, 50}, + {Items.sitrusBerry, 50}, + {Items.figyBerry, 10}, + {Items.wikiBerry, 10}, + {Items.magoBerry, 10}, + {Items.aguavBerry, 10}, + {Items.iapapaBerry, 10}, + {Items.razzBerry, 50}, + {Items.blukBerry, 50}, + {Items.nanabBerry, 50}, + {Items.wepearBerry, 50}, + {Items.pinapBerry, 50}, + {Items.pomegBerry, 50}, + {Items.kelpsyBerry, 50}, + {Items.qualotBerry, 50}, + {Items.hondewBerry, 50}, + {Items.grepaBerry, 50}, + {Items.tamatoBerry, 50}, + {Items.cornnBerry, 50}, + {Items.magostBerry, 50}, + {Items.rabutaBerry, 50}, + {Items.nomelBerry, 50}, + {Items.spelonBerry, 50}, + {Items.pamtreBerry, 50}, + {Items.watmelBerry, 50}, + {Items.durinBerry, 50}, + {Items.belueBerry, 50}, + {Items.occaBerry, 100}, + {Items.passhoBerry, 100}, + {Items.wacanBerry, 100}, + {Items.rindoBerry, 100}, + {Items.yacheBerry, 100}, + {Items.chopleBerry, 100}, + {Items.kebiaBerry, 100}, + {Items.shucaBerry, 100}, + {Items.cobaBerry, 100}, + {Items.payapaBerry, 100}, + {Items.tangaBerry, 100}, + {Items.chartiBerry, 100}, + {Items.kasibBerry, 100}, + {Items.habanBerry, 100}, + {Items.colburBerry, 100}, + {Items.babiriBerry, 100}, + {Items.chilanBerry, 100}, + {Items.liechiBerry, 100}, + {Items.ganlonBerry, 100}, + {Items.salacBerry, 100}, + {Items.petayaBerry, 100}, + {Items.apicotBerry, 100}, + {Items.lansatBerry, 100}, + {Items.starfBerry, 100}, + {Items.enigmaBerry, 100}, + {Items.micleBerry, 100}, + {Items.custapBerry, 100}, + {Items.jabocaBerry, 100}, + {Items.rowapBerry, 100}, + {Items.brightPowder, 400}, + {Items.whiteHerb, 400}, + {Items.machoBrace, 300}, + {Items.expShare, 0}, + {Items.quickClaw, 450}, + {Items.sootheBell, 100}, + {Items.mentalHerb, 100}, + {Items.choiceBand, 1000}, + {Items.kingsRock, 500}, + {Items.silverPowder, 200}, + {Items.amuletCoin, 1500}, + {Items.cleanseTag, 100}, + {Items.soulDew, 20}, + {Items.deepSeaTooth, 300}, + {Items.deepSeaScale, 300}, + {Items.smokeBall, 400}, + {Items.everstone, 300}, + {Items.focusBand, 300}, + {Items.luckyEgg, 1000}, + {Items.scopeLens, 500}, + {Items.metalCoat, 300}, + {Items.leftovers, 1000}, + {Items.dragonScale, 300}, + {Items.lightBall, 100}, + {Items.softSand, 200}, + {Items.hardStone, 200}, + {Items.miracleSeed, 200}, + {Items.blackGlasses, 200}, + {Items.blackBelt, 200}, + {Items.magnet, 200}, + {Items.mysticWater, 200}, + {Items.sharpBeak, 200}, + {Items.poisonBarb, 200}, + {Items.neverMeltIce, 200}, + {Items.spellTag, 200}, + {Items.twistedSpoon, 200}, + {Items.charcoal, 200}, + {Items.dragonFang, 200}, + {Items.silkScarf, 200}, + {Items.upgrade, 300}, + {Items.shellBell, 600}, + {Items.seaIncense, 200}, + {Items.laxIncense, 300}, + {Items.luckyPunch, 100}, + {Items.metalPowder, 100}, + {Items.thickClub, 100}, + {Items.leek, 100}, + {Items.redScarf, 10}, + {Items.blueScarf, 10}, + {Items.pinkScarf, 10}, + {Items.greenScarf, 10}, + {Items.yellowScarf, 10}, + {Items.wideLens, 150}, + {Items.muscleBand, 200}, + {Items.wiseGlasses, 200}, + {Items.expertBelt, 600}, + {Items.lightClay, 150}, + {Items.lifeOrb, 1000}, + {Items.powerHerb, 100}, + {Items.toxicOrb, 150}, + {Items.flameOrb, 150}, + {Items.quickPowder, 100}, + {Items.focusSash, 200}, + {Items.zoomLens, 150}, + {Items.metronome, 300}, + {Items.ironBall, 100}, + {Items.laggingTail, 100}, + {Items.destinyKnot, 150}, + {Items.blackSludge, 500}, + {Items.icyRock, 20}, + {Items.smoothRock, 20}, + {Items.heatRock, 20}, + {Items.dampRock, 20}, + {Items.gripClaw, 150}, + {Items.choiceScarf, 1000}, + {Items.stickyBarb, 150}, + {Items.powerBracer, 300}, + {Items.powerBelt, 300}, + {Items.powerLens, 300}, + {Items.powerBand, 300}, + {Items.powerAnklet, 300}, + {Items.powerWeight, 300}, + {Items.shedShell, 50}, + {Items.bigRoot, 150}, + {Items.choiceSpecs, 1000}, + {Items.flamePlate, 200}, + {Items.splashPlate, 200}, + {Items.zapPlate, 200}, + {Items.meadowPlate, 200}, + {Items.iciclePlate, 200}, + {Items.fistPlate, 200}, + {Items.toxicPlate, 200}, + {Items.earthPlate, 200}, + {Items.skyPlate, 200}, + {Items.mindPlate, 200}, + {Items.insectPlate, 200}, + {Items.stonePlate, 200}, + {Items.spookyPlate, 200}, + {Items.dracoPlate, 200}, + {Items.dreadPlate, 200}, + {Items.ironPlate, 200}, + {Items.oddIncense, 200}, + {Items.rockIncense, 200}, + {Items.fullIncense, 100}, + {Items.waveIncense, 200}, + {Items.roseIncense, 200}, + {Items.luckIncense, 1500}, + {Items.pureIncense, 100}, + {Items.protector, 300}, + {Items.electirizer, 300}, + {Items.magmarizer, 300}, + {Items.dubiousDisc, 300}, + {Items.reaperCloth, 300}, + {Items.razorClaw, 500}, + {Items.razorFang, 500}, + {Items.tm01, 1000}, + {Items.tm02, 1000}, + {Items.tm03, 1000}, + {Items.tm04, 1000}, + {Items.tm05, 1000}, + {Items.tm06, 1000}, + {Items.tm07, 2000}, + {Items.tm08, 1000}, + {Items.tm09, 1000}, + {Items.tm10, 1000}, + {Items.tm11, 2000}, + {Items.tm12, 1000}, + {Items.tm13, 1000}, + {Items.tm14, 2000}, + {Items.tm15, 2000}, + {Items.tm16, 1000}, + {Items.tm17, 1000}, + {Items.tm18, 2000}, + {Items.tm19, 1000}, + {Items.tm20, 1000}, + {Items.tm21, 1000}, + {Items.tm22, 1000}, + {Items.tm23, 1000}, + {Items.tm24, 1000}, + {Items.tm25, 2000}, + {Items.tm26, 1000}, + {Items.tm27, 1000}, + {Items.tm28, 2000}, + {Items.tm29, 1000}, + {Items.tm30, 1000}, + {Items.tm31, 1000}, + {Items.tm32, 1000}, + {Items.tm33, 1000}, + {Items.tm34, 1000}, + {Items.tm35, 1000}, + {Items.tm36, 1000}, + {Items.tm37, 2000}, + {Items.tm38, 2000}, + {Items.tm39, 1000}, + {Items.tm40, 1000}, + {Items.tm41, 1000}, + {Items.tm42, 1000}, + {Items.tm43, 1000}, + {Items.tm44, 1000}, + {Items.tm45, 1000}, + {Items.tm46, 1000}, + {Items.tm47, 1000}, + {Items.tm48, 1000}, + {Items.tm49, 1000}, + {Items.tm50, 2000}, + {Items.tm51, 1000}, + {Items.tm52, 2000}, + {Items.tm53, 1000}, + {Items.tm54, 1000}, + {Items.tm55, 1000}, + {Items.tm56, 1000}, + {Items.tm57, 1000}, + {Items.tm58, 1000}, + {Items.tm59, 2000}, + {Items.tm60, 1000}, + {Items.tm61, 1000}, + {Items.tm62, 1000}, + {Items.tm63, 1000}, + {Items.tm64, 1000}, + {Items.tm65, 1000}, + {Items.tm66, 1000}, + {Items.tm67, 1000}, + {Items.tm68, 2000}, + {Items.tm69, 1000}, + {Items.tm70, 2000}, + {Items.tm71, 2000}, + {Items.tm72, 1000}, + {Items.tm73, 500}, + {Items.tm74, 1000}, + {Items.tm75, 1000}, + {Items.tm76, 1000}, + {Items.tm77, 1000}, + {Items.tm78, 1000}, + {Items.tm79, 1000}, + {Items.tm80, 1000}, + {Items.tm81, 1000}, + {Items.tm82, 1000}, + {Items.tm83, 1000}, + {Items.tm84, 1000}, + {Items.tm85, 1000}, + {Items.tm86, 1000}, + {Items.tm87, 1000}, + {Items.tm88, 1000}, + {Items.tm89, 1000}, + {Items.tm90, 1000}, + {Items.tm91, 1000}, + {Items.tm92, 1000}, + {Items.hm01, 0}, + {Items.hm02, 0}, + {Items.hm03, 0}, + {Items.hm04, 0}, + {Items.hm05, 0}, + {Items.hm06, 0}, + {Items.hm07, 0}, // unused in Gen 7 + {Items.hm08, 0}, // unused in Gen 7 + {Items.explorerKit, 0}, + {Items.lootSack, 0}, + {Items.ruleBook, 0}, + {Items.pokeRadar, 0}, + {Items.pointCard, 0}, + {Items.journal, 0}, + {Items.sealCase, 0}, + {Items.fashionCase, 0}, + {Items.sealBag, 0}, + {Items.palPad, 0}, + {Items.worksKey, 0}, + {Items.oldCharm, 0}, + {Items.galacticKey, 0}, + {Items.redChain, 0}, + {Items.townMap, 0}, + {Items.vsSeeker, 0}, + {Items.coinCase, 0}, + {Items.oldRod, 0}, + {Items.goodRod, 0}, + {Items.superRod, 0}, + {Items.sprayduck, 0}, + {Items.poffinCase, 0}, + {Items.bike, 0}, + {Items.suiteKey, 0}, + {Items.oaksLetter, 0}, + {Items.lunarWing, 0}, + {Items.memberCard, 0}, + {Items.azureFlute, 0}, + {Items.ssTicketJohto, 0}, + {Items.contestPass, 0}, + {Items.magmaStone, 0}, + {Items.parcelSinnoh, 0}, + {Items.coupon1, 0}, + {Items.coupon2, 0}, + {Items.coupon3, 0}, + {Items.storageKeySinnoh, 0}, + {Items.secretPotion, 0}, + {Items.vsRecorder, 0}, + {Items.gracidea, 0}, + {Items.secretKeySinnoh, 0}, + {Items.apricornBox, 0}, + {Items.unownReport, 0}, + {Items.berryPots, 0}, + {Items.dowsingMachine, 0}, + {Items.blueCard, 0}, + {Items.slowpokeTail, 0}, + {Items.clearBell, 0}, + {Items.cardKeyJohto, 0}, + {Items.basementKeyJohto, 0}, + {Items.squirtBottle, 0}, + {Items.redScale, 0}, + {Items.lostItem, 0}, + {Items.pass, 0}, + {Items.machinePart, 0}, + {Items.silverWing, 0}, + {Items.rainbowWing, 0}, + {Items.mysteryEgg, 0}, + {Items.redApricorn, 2}, + {Items.blueApricorn, 2}, + {Items.yellowApricorn, 2}, + {Items.greenApricorn, 2}, + {Items.pinkApricorn, 2}, + {Items.whiteApricorn, 2}, + {Items.blackApricorn, 2}, + {Items.fastBall, 30}, + {Items.levelBall, 30}, + {Items.lureBall, 30}, + {Items.heavyBall, 30}, + {Items.loveBall, 30}, + {Items.friendBall, 30}, + {Items.moonBall, 30}, + {Items.sportBall, 30}, + {Items.parkBall, 0}, + {Items.photoAlbum, 0}, + {Items.gbSounds, 0}, + {Items.tidalBell, 0}, + {Items.rageCandyBar, 35}, + {Items.dataCard01, 0}, + {Items.dataCard02, 0}, + {Items.dataCard03, 0}, + {Items.dataCard04, 0}, + {Items.dataCard05, 0}, + {Items.dataCard06, 0}, + {Items.dataCard07, 0}, + {Items.dataCard08, 0}, + {Items.dataCard09, 0}, + {Items.dataCard10, 0}, + {Items.dataCard11, 0}, + {Items.dataCard12, 0}, + {Items.dataCard13, 0}, + {Items.dataCard14, 0}, + {Items.dataCard15, 0}, + {Items.dataCard16, 0}, + {Items.dataCard17, 0}, + {Items.dataCard18, 0}, + {Items.dataCard19, 0}, + {Items.dataCard20, 0}, + {Items.dataCard21, 0}, + {Items.dataCard22, 0}, + {Items.dataCard23, 0}, + {Items.dataCard24, 0}, + {Items.dataCard25, 0}, + {Items.dataCard26, 0}, + {Items.dataCard27, 0}, + {Items.jadeOrb, 0}, + {Items.lockCapsule, 0}, + {Items.redOrb, 0}, + {Items.blueOrb, 0}, + {Items.enigmaStone, 0}, + {Items.prismScale, 300}, + {Items.eviolite, 1000}, + {Items.floatStone, 100}, + {Items.rockyHelmet, 600}, + {Items.airBalloon, 100}, + {Items.redCard, 100}, + {Items.ringTarget, 100}, + {Items.bindingBand, 200}, + {Items.absorbBulb, 100}, + {Items.cellBattery, 100}, + {Items.ejectButton, 100}, + {Items.fireGem, 100}, + {Items.waterGem, 100}, + {Items.electricGem, 100}, + {Items.grassGem, 100}, + {Items.iceGem, 100}, + {Items.fightingGem, 100}, + {Items.poisonGem, 100}, + {Items.groundGem, 100}, + {Items.flyingGem, 100}, + {Items.psychicGem, 100}, + {Items.bugGem, 100}, + {Items.rockGem, 100}, + {Items.ghostGem, 100}, + {Items.dragonGem, 100}, + {Items.darkGem, 100}, + {Items.steelGem, 100}, + {Items.normalGem, 100}, + {Items.healthFeather, 30}, + {Items.muscleFeather, 30}, + {Items.resistFeather, 30}, + {Items.geniusFeather, 30}, + {Items.cleverFeather, 30}, + {Items.swiftFeather, 30}, + {Items.prettyFeather, 100}, + {Items.coverFossil, 700}, + {Items.plumeFossil, 700}, + {Items.libertyPass, 0}, + {Items.passOrb, 20}, + {Items.dreamBall, 100}, + {Items.pokeToy, 10}, + {Items.propCase, 0}, + {Items.dragonSkull, 0}, + {Items.balmMushroom, 1500}, + {Items.bigNugget, 4000}, + {Items.pearlString, 3000}, + {Items.cometShard, 6000}, + {Items.relicCopper, 0}, + {Items.relicSilver, 0}, + {Items.relicGold, 0}, + {Items.relicVase, 0}, + {Items.relicBand, 0}, + {Items.relicStatue, 0}, + {Items.relicCrown, 0}, + {Items.casteliacone, 35}, + {Items.direHit2, 0}, + {Items.xSpeed2, 0}, + {Items.xSpAtk2, 0}, + {Items.xSpDef2, 0}, + {Items.xDefense2, 0}, + {Items.xAttack2, 0}, + {Items.xAccuracy2, 0}, + {Items.xSpeed3, 0}, + {Items.xSpAtk3, 0}, + {Items.xSpDef3, 0}, + {Items.xDefense3, 0}, + {Items.xAttack3, 0}, + {Items.xAccuracy3, 0}, + {Items.xSpeed6, 0}, + {Items.xSpAtk6, 0}, + {Items.xSpDef6, 0}, + {Items.xDefense6, 0}, + {Items.xAttack6, 0}, + {Items.xAccuracy6, 0}, + {Items.abilityUrge, 0}, + {Items.itemDrop, 0}, + {Items.itemUrge, 0}, + {Items.resetUrge, 0}, + {Items.direHit3, 0}, + {Items.lightStone, 0}, + {Items.darkStone, 0}, + {Items.tm93, 2000}, + {Items.tm94, 2000}, + {Items.tm95, 1000}, + {Items.xtransceiverMale, 0}, + {Items.unused622, 0}, + {Items.gram1, 0}, + {Items.gram2, 0}, + {Items.gram3, 0}, + {Items.xtransceiverFemale, 0}, + {Items.medalBox, 0}, + {Items.dNASplicersFuse, 0}, + {Items.dNASplicersSeparate, 0}, + {Items.permit, 0}, + {Items.ovalCharm, 0}, + {Items.shinyCharm, 0}, + {Items.plasmaCard, 0}, + {Items.grubbyHanky, 0}, + {Items.colressMachine, 0}, + {Items.droppedItemCurtis, 0}, + {Items.droppedItemYancy, 0}, + {Items.revealGlass, 0}, + {Items.weaknessPolicy, 200}, + {Items.assaultVest, 600}, + {Items.holoCasterMale, 0}, + {Items.profsLetter, 0}, + {Items.rollerSkates, 0}, + {Items.pixiePlate, 200}, + {Items.abilityCapsule, 500}, + {Items.whippedDream, 300}, + {Items.sachet, 300}, + {Items.luminousMoss, 20}, + {Items.snowball, 20}, + {Items.safetyGoggles, 300}, + {Items.pokeFlute, 0}, + {Items.richMulch, 20}, + {Items.surpriseMulch, 20}, + {Items.boostMulch, 20}, + {Items.amazeMulch, 20}, + {Items.gengarite, 1000}, + {Items.gardevoirite, 1000}, + {Items.ampharosite, 1000}, + {Items.venusaurite, 1000}, + {Items.charizarditeX, 1000}, + {Items.blastoisinite, 1000}, + {Items.mewtwoniteX, 2000}, + {Items.mewtwoniteY, 2000}, + {Items.blazikenite, 1000}, + {Items.medichamite, 500}, + {Items.houndoominite, 1000}, + {Items.aggronite, 1000}, + {Items.banettite, 500}, + {Items.tyranitarite, 2000}, + {Items.scizorite, 1000}, + {Items.pinsirite, 1000}, + {Items.aerodactylite, 1000}, + {Items.lucarionite, 1000}, + {Items.abomasite, 500}, + {Items.kangaskhanite, 500}, + {Items.gyaradosite, 1000}, + {Items.absolite, 500}, + {Items.charizarditeY, 1000}, + {Items.alakazite, 1000}, + {Items.heracronite, 1000}, + {Items.mawilite, 300}, + {Items.manectite, 500}, + {Items.garchompite, 2000}, + {Items.latiasite, 2000}, + {Items.latiosite, 2000}, + {Items.roseliBerry, 100}, + {Items.keeBerry, 100}, + {Items.marangaBerry, 100}, + {Items.sprinklotad, 0}, + {Items.tm96, 1000}, + {Items.tm97, 1000}, + {Items.tm98, 2000}, + {Items.tm99, 1000}, + {Items.tm100, 500}, + {Items.powerPlantPass, 0}, + {Items.megaRing, 0}, + {Items.intriguingStone, 0}, + {Items.commonStone, 0}, + {Items.discountCoupon, 2}, + {Items.elevatorKey, 0}, + {Items.tmvPass, 0}, + {Items.honorofKalos, 0}, + {Items.adventureGuide, 0}, + {Items.strangeSouvenir, 300}, + {Items.lensCase, 0}, + {Items.makeupBag, 0}, + {Items.travelTrunk, 0}, + {Items.lumioseGalette, 35}, + {Items.shalourSable, 35}, + {Items.jawFossil, 700}, + {Items.sailFossil, 700}, + {Items.lookerTicket, 0}, + {Items.bikeYellow, 0}, + {Items.holoCasterFemale, 0}, + {Items.fairyGem, 100}, + {Items.megaCharm, 0}, + {Items.megaGlove, 0}, + {Items.machBike, 0}, + {Items.acroBike, 0}, + {Items.wailmerPail, 0}, + {Items.devonParts, 0}, + {Items.sootSack, 0}, + {Items.basementKeyHoenn, 0}, + {Items.pokeblockKit, 0}, + {Items.letter, 0}, + {Items.eonTicket, 0}, + {Items.scanner, 0}, + {Items.goGoggles, 0}, + {Items.meteoriteFirstForm, 0}, + {Items.keytoRoom1, 0}, + {Items.keytoRoom2, 0}, + {Items.keytoRoom4, 0}, + {Items.keytoRoom6, 0}, + {Items.storageKeyHoenn, 0}, + {Items.devonScope, 0}, + {Items.ssTicketHoenn, 0}, + {Items.hm07ORAS, 0}, + {Items.devonScubaGear, 0}, + {Items.contestCostumeMale, 0}, + {Items.contestCostumeFemale, 0}, + {Items.magmaSuit, 0}, + {Items.aquaSuit, 0}, + {Items.pairOfTickets, 0}, + {Items.megaBracelet, 0}, + {Items.megaPendant, 0}, + {Items.megaGlasses, 0}, + {Items.megaAnchor, 0}, + {Items.megaStickpin, 0}, + {Items.megaTiara, 0}, + {Items.megaAnklet, 0}, + {Items.meteoriteSecondForm, 0}, + {Items.swampertite, 1000}, + {Items.sceptilite, 1000}, + {Items.sablenite, 300}, + {Items.altarianite, 500}, + {Items.galladite, 1000}, + {Items.audinite, 500}, + {Items.metagrossite, 2000}, + {Items.sharpedonite, 500}, + {Items.slowbronite, 500}, + {Items.steelixite, 1000}, + {Items.pidgeotite, 500}, + {Items.glalitite, 500}, + {Items.diancite, 2000}, + {Items.prisonBottle, 0}, + {Items.megaCuff, 0}, + {Items.cameruptite, 500}, + {Items.lopunnite, 500}, + {Items.salamencite, 2000}, + {Items.beedrillite, 300}, + {Items.meteoriteThirdForm, 0}, + {Items.meteoriteFinalForm, 0}, + {Items.keyStone, 0}, + {Items.meteoriteShard, 0}, + {Items.eonFlute, 0}, + {Items.normaliumZHeld, 0}, + {Items.firiumZHeld, 0}, + {Items.wateriumZHeld, 0}, + {Items.electriumZHeld, 0}, + {Items.grassiumZHeld, 0}, + {Items.iciumZHeld, 0}, + {Items.fightiniumZHeld, 0}, + {Items.poisoniumZHeld, 0}, + {Items.groundiumZHeld, 0}, + {Items.flyiniumZHeld, 0}, + {Items.psychiumZHeld, 0}, + {Items.buginiumZHeld, 0}, + {Items.rockiumZHeld, 0}, + {Items.ghostiumZHeld, 0}, + {Items.dragoniumZHeld, 0}, + {Items.darkiniumZHeld, 0}, + {Items.steeliumZHeld, 0}, + {Items.fairiumZHeld, 0}, + {Items.pikaniumZHeld, 0}, + {Items.bottleCap, 500}, + {Items.goldBottleCap, 1000}, + {Items.zRing, 0}, + {Items.decidiumZHeld, 0}, + {Items.inciniumZHeld, 0}, + {Items.primariumZHeld, 0}, + {Items.tapuniumZHeld, 0}, + {Items.marshadiumZHeld, 0}, + {Items.aloraichiumZHeld, 0}, + {Items.snorliumZHeld, 0}, + {Items.eeviumZHeld, 0}, + {Items.mewniumZHeld, 0}, + {Items.normaliumZBag, 0}, + {Items.firiumZBag, 0}, + {Items.wateriumZBag, 0}, + {Items.electriumZBag, 0}, + {Items.grassiumZBag, 0}, + {Items.iciumZBag, 0}, + {Items.fightiniumZBag, 0}, + {Items.poisoniumZBag, 0}, + {Items.groundiumZBag, 0}, + {Items.flyiniumZBag, 0}, + {Items.psychiumZBag, 0}, + {Items.buginiumZBag, 0}, + {Items.rockiumZBag, 0}, + {Items.ghostiumZBag, 0}, + {Items.dragoniumZBag, 0}, + {Items.darkiniumZBag, 0}, + {Items.steeliumZBag, 0}, + {Items.fairiumZBag, 0}, + {Items.pikaniumZBag, 0}, + {Items.decidiumZBag, 0}, + {Items.inciniumZBag, 0}, + {Items.primariumZBag, 0}, + {Items.tapuniumZBag, 0}, + {Items.marshadiumZBag, 0}, + {Items.aloraichiumZBag, 0}, + {Items.snorliumZBag, 0}, + {Items.eeviumZBag, 0}, + {Items.mewniumZBag, 0}, + {Items.pikashuniumZHeld, 0}, + {Items.pikashuniumZBag, 0}, + {Items.unused837, 0}, + {Items.unused838, 0}, + {Items.unused839, 0}, + {Items.unused840, 0}, + {Items.forageBag, 0}, + {Items.fishingRod, 0}, + {Items.professorsMask, 0}, + {Items.festivalTicket, 1}, + {Items.sparklingStone, 0}, + {Items.adrenalineOrb, 30}, + {Items.zygardeCube, 0}, + {Items.unused848, 0}, + {Items.iceStone, 300}, + {Items.ridePager, 0}, + {Items.beastBall, 30}, + {Items.bigMalasada, 35}, + {Items.redNectar, 30}, + {Items.yellowNectar, 30}, + {Items.pinkNectar, 30}, + {Items.purpleNectar, 30}, + {Items.sunFlute, 0}, + {Items.moonFlute, 0}, + {Items.unused859, 0}, + {Items.enigmaticCard, 0}, + {Items.silverRazzBerry, 0}, // unused in Gen 7 + {Items.goldenRazzBerry, 0}, // unused in Gen 7 + {Items.silverNanabBerry, 0}, // unused in Gen 7 + {Items.goldenNanabBerry, 0}, // unused in Gen 7 + {Items.silverPinapBerry, 0}, // unused in Gen 7 + {Items.goldenPinapBerry, 0}, // unused in Gen 7 + {Items.unused867, 0}, + {Items.unused868, 0}, + {Items.unused869, 0}, + {Items.unused870, 0}, + {Items.unused871, 0}, + {Items.secretKeyKanto, 0}, // unused in Gen 7 + {Items.ssTicketKanto, 0}, // unused in Gen 7 + {Items.silphScope, 0}, // unused in Gen 7 + {Items.parcelKanto, 0}, // unused in Gen 7 + {Items.cardKeyKanto, 0}, // unused in Gen 7 + {Items.goldTeeth, 0}, // unused in Gen 7 + {Items.liftKey, 0}, // unused in Gen 7 + {Items.terrainExtender, 400}, + {Items.protectivePads, 300}, + {Items.electricSeed, 100}, + {Items.psychicSeed, 100}, + {Items.mistySeed, 100}, + {Items.grassySeed, 100}, + {Items.stretchySpring, 0}, // unused in Gen 7 + {Items.chalkyStone, 0}, // unused in Gen 7 + {Items.marble, 0}, // unused in Gen 7 + {Items.loneEarring, 0}, // unused in Gen 7 + {Items.beachGlass, 0}, // unused in Gen 7 + {Items.goldLeaf, 0}, // unused in Gen 7 + {Items.silverLeaf, 0}, // unused in Gen 7 + {Items.polishedMudBall, 0}, // unused in Gen 7 + {Items.tropicalShell, 0}, // unused in Gen 7 + {Items.leafLetterPikachu, 0}, // unused in Gen 7 + {Items.leafLetterEevee, 0}, // unused in Gen 7 + {Items.smallBouquet, 0}, // unused in Gen 7 + {Items.unused897, 0}, + {Items.unused898, 0}, + {Items.unused899, 0}, + {Items.lure, 0}, // unused in Gen 7 + {Items.superLure, 0}, // unused in Gen 7 + {Items.maxLure, 0}, // unused in Gen 7 + {Items.pewterCrunchies, 0}, // unused in Gen 7 + {Items.fightingMemory, 100}, + {Items.flyingMemory, 100}, + {Items.poisonMemory, 100}, + {Items.groundMemory, 100}, + {Items.rockMemory, 100}, + {Items.bugMemory, 100}, + {Items.ghostMemory, 100}, + {Items.steelMemory, 100}, + {Items.fireMemory, 100}, + {Items.waterMemory, 100}, + {Items.grassMemory, 100}, + {Items.electricMemory, 100}, + {Items.psychicMemory, 100}, + {Items.iceMemory, 100}, + {Items.dragonMemory, 100}, + {Items.darkMemory, 100}, + {Items.fairyMemory, 100}, + {Items.solganiumZBag, 0}, + {Items.lunaliumZBag, 0}, + {Items.ultranecroziumZBag, 0}, + {Items.mimikiumZHeld, 0}, + {Items.lycaniumZHeld, 0}, + {Items.kommoniumZHeld, 0}, + {Items.solganiumZHeld, 0}, + {Items.lunaliumZHeld, 0}, + {Items.ultranecroziumZHeld, 0}, + {Items.mimikiumZBag, 0}, + {Items.lycaniumZBag, 0}, + {Items.kommoniumZBag, 0}, + {Items.zPowerRing, 0}, + {Items.pinkPetal, 0}, + {Items.orangePetal, 0}, + {Items.bluePetal, 0}, + {Items.redPetal, 0}, + {Items.greenPetal, 0}, + {Items.yellowPetal, 0}, + {Items.purplePetal, 0}, + {Items.rainbowFlower, 0}, + {Items.surgeBadge, 0}, + {Items.nSolarizerFuse, 0}, + {Items.nLunarizerFuse, 0}, + {Items.nSolarizerSeparate, 0}, + {Items.nLunarizerSeparate, 0}, + {Items.ilimaNormaliumZ, 0}, + {Items.leftPokeBall, 0}, + {Items.rotoHatch, 0}, + {Items.rotoBargain, 0}, + {Items.rotoPrizeMoney, 0}, + {Items.rotoExpPoints, 0}, + {Items.rotoFriendship, 0}, + {Items.rotoEncounter, 0}, + {Items.rotoStealth, 0}, + {Items.rotoHPRestore, 0}, + {Items.rotoPPRestore, 0}, + {Items.rotoBoost, 0}, + {Items.rotoCatch, 0}, + }).collect(Collectors.toMap(kv -> kv[0], kv -> kv[1])); +} diff --git a/src/com/pkrandom/constants/GlobalConstants.java b/src/com/pkrandom/constants/GlobalConstants.java new file mode 100644 index 0000000..c4d1352 --- /dev/null +++ b/src/com/pkrandom/constants/GlobalConstants.java @@ -0,0 +1,252 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- GlobalConstants.java - constants that are relevant for multiple games --*/ +/*-- in the Pokemon series --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.pokemon.*; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class GlobalConstants { + + public static final boolean[] bannedRandomMoves = new boolean[827], bannedForDamagingMove = new boolean[827]; + static { + bannedRandomMoves[Moves.struggle] = true; // self explanatory + + bannedForDamagingMove[Moves.selfDestruct] = true; + bannedForDamagingMove[Moves.dreamEater] = true; + bannedForDamagingMove[Moves.explosion] = true; + bannedForDamagingMove[Moves.snore] = true; + bannedForDamagingMove[Moves.falseSwipe] = true; + bannedForDamagingMove[Moves.futureSight] = true; + bannedForDamagingMove[Moves.fakeOut] = true; + bannedForDamagingMove[Moves.focusPunch] = true; + bannedForDamagingMove[Moves.doomDesire] = true; + bannedForDamagingMove[Moves.feint] = true; + bannedForDamagingMove[Moves.lastResort] = true; + bannedForDamagingMove[Moves.suckerPunch] = true; + bannedForDamagingMove[Moves.constrict] = true; // overly weak + bannedForDamagingMove[Moves.rage] = true; // lock-in in gen1 + bannedForDamagingMove[Moves.rollout] = true; // lock-in + bannedForDamagingMove[Moves.iceBall] = true; // Rollout clone + bannedForDamagingMove[Moves.synchronoise] = true; // hard to use + bannedForDamagingMove[Moves.shellTrap] = true; // hard to use + bannedForDamagingMove[Moves.foulPlay] = true; // doesn't depend on your own attacking stat + bannedForDamagingMove[Moves.spitUp] = true; // hard to use + + // make sure these cant roll + bannedForDamagingMove[Moves.sonicBoom] = true; + bannedForDamagingMove[Moves.dragonRage] = true; + bannedForDamagingMove[Moves.hornDrill] = true; + bannedForDamagingMove[Moves.guillotine] = true; + bannedForDamagingMove[Moves.fissure] = true; + bannedForDamagingMove[Moves.sheerCold] = true; + + } + + /* @formatter:off */ + public static final List normalMultihitMoves = Arrays.asList( + Moves.armThrust, Moves.barrage, Moves.boneRush, Moves.bulletSeed, Moves.cometPunch, Moves.doubleSlap, + Moves.furyAttack, Moves.furySwipes, Moves.icicleSpear, Moves.pinMissile, Moves.rockBlast, Moves.spikeCannon, + Moves.tailSlap, Moves.waterShuriken); + + public static final List doubleHitMoves = Arrays.asList( + Moves.bonemerang, Moves.doubleHit, Moves.doubleIronBash, Moves.doubleKick, Moves.dragonDarts, + Moves.dualChop, Moves.gearGrind, Moves.twineedle); + + public static final List varyingPowerZMoves = Arrays.asList( + Moves.breakneckBlitzPhysical, Moves.breakneckBlitzSpecial, + Moves.allOutPummelingPhysical, Moves.allOutPummelingSpecial, + Moves.supersonicSkystrikePhysical, Moves.supersonicSkystrikeSpecial, + Moves.acidDownpourPhysical, Moves.acidDownpourSpecial, + Moves.tectonicRagePhysical, Moves.tectonicRageSpecial, + Moves.continentalCrushPhysical, Moves.continentalCrushSpecial, + Moves.savageSpinOutPhysical, Moves.savageSpinOutSpecial, + Moves.neverEndingNightmarePhysical, Moves.neverEndingNightmareSpecial, + Moves.corkscrewCrashPhysical, Moves.corkscrewCrashSpecial, + Moves.infernoOverdrivePhysical, Moves.infernoOverdriveSpecial, + Moves.hydroVortexPhysical, Moves.hydroVortexSpecial, + Moves.bloomDoomPhysical, Moves.bloomDoomSpecial, + Moves.gigavoltHavocPhysical, Moves.gigavoltHavocSpecial, + Moves.shatteredPsychePhysical, Moves.shatteredPsycheSpecial, + Moves.subzeroSlammerPhysical, Moves.subzeroSlammerSpecial, + Moves.devastatingDrakePhysical, Moves.devastatingDrakeSpecial, + Moves.blackHoleEclipsePhysical, Moves.blackHoleEclipseSpecial, + Moves.twinkleTacklePhysical, Moves.twinkleTackleSpecial); + + public static final List fixedPowerZMoves = Arrays.asList( + Moves.catastropika, Moves.sinisterArrowRaid, Moves.maliciousMoonsault, Moves.oceanicOperetta, + Moves.guardianOfAlola, Moves.soulStealing7StarStrike, Moves.stokedSparksurfer, Moves.pulverizingPancake, + Moves.extremeEvoboost, Moves.genesisSupernova, Moves.tenMillionVoltThunderbolt, Moves.lightThatBurnsTheSky, + Moves.searingSunrazeSmash, Moves.menacingMoonrazeMaelstrom, Moves.letsSnuggleForever, + Moves.splinteredStormshards, Moves.clangorousSoulblaze); + + public static final List zMoves = Stream.concat(fixedPowerZMoves.stream(), + varyingPowerZMoves.stream()).collect(Collectors.toList()); + + public static Map getStatChanges(int generation) { + Map map = new TreeMap<>(); + + switch(generation) { + case 6: + map.put(Species.butterfree,new StatChange(Stat.SPATK.val,90)); + map.put(Species.beedrill,new StatChange(Stat.ATK.val,90)); + map.put(Species.pidgeot,new StatChange(Stat.SPEED.val,101)); + map.put(Species.pikachu,new StatChange(Stat.DEF.val | Stat.SPDEF.val,40, 50)); + map.put(Species.raichu,new StatChange(Stat.SPEED.val,110)); + map.put(Species.nidoqueen,new StatChange(Stat.ATK.val,92)); + map.put(Species.nidoking,new StatChange(Stat.ATK.val,102)); + map.put(Species.clefable,new StatChange(Stat.SPATK.val,95)); + map.put(Species.wigglytuff,new StatChange(Stat.SPATK.val,85)); + map.put(Species.vileplume,new StatChange(Stat.SPATK.val,110)); + map.put(Species.poliwrath,new StatChange(Stat.ATK.val,95)); + map.put(Species.alakazam,new StatChange(Stat.SPDEF.val,95)); + map.put(Species.victreebel,new StatChange(Stat.SPDEF.val,70)); + map.put(Species.golem,new StatChange(Stat.ATK.val,120)); + map.put(Species.ampharos,new StatChange(Stat.DEF.val,85)); + map.put(Species.bellossom,new StatChange(Stat.DEF.val,95)); + map.put(Species.azumarill,new StatChange(Stat.SPATK.val,60)); + map.put(Species.jumpluff,new StatChange(Stat.SPDEF.val,95)); + map.put(Species.beautifly,new StatChange(Stat.SPATK.val,100)); + map.put(Species.exploud,new StatChange(Stat.SPDEF.val,73)); + map.put(Species.staraptor,new StatChange(Stat.SPDEF.val,60)); + map.put(Species.roserade,new StatChange(Stat.DEF.val,65)); + map.put(Species.stoutland,new StatChange(Stat.ATK.val,110)); + map.put(Species.unfezant,new StatChange(Stat.ATK.val,115)); + map.put(Species.gigalith,new StatChange(Stat.SPDEF.val,80)); + map.put(Species.seismitoad,new StatChange(Stat.ATK.val,95)); + map.put(Species.leavanny,new StatChange(Stat.SPDEF.val,80)); + map.put(Species.scolipede,new StatChange(Stat.ATK.val,100)); + map.put(Species.krookodile,new StatChange(Stat.DEF.val,80)); + break; + case 7: + map.put(Species.arbok,new StatChange(Stat.ATK.val,95)); + map.put(Species.dugtrio,new StatChange(Stat.ATK.val,100)); + map.put(Species.farfetchd,new StatChange(Stat.ATK.val,90)); + map.put(Species.dodrio,new StatChange(Stat.SPEED.val,110)); + map.put(Species.electrode,new StatChange(Stat.SPEED.val,150)); + map.put(Species.exeggutor,new StatChange(Stat.SPDEF.val,75)); + map.put(Species.noctowl,new StatChange(Stat.SPATK.val,86)); + map.put(Species.ariados,new StatChange(Stat.SPDEF.val,70)); + map.put(Species.qwilfish,new StatChange(Stat.DEF.val,85)); + map.put(Species.magcargo,new StatChange(Stat.HP.val | Stat.SPATK.val,60,90)); + map.put(Species.corsola,new StatChange(Stat.HP.val | Stat.DEF.val | Stat.SPDEF.val,65,95,95)); + map.put(Species.mantine,new StatChange(Stat.HP.val,85)); + map.put(Species.swellow,new StatChange(Stat.SPATK.val,75)); + map.put(Species.pelipper,new StatChange(Stat.SPATK.val,95)); + map.put(Species.masquerain,new StatChange(Stat.SPATK.val | Stat.SPEED.val,100,80)); + map.put(Species.delcatty,new StatChange(Stat.SPEED.val,90)); + map.put(Species.volbeat,new StatChange(Stat.DEF.val | Stat.SPDEF.val,75,85)); + map.put(Species.illumise,new StatChange(Stat.DEF.val | Stat.SPDEF.val,75,85)); + map.put(Species.lunatone,new StatChange(Stat.HP.val,90)); + map.put(Species.solrock,new StatChange(Stat.HP.val,90)); + map.put(Species.chimecho,new StatChange(Stat.HP.val | Stat.DEF.val | Stat.SPDEF.val,75,80,90)); + map.put(Species.woobat,new StatChange(Stat.HP.val,65)); + map.put(Species.crustle,new StatChange(Stat.ATK.val,105)); + map.put(Species.beartic,new StatChange(Stat.ATK.val,130)); + map.put(Species.cryogonal,new StatChange(Stat.HP.val | Stat.DEF.val,80,50)); + break; + case 8: + map.put(Species.aegislash,new StatChange(Stat.DEF.val | Stat.SPDEF.val,140,140)); + break; + case 9: + map.put(Species.cresselia,new StatChange(Stat.DEF.val | Stat.SPDEF.val, 110,120)); + map.put(Species.zacian,new StatChange(Stat.ATK.val, 120)); + map.put(Species.zamazenta,new StatChange(Stat.ATK.val, 120)); + break; + } + return map; + } + + /* @formatter:on */ + + public static final List xItems = Arrays.asList(Items.guardSpec, Items.direHit, Items.xAttack, + Items.xDefense, Items.xSpeed, Items.xAccuracy, Items.xSpAtk, Items.xSpDef); + + public static final List battleTrappingAbilities = Arrays.asList(Abilities.shadowTag, Abilities.magnetPull, + Abilities.arenaTrap); + + public static final List negativeAbilities = Arrays.asList( + Abilities.defeatist, Abilities.slowStart, Abilities.truant, Abilities.klutz, Abilities.stall + ); + + public static final List badAbilities = Arrays.asList( + Abilities.minus, Abilities.plus, Abilities.anticipation, Abilities.forewarn, Abilities.frisk, + Abilities.honeyGather, Abilities.auraBreak, Abilities.receiver, Abilities.powerOfAlchemy + ); + + public static final List doubleBattleAbilities = Arrays.asList( + Abilities.friendGuard, Abilities.healer, Abilities.telepathy, Abilities.symbiosis, + Abilities.battery + ); + + public static final List duplicateAbilities = Arrays.asList( + Abilities.vitalSpirit, Abilities.whiteSmoke, Abilities.purePower, Abilities.shellArmor, Abilities.airLock, + Abilities.solidRock, Abilities.ironBarbs, Abilities.turboblaze, Abilities.teravolt, Abilities.emergencyExit, + Abilities.dazzling, Abilities.tanglingHair, Abilities.powerOfAlchemy, Abilities.fullMetalBody, + Abilities.shadowShield, Abilities.prismArmor, Abilities.libero, Abilities.stalwart + ); + + public static final List noPowerNonStatusMoves = Arrays.asList( + Moves.guillotine, Moves.hornDrill, Moves.sonicBoom, Moves.lowKick, Moves.counter, Moves.seismicToss, + Moves.dragonRage, Moves.fissure, Moves.nightShade, Moves.bide, Moves.psywave, Moves.superFang, + Moves.flail, Moves.revenge, Moves.returnTheMoveNotTheKeyword, Moves.present, Moves.frustration, + Moves.magnitude, Moves.mirrorCoat, Moves.beatUp, Moves.spitUp, Moves.sheerCold + ); + + public static final List cannotBeObsoletedMoves = Arrays.asList( + Moves.returnTheMoveNotTheKeyword, Moves.frustration, Moves.endeavor, Moves.flail, Moves.reversal, + Moves.hiddenPower, Moves.storedPower, Moves.smellingSalts, Moves.fling, Moves.powerTrip, Moves.counter, + Moves.mirrorCoat, Moves.superFang + ); + + public static final List cannotObsoleteMoves = Arrays.asList( + Moves.gearUp, Moves.magneticFlux, Moves.focusPunch, Moves.explosion, Moves.selfDestruct, Moves.geomancy, + Moves.venomDrench + ); + + public static final List doubleBattleMoves = Arrays.asList( + Moves.followMe, Moves.helpingHand, Moves.ragePowder, Moves.afterYou, Moves.allySwitch, Moves.healPulse, + Moves.quash, Moves.ionDeluge, Moves.matBlock, Moves.aromaticMist, Moves.electrify, Moves.instruct, + Moves.spotlight, Moves.decorate, Moves.lifeDew, Moves.coaching + ); + + public static final List uselessMoves = Arrays.asList( + Moves.splash, Moves.celebrate, Moves.holdHands, Moves.teleport, + Moves.reflectType // the AI does not know how to use this move properly + ); + + public static final List requiresOtherMove = Arrays.asList( + Moves.spitUp, Moves.swallow, Moves.dreamEater, Moves.nightmare + ); + + public static final int MIN_DAMAGING_MOVE_POWER = 50; + + public static final int HIGHEST_POKEMON_GEN = 9; + + // Eevee has 8 potential evolutions + public static final int LARGEST_NUMBER_OF_SPLIT_EVOS = 8; +} diff --git a/src/com/pkrandom/constants/Items.java b/src/com/pkrandom/constants/Items.java new file mode 100644 index 0000000..aecf237 --- /dev/null +++ b/src/com/pkrandom/constants/Items.java @@ -0,0 +1,1670 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Items.java - defines an index number constant for every item in the --*/ +/*-- game from Diamond/Pearl to Sword/Shield. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class Items { + // https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_(Generation_IV) + // https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_(Generation_V) + // https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_(Generation_VI) + // https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_(Generation_VII) + // https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_(Let%27s_Go,_Pikachu!_and_Let%27s_Go,_Eevee!) + // https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_(Generation_VIII) + // Unlike in previous generations, GameFreak was *mostly* consistent with this item list starting with Diamond and + // Pearl. Places where they were not consistent are specifically called out in comments. + + // These items are generally available in Diamond/Pearl and onwards. + public static final int none = 0; + public static final int masterBall = 1; + public static final int ultraBall = 2; + public static final int greatBall = 3; + public static final int pokeBall = 4; + public static final int safariBall = 5; + public static final int netBall = 6; + public static final int diveBall = 7; + public static final int nestBall = 8; + public static final int repeatBall = 9; + public static final int timerBall = 10; + public static final int luxuryBall = 11; + public static final int premierBall = 12; + public static final int duskBall = 13; + public static final int healBall = 14; + public static final int quickBall = 15; + public static final int cherishBall = 16; + public static final int potion = 17; + public static final int antidote = 18; + public static final int burnHeal = 19; + public static final int iceHeal = 20; + public static final int awakening = 21; + public static final int paralyzeHeal = 22; + public static final int fullRestore = 23; + public static final int maxPotion = 24; + public static final int hyperPotion = 25; + public static final int superPotion = 26; + public static final int fullHeal = 27; + public static final int revive = 28; + public static final int maxRevive = 29; + public static final int freshWater = 30; + public static final int sodaPop = 31; + public static final int lemonade = 32; + public static final int moomooMilk = 33; + public static final int energyPowder = 34; + public static final int energyRoot = 35; + public static final int healPowder = 36; + public static final int revivalHerb = 37; + public static final int ether = 38; + public static final int maxEther = 39; + public static final int elixir = 40; + public static final int maxElixir = 41; + public static final int lavaCookie = 42; + public static final int berryJuice = 43; + public static final int sacredAsh = 44; + public static final int hpUp = 45; + public static final int protein = 46; + public static final int iron = 47; + public static final int carbos = 48; + public static final int calcium = 49; + public static final int rareCandy = 50; + public static final int ppUp = 51; + public static final int zinc = 52; + public static final int ppMax = 53; + public static final int oldGateau = 54; + public static final int guardSpec = 55; + public static final int direHit = 56; + public static final int xAttack = 57; + public static final int xDefense = 58; + public static final int xSpeed = 59; + public static final int xAccuracy = 60; + public static final int xSpAtk = 61; + public static final int xSpDef = 62; + public static final int pokeDoll = 63; + public static final int fluffyTail = 64; + public static final int blueFlute = 65; + public static final int yellowFlute = 66; + public static final int redFlute = 67; + public static final int blackFlute = 68; + public static final int whiteFlute = 69; + public static final int shoalSalt = 70; + public static final int shoalShell = 71; + public static final int redShard = 72; + public static final int blueShard = 73; + public static final int yellowShard = 74; + public static final int greenShard = 75; + public static final int superRepel = 76; + public static final int maxRepel = 77; + public static final int escapeRope = 78; + public static final int repel = 79; + public static final int sunStone = 80; + public static final int moonStone = 81; + public static final int fireStone = 82; + public static final int thunderStone = 83; + public static final int waterStone = 84; + public static final int leafStone = 85; + public static final int tinyMushroom = 86; + public static final int bigMushroom = 87; + public static final int pearl = 88; + public static final int bigPearl = 89; + public static final int stardust = 90; + public static final int starPiece = 91; + public static final int nugget = 92; + public static final int heartScale = 93; + public static final int honey = 94; + public static final int growthMulch = 95; + public static final int dampMulch = 96; + public static final int stableMulch = 97; + public static final int gooeyMulch = 98; + public static final int rootFossil = 99; + public static final int clawFossil = 100; + public static final int helixFossil = 101; + public static final int domeFossil = 102; + public static final int oldAmber = 103; + public static final int armorFossil = 104; + public static final int skullFossil = 105; + public static final int rareBone = 106; + public static final int shinyStone = 107; + public static final int duskStone = 108; + public static final int dawnStone = 109; + public static final int ovalStone = 110; + public static final int oddKeystone = 111; + public static final int griseousOrb = 112; // unused until Platinum + public static final int tea = 113; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int unused114 = 114; + public static final int autograph = 115; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int douseDrive = 116; // unused until Black and White + public static final int shockDrive = 117; // unused until Black and White + public static final int burnDrive = 118; // unused until Black and White + public static final int chillDrive = 119; // unused until Black and White + public static final int unused120 = 120; + public static final int pokemonBox = 121; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int medicinePocket = 122; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int tmCase = 123; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int candyJar = 124; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int powerUpPocket = 125; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int clothingTrunk = 126; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int catchingPocket = 127; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int battlePocket = 128; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int unused129 = 129; + public static final int unused130 = 130; + public static final int unused131 = 131; + public static final int unused132 = 132; + public static final int unused133 = 133; + public static final int sweetHeart = 134; // unused until Black and White + public static final int adamantOrb = 135; + public static final int lustrousOrb = 136; + public static final int mail1 = 137; // Grass Mail in Gen 4, Greet Mail in Gen 5+ + public static final int mail2 = 138; // Flame Mail in Gen 4, Favored Mail in Gen 5+ + public static final int mail3 = 139; // Bubble Mail in Gen 4, RSVP Mail in Gen 5+ + public static final int mail4 = 140; // Bloom Mail in Gen 4, Thanks Mail in Gen 5+ + public static final int mail5 = 141; // Tunnel Mail in Gen 4, Inquiry Mail in Gen 5+ + public static final int mail6 = 142; // Steel Mail in Gen 4, Like Mail in Gen 5+ + public static final int mail7 = 143; // Heart Mail in Gen 4, Reply Mail in Gen 5+ + public static final int mail8 = 144; // Snow Mail in Gen 4, Bridge Mail S in Gen 5+ + public static final int mail9 = 145; // Space Mail in Gen 4, Bridge Mail D in Gen 5+ + public static final int mail10 = 146; // Air Mail in Gen 4, Bridge Mail T in Gen 5+ + public static final int mail11 = 147; // Mosaic Mail in Gen 4, Bridge Mail V in Gen 5+ + public static final int mail12 = 148; // Brick Mail in Gen 4, Bridge Mail W in Gen 5+ + public static final int cheriBerry = 149; + public static final int chestoBerry = 150; + public static final int pechaBerry = 151; + public static final int rawstBerry = 152; + public static final int aspearBerry = 153; + public static final int leppaBerry = 154; + public static final int oranBerry = 155; + public static final int persimBerry = 156; + public static final int lumBerry = 157; + public static final int sitrusBerry = 158; + public static final int figyBerry = 159; + public static final int wikiBerry = 160; + public static final int magoBerry = 161; + public static final int aguavBerry = 162; + public static final int iapapaBerry = 163; + public static final int razzBerry = 164; + public static final int blukBerry = 165; + public static final int nanabBerry = 166; + public static final int wepearBerry = 167; + public static final int pinapBerry = 168; + public static final int pomegBerry = 169; + public static final int kelpsyBerry = 170; + public static final int qualotBerry = 171; + public static final int hondewBerry = 172; + public static final int grepaBerry = 173; + public static final int tamatoBerry = 174; + public static final int cornnBerry = 175; + public static final int magostBerry = 176; + public static final int rabutaBerry = 177; + public static final int nomelBerry = 178; + public static final int spelonBerry = 179; + public static final int pamtreBerry = 180; + public static final int watmelBerry = 181; + public static final int durinBerry = 182; + public static final int belueBerry = 183; + public static final int occaBerry = 184; + public static final int passhoBerry = 185; + public static final int wacanBerry = 186; + public static final int rindoBerry = 187; + public static final int yacheBerry = 188; + public static final int chopleBerry = 189; + public static final int kebiaBerry = 190; + public static final int shucaBerry = 191; + public static final int cobaBerry = 192; + public static final int payapaBerry = 193; + public static final int tangaBerry = 194; + public static final int chartiBerry = 195; + public static final int kasibBerry = 196; + public static final int habanBerry = 197; + public static final int colburBerry = 198; + public static final int babiriBerry = 199; + public static final int chilanBerry = 200; + public static final int liechiBerry = 201; + public static final int ganlonBerry = 202; + public static final int salacBerry = 203; + public static final int petayaBerry = 204; + public static final int apicotBerry = 205; + public static final int lansatBerry = 206; + public static final int starfBerry = 207; + public static final int enigmaBerry = 208; + public static final int micleBerry = 209; + public static final int custapBerry = 210; + public static final int jabocaBerry = 211; + public static final int rowapBerry = 212; + public static final int brightPowder = 213; + public static final int whiteHerb = 214; + public static final int machoBrace = 215; + public static final int expShare = 216; // Changed into a key item in X/Y + public static final int quickClaw = 217; + public static final int sootheBell = 218; + public static final int mentalHerb = 219; + public static final int choiceBand = 220; + public static final int kingsRock = 221; + public static final int silverPowder = 222; + public static final int amuletCoin = 223; + public static final int cleanseTag = 224; + public static final int soulDew = 225; + public static final int deepSeaTooth = 226; + public static final int deepSeaScale = 227; + public static final int smokeBall = 228; + public static final int everstone = 229; + public static final int focusBand = 230; + public static final int luckyEgg = 231; + public static final int scopeLens = 232; + public static final int metalCoat = 233; + public static final int leftovers = 234; + public static final int dragonScale = 235; + public static final int lightBall = 236; + public static final int softSand = 237; + public static final int hardStone = 238; + public static final int miracleSeed = 239; + public static final int blackGlasses = 240; + public static final int blackBelt = 241; + public static final int magnet = 242; + public static final int mysticWater = 243; + public static final int sharpBeak = 244; + public static final int poisonBarb = 245; + public static final int neverMeltIce = 246; + public static final int spellTag = 247; + public static final int twistedSpoon = 248; + public static final int charcoal = 249; + public static final int dragonFang = 250; + public static final int silkScarf = 251; + public static final int upgrade = 252; + public static final int shellBell = 253; + public static final int seaIncense = 254; + public static final int laxIncense = 255; + public static final int luckyPunch = 256; + public static final int metalPowder = 257; + public static final int thickClub = 258; + public static final int leek = 259; // called "Stick" prior to SwSh + public static final int redScarf = 260; + public static final int blueScarf = 261; + public static final int pinkScarf = 262; + public static final int greenScarf = 263; + public static final int yellowScarf = 264; + public static final int wideLens = 265; + public static final int muscleBand = 266; + public static final int wiseGlasses = 267; + public static final int expertBelt = 268; + public static final int lightClay = 269; + public static final int lifeOrb = 270; + public static final int powerHerb = 271; + public static final int toxicOrb = 272; + public static final int flameOrb = 273; + public static final int quickPowder = 274; + public static final int focusSash = 275; + public static final int zoomLens = 276; + public static final int metronome = 277; + public static final int ironBall = 278; + public static final int laggingTail = 279; + public static final int destinyKnot = 280; + public static final int blackSludge = 281; + public static final int icyRock = 282; + public static final int smoothRock = 283; + public static final int heatRock = 284; + public static final int dampRock = 285; + public static final int gripClaw = 286; + public static final int choiceScarf = 287; + public static final int stickyBarb = 288; + public static final int powerBracer = 289; + public static final int powerBelt = 290; + public static final int powerLens = 291; + public static final int powerBand = 292; + public static final int powerAnklet = 293; + public static final int powerWeight = 294; + public static final int shedShell = 295; + public static final int bigRoot = 296; + public static final int choiceSpecs = 297; + public static final int flamePlate = 298; + public static final int splashPlate = 299; + public static final int zapPlate = 300; + public static final int meadowPlate = 301; + public static final int iciclePlate = 302; + public static final int fistPlate = 303; + public static final int toxicPlate = 304; + public static final int earthPlate = 305; + public static final int skyPlate = 306; + public static final int mindPlate = 307; + public static final int insectPlate = 308; + public static final int stonePlate = 309; + public static final int spookyPlate = 310; + public static final int dracoPlate = 311; + public static final int dreadPlate = 312; + public static final int ironPlate = 313; + public static final int oddIncense = 314; + public static final int rockIncense = 315; + public static final int fullIncense = 316; + public static final int waveIncense = 317; + public static final int roseIncense = 318; + public static final int luckIncense = 319; + public static final int pureIncense = 320; + public static final int protector = 321; + public static final int electirizer = 322; + public static final int magmarizer = 323; + public static final int dubiousDisc = 324; + public static final int reaperCloth = 325; + public static final int razorClaw = 326; + public static final int razorFang = 327; + public static final int tm01 = 328; + public static final int tm02 = 329; + public static final int tm03 = 330; + public static final int tm04 = 331; + public static final int tm05 = 332; + public static final int tm06 = 333; + public static final int tm07 = 334; + public static final int tm08 = 335; + public static final int tm09 = 336; + public static final int tm10 = 337; + public static final int tm11 = 338; + public static final int tm12 = 339; + public static final int tm13 = 340; + public static final int tm14 = 341; + public static final int tm15 = 342; + public static final int tm16 = 343; + public static final int tm17 = 344; + public static final int tm18 = 345; + public static final int tm19 = 346; + public static final int tm20 = 347; + public static final int tm21 = 348; + public static final int tm22 = 349; + public static final int tm23 = 350; + public static final int tm24 = 351; + public static final int tm25 = 352; + public static final int tm26 = 353; + public static final int tm27 = 354; + public static final int tm28 = 355; + public static final int tm29 = 356; + public static final int tm30 = 357; + public static final int tm31 = 358; + public static final int tm32 = 359; + public static final int tm33 = 360; + public static final int tm34 = 361; + public static final int tm35 = 362; + public static final int tm36 = 363; + public static final int tm37 = 364; + public static final int tm38 = 365; + public static final int tm39 = 366; + public static final int tm40 = 367; + public static final int tm41 = 368; + public static final int tm42 = 369; + public static final int tm43 = 370; + public static final int tm44 = 371; + public static final int tm45 = 372; + public static final int tm46 = 373; + public static final int tm47 = 374; + public static final int tm48 = 375; + public static final int tm49 = 376; + public static final int tm50 = 377; + public static final int tm51 = 378; + public static final int tm52 = 379; + public static final int tm53 = 380; + public static final int tm54 = 381; + public static final int tm55 = 382; + public static final int tm56 = 383; + public static final int tm57 = 384; + public static final int tm58 = 385; + public static final int tm59 = 386; + public static final int tm60 = 387; + public static final int tm61 = 388; + public static final int tm62 = 389; + public static final int tm63 = 390; + public static final int tm64 = 391; + public static final int tm65 = 392; + public static final int tm66 = 393; + public static final int tm67 = 394; + public static final int tm68 = 395; + public static final int tm69 = 396; + public static final int tm70 = 397; + public static final int tm71 = 398; + public static final int tm72 = 399; + public static final int tm73 = 400; + public static final int tm74 = 401; + public static final int tm75 = 402; + public static final int tm76 = 403; + public static final int tm77 = 404; + public static final int tm78 = 405; + public static final int tm79 = 406; + public static final int tm80 = 407; + public static final int tm81 = 408; + public static final int tm82 = 409; + public static final int tm83 = 410; + public static final int tm84 = 411; + public static final int tm85 = 412; + public static final int tm86 = 413; + public static final int tm87 = 414; + public static final int tm88 = 415; + public static final int tm89 = 416; + public static final int tm90 = 417; + public static final int tm91 = 418; + public static final int tm92 = 419; + public static final int hm01 = 420; + public static final int hm02 = 421; + public static final int hm03 = 422; + public static final int hm04 = 423; + public static final int hm05 = 424; + public static final int hm06 = 425; + public static final int hm07 = 426; // unused after Gen 4 + public static final int hm08 = 427; // unused after Gen 4 + public static final int explorerKit = 428; // unused in HeartGold and SoulSilver + public static final int lootSack = 429; + public static final int ruleBook = 430; + public static final int pokeRadar = 431; + public static final int pointCard = 432; + public static final int journal = 433; + public static final int sealCase = 434; + public static final int fashionCase = 435; + public static final int sealBag = 436; + public static final int palPad = 437; + public static final int worksKey = 438; + public static final int oldCharm = 439; + public static final int galacticKey = 440; + public static final int redChain = 441; + public static final int townMap = 442; + public static final int vsSeeker = 443; + public static final int coinCase = 444; + public static final int oldRod = 445; + public static final int goodRod = 446; + public static final int superRod = 447; + public static final int sprayduck = 448; + public static final int poffinCase = 449; + public static final int bike = 450; // Green Bicycle in X/Y + public static final int suiteKey = 451; + public static final int oaksLetter = 452; + public static final int lunarWing = 453; + public static final int memberCard = 454; + public static final int azureFlute = 455; + public static final int ssTicketJohto = 456; + public static final int contestPass = 457; + public static final int magmaStone = 458; + public static final int parcelSinnoh = 459; + public static final int coupon1 = 460; + public static final int coupon2 = 461; + public static final int coupon3 = 462; + public static final int storageKeySinnoh = 463; + public static final int secretPotion = 464; + + // These items are generally available in Platinum and onwards. + public static final int vsRecorder = 465; + public static final int gracidea = 466; + public static final int secretKeySinnoh = 467; + + // These items are generally available in HeartGold/SoulSilver and onwards. + public static final int apricornBox = 468; + public static final int unownReport = 469; + public static final int berryPots = 470; + public static final int dowsingMachine = 471; + public static final int blueCard = 472; + public static final int slowpokeTail = 473; + public static final int clearBell = 474; + public static final int cardKeyJohto = 475; + public static final int basementKeyJohto = 476; + public static final int squirtBottle = 477; + public static final int redScale = 478; + public static final int lostItem = 479; + public static final int pass = 480; + public static final int machinePart = 481; + public static final int silverWing = 482; + public static final int rainbowWing = 483; + public static final int mysteryEgg = 484; + public static final int redApricorn = 485; + public static final int blueApricorn = 486; + public static final int yellowApricorn = 487; + public static final int greenApricorn = 488; + public static final int pinkApricorn = 489; + public static final int whiteApricorn = 490; + public static final int blackApricorn = 491; + public static final int fastBall = 492; + public static final int levelBall = 493; + public static final int lureBall = 494; + public static final int heavyBall = 495; + public static final int loveBall = 496; + public static final int friendBall = 497; + public static final int moonBall = 498; + public static final int sportBall = 499; + public static final int parkBall = 500; + public static final int photoAlbum = 501; + public static final int gbSounds = 502; + public static final int tidalBell = 503; + public static final int rageCandyBar = 504; + public static final int dataCard01 = 505; + public static final int dataCard02 = 506; + public static final int dataCard03 = 507; + public static final int dataCard04 = 508; + public static final int dataCard05 = 509; + public static final int dataCard06 = 510; + public static final int dataCard07 = 511; + public static final int dataCard08 = 512; + public static final int dataCard09 = 513; + public static final int dataCard10 = 514; + public static final int dataCard11 = 515; + public static final int dataCard12 = 516; + public static final int dataCard13 = 517; + public static final int dataCard14 = 518; + public static final int dataCard15 = 519; + public static final int dataCard16 = 520; + public static final int dataCard17 = 521; + public static final int dataCard18 = 522; + public static final int dataCard19 = 523; + public static final int dataCard20 = 524; + public static final int dataCard21 = 525; + public static final int dataCard22 = 526; + public static final int dataCard23 = 527; + public static final int dataCard24 = 528; + public static final int dataCard25 = 529; + public static final int dataCard26 = 530; + public static final int dataCard27 = 531; + public static final int jadeOrb = 532; + public static final int lockCapsule = 533; + public static final int redOrb = 534; // Changed into a held item in Omega Ruby/Alpha Sapphire + public static final int blueOrb = 535; // Changed into a held item in Omega Ruby/Alpha Sapphire + public static final int enigmaStone = 536; + + // These items are generally available in Black/White and onwards + public static final int prismScale = 537; + public static final int eviolite = 538; + public static final int floatStone = 539; + public static final int rockyHelmet = 540; + public static final int airBalloon = 541; + public static final int redCard = 542; + public static final int ringTarget = 543; + public static final int bindingBand = 544; + public static final int absorbBulb = 545; + public static final int cellBattery = 546; + public static final int ejectButton = 547; + public static final int fireGem = 548; + public static final int waterGem = 549; + public static final int electricGem = 550; + public static final int grassGem = 551; + public static final int iceGem = 552; + public static final int fightingGem = 553; + public static final int poisonGem = 554; + public static final int groundGem = 555; + public static final int flyingGem = 556; + public static final int psychicGem = 557; + public static final int bugGem = 558; + public static final int rockGem = 559; + public static final int ghostGem = 560; + public static final int dragonGem = 561; + public static final int darkGem = 562; + public static final int steelGem = 563; + public static final int normalGem = 564; + public static final int healthFeather = 565; + public static final int muscleFeather = 566; + public static final int resistFeather = 567; + public static final int geniusFeather = 568; + public static final int cleverFeather = 569; + public static final int swiftFeather = 570; + public static final int prettyFeather = 571; + public static final int coverFossil = 572; + public static final int plumeFossil = 573; + public static final int libertyPass = 574; + public static final int passOrb = 575; + public static final int dreamBall = 576; + public static final int pokeToy = 577; + public static final int propCase = 578; + public static final int dragonSkull = 579; + public static final int balmMushroom = 580; + public static final int bigNugget = 581; + public static final int pearlString = 582; + public static final int cometShard = 583; + public static final int relicCopper = 584; + public static final int relicSilver = 585; + public static final int relicGold = 586; + public static final int relicVase = 587; + public static final int relicBand = 588; + public static final int relicStatue = 589; + public static final int relicCrown = 590; + public static final int casteliacone = 591; + public static final int direHit2 = 592; + public static final int xSpeed2 = 593; + public static final int xSpAtk2 = 594; + public static final int xSpDef2 = 595; + public static final int xDefense2 = 596; + public static final int xAttack2 = 597; + public static final int xAccuracy2 = 598; + public static final int xSpeed3 = 599; + public static final int xSpAtk3 = 600; + public static final int xSpDef3 = 601; + public static final int xDefense3 = 602; + public static final int xAttack3 = 603; + public static final int xAccuracy3 = 604; + public static final int xSpeed6 = 605; + public static final int xSpAtk6 = 606; + public static final int xSpDef6 = 607; + public static final int xDefense6 = 608; + public static final int xAttack6 = 609; + public static final int xAccuracy6 = 610; + public static final int abilityUrge = 611; + public static final int itemDrop = 612; + public static final int itemUrge = 613; + public static final int resetUrge = 614; + public static final int direHit3 = 615; + public static final int lightStone = 616; + public static final int darkStone = 617; + public static final int tm93 = 618; + public static final int tm94 = 619; + public static final int tm95 = 620; + public static final int xtransceiverMale = 621; + public static final int unused622 = 622; + public static final int gram1 = 623; + public static final int gram2 = 624; + public static final int gram3 = 625; + public static final int xtransceiverFemale = 626; + + // These items are generally available in Black 2/White 2 and onwards. + public static final int medalBox = 627; + public static final int dNASplicersFuse = 628; + public static final int dNASplicersSeparate = 629; + public static final int permit = 630; + public static final int ovalCharm = 631; + public static final int shinyCharm = 632; + public static final int plasmaCard = 633; + public static final int grubbyHanky = 634; + public static final int colressMachine = 635; + public static final int droppedItemCurtis = 636; + public static final int droppedItemYancy = 637; + public static final int revealGlass = 638; + + // These items are generally available in X/Y and onwards. + public static final int weaknessPolicy = 639; + public static final int assaultVest = 640; + public static final int holoCasterMale = 641; + public static final int profsLetter = 642; + public static final int rollerSkates = 643; + public static final int pixiePlate = 644; + public static final int abilityCapsule = 645; + public static final int whippedDream = 646; + public static final int sachet = 647; + public static final int luminousMoss = 648; + public static final int snowball = 649; + public static final int safetyGoggles = 650; + public static final int pokeFlute = 651; + public static final int richMulch = 652; + public static final int surpriseMulch = 653; + public static final int boostMulch = 654; + public static final int amazeMulch = 655; + public static final int gengarite = 656; + public static final int gardevoirite = 657; + public static final int ampharosite = 658; + public static final int venusaurite = 659; + public static final int charizarditeX = 660; + public static final int blastoisinite = 661; + public static final int mewtwoniteX = 662; + public static final int mewtwoniteY = 663; + public static final int blazikenite = 664; + public static final int medichamite = 665; + public static final int houndoominite = 666; + public static final int aggronite = 667; + public static final int banettite = 668; + public static final int tyranitarite = 669; + public static final int scizorite = 670; + public static final int pinsirite = 671; + public static final int aerodactylite = 672; + public static final int lucarionite = 673; + public static final int abomasite = 674; + public static final int kangaskhanite = 675; + public static final int gyaradosite = 676; + public static final int absolite = 677; + public static final int charizarditeY = 678; + public static final int alakazite = 679; + public static final int heracronite = 680; + public static final int mawilite = 681; + public static final int manectite = 682; + public static final int garchompite = 683; + public static final int latiasite = 684; + public static final int latiosite = 685; + public static final int roseliBerry = 686; + public static final int keeBerry = 687; + public static final int marangaBerry = 688; + public static final int sprinklotad = 689; + public static final int tm96 = 690; + public static final int tm97 = 691; + public static final int tm98 = 692; + public static final int tm99 = 693; + public static final int tm100 = 694; + public static final int powerPlantPass = 695; + public static final int megaRing = 696; + public static final int intriguingStone = 697; + public static final int commonStone = 698; + public static final int discountCoupon = 699; + public static final int elevatorKey = 700; + public static final int tmvPass = 701; + public static final int honorofKalos = 702; + public static final int adventureGuide = 703; + public static final int strangeSouvenir = 704; + public static final int lensCase = 705; + public static final int makeupBag = 706; + public static final int travelTrunk = 707; + public static final int lumioseGalette = 708; + public static final int shalourSable = 709; + public static final int jawFossil = 710; + public static final int sailFossil = 711; + public static final int lookerTicket = 712; + public static final int bikeYellow = 713; + public static final int holoCasterFemale = 714; + public static final int fairyGem = 715; + public static final int megaCharm = 716; + public static final int megaGlove = 717; + + // These items are generally available in Omega Ruby/Alpha Sapphire and onwards. + public static final int machBike = 718; + public static final int acroBike = 719; + public static final int wailmerPail = 720; + public static final int devonParts = 721; + public static final int sootSack = 722; + public static final int basementKeyHoenn = 723; + public static final int pokeblockKit = 724; + public static final int letter = 725; + public static final int eonTicket = 726; + public static final int scanner = 727; + public static final int goGoggles = 728; + public static final int meteoriteFirstForm = 729; + public static final int keytoRoom1 = 730; + public static final int keytoRoom2 = 731; + public static final int keytoRoom4 = 732; + public static final int keytoRoom6 = 733; + public static final int storageKeyHoenn = 734; + public static final int devonScope = 735; + public static final int ssTicketHoenn = 736; + public static final int hm07ORAS = 737; + public static final int devonScubaGear = 738; + public static final int contestCostumeMale = 739; + public static final int contestCostumeFemale = 740; + public static final int magmaSuit = 741; + public static final int aquaSuit = 742; + public static final int pairOfTickets = 743; + public static final int megaBracelet = 744; + public static final int megaPendant = 745; + public static final int megaGlasses = 746; + public static final int megaAnchor = 747; + public static final int megaStickpin = 748; + public static final int megaTiara = 749; + public static final int megaAnklet = 750; + public static final int meteoriteSecondForm = 751; + public static final int swampertite = 752; + public static final int sceptilite = 753; + public static final int sablenite = 754; + public static final int altarianite = 755; + public static final int galladite = 756; + public static final int audinite = 757; + public static final int metagrossite = 758; + public static final int sharpedonite = 759; + public static final int slowbronite = 760; + public static final int steelixite = 761; + public static final int pidgeotite = 762; + public static final int glalitite = 763; + public static final int diancite = 764; + public static final int prisonBottle = 765; + public static final int megaCuff = 766; + public static final int cameruptite = 767; + public static final int lopunnite = 768; + public static final int salamencite = 769; + public static final int beedrillite = 770; + public static final int meteoriteThirdForm = 771; + public static final int meteoriteFinalForm = 772; + public static final int keyStone = 773; + public static final int meteoriteShard = 774; + public static final int eonFlute = 775; + + // These items are generally available in Sun/Moon and onwards. + public static final int normaliumZHeld = 776; + public static final int firiumZHeld = 777; + public static final int wateriumZHeld = 778; + public static final int electriumZHeld = 779; + public static final int grassiumZHeld = 780; + public static final int iciumZHeld = 781; + public static final int fightiniumZHeld = 782; + public static final int poisoniumZHeld = 783; + public static final int groundiumZHeld = 784; + public static final int flyiniumZHeld = 785; + public static final int psychiumZHeld = 786; + public static final int buginiumZHeld = 787; + public static final int rockiumZHeld = 788; + public static final int ghostiumZHeld = 789; + public static final int dragoniumZHeld = 790; + public static final int darkiniumZHeld = 791; + public static final int steeliumZHeld = 792; + public static final int fairiumZHeld = 793; + public static final int pikaniumZHeld = 794; + public static final int bottleCap = 795; + public static final int goldBottleCap = 796; + public static final int zRing = 797; + public static final int decidiumZHeld = 798; + public static final int inciniumZHeld = 799; + public static final int primariumZHeld = 800; + public static final int tapuniumZHeld = 801; + public static final int marshadiumZHeld = 802; + public static final int aloraichiumZHeld = 803; + public static final int snorliumZHeld = 804; + public static final int eeviumZHeld = 805; + public static final int mewniumZHeld = 806; + public static final int normaliumZBag = 807; + public static final int firiumZBag = 808; + public static final int wateriumZBag = 809; + public static final int electriumZBag = 810; + public static final int grassiumZBag = 811; + public static final int iciumZBag = 812; + public static final int fightiniumZBag = 813; + public static final int poisoniumZBag = 814; + public static final int groundiumZBag = 815; + public static final int flyiniumZBag = 816; + public static final int psychiumZBag = 817; + public static final int buginiumZBag = 818; + public static final int rockiumZBag = 819; + public static final int ghostiumZBag = 820; + public static final int dragoniumZBag = 821; + public static final int darkiniumZBag = 822; + public static final int steeliumZBag = 823; + public static final int fairiumZBag = 824; + public static final int pikaniumZBag = 825; + public static final int decidiumZBag = 826; + public static final int inciniumZBag = 827; + public static final int primariumZBag = 828; + public static final int tapuniumZBag = 829; + public static final int marshadiumZBag = 830; + public static final int aloraichiumZBag = 831; + public static final int snorliumZBag = 832; + public static final int eeviumZBag = 833; + public static final int mewniumZBag = 834; + public static final int pikashuniumZHeld = 835; + public static final int pikashuniumZBag = 836; + public static final int unused837 = 837; + public static final int unused838 = 838; + public static final int unused839 = 839; + public static final int unused840 = 840; + public static final int forageBag = 841; + public static final int fishingRod = 842; + public static final int professorsMask = 843; + public static final int festivalTicket = 844; + public static final int sparklingStone = 845; + public static final int adrenalineOrb = 846; + public static final int zygardeCube = 847; + public static final int unused848 = 848; + public static final int iceStone = 849; + public static final int ridePager = 850; + public static final int beastBall = 851; + public static final int bigMalasada = 852; + public static final int redNectar = 853; + public static final int yellowNectar = 854; + public static final int pinkNectar = 855; + public static final int purpleNectar = 856; + public static final int sunFlute = 857; + public static final int moonFlute = 858; + public static final int unused859 = 859; + public static final int enigmaticCard = 860; + public static final int silverRazzBerry = 861; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int goldenRazzBerry = 862; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int silverNanabBerry = 863; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int goldenNanabBerry = 864; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int silverPinapBerry = 865; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int goldenPinapBerry = 866; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int unused867 = 867; + public static final int unused868 = 868; + public static final int unused869 = 869; + public static final int unused870 = 870; + public static final int unused871 = 871; + public static final int secretKeyKanto = 872; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int ssTicketKanto = 873; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int silphScope = 874; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int parcelKanto = 875; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int cardKeyKanto = 876; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int goldTeeth = 877; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int liftKey = 878; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int terrainExtender = 879; + public static final int protectivePads = 880; + public static final int electricSeed = 881; + public static final int psychicSeed = 882; + public static final int mistySeed = 883; + public static final int grassySeed = 884; + public static final int stretchySpring = 885; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int chalkyStone = 886; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int marble = 887; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int loneEarring = 888; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int beachGlass = 889; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int goldLeaf = 890; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int silverLeaf = 891; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int polishedMudBall = 892; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int tropicalShell = 893; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int leafLetterPikachu = 894; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int leafLetterEevee = 895; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int smallBouquet = 896; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int unused897 = 897; + public static final int unused898 = 898; + public static final int unused899 = 899; + public static final int lure = 900; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int superLure = 901; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int maxLure = 902; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int pewterCrunchies = 903; // unused until Let's Go Pikachu/Let's Go Eevee + public static final int fightingMemory = 904; + public static final int flyingMemory = 905; + public static final int poisonMemory = 906; + public static final int groundMemory = 907; + public static final int rockMemory = 908; + public static final int bugMemory = 909; + public static final int ghostMemory = 910; + public static final int steelMemory = 911; + public static final int fireMemory = 912; + public static final int waterMemory = 913; + public static final int grassMemory = 914; + public static final int electricMemory = 915; + public static final int psychicMemory = 916; + public static final int iceMemory = 917; + public static final int dragonMemory = 918; + public static final int darkMemory = 919; + public static final int fairyMemory = 920; + + // These items are generally available in Ultra Sun/Ultra Moon and onwards. + public static final int solganiumZBag = 921; + public static final int lunaliumZBag = 922; + public static final int ultranecroziumZBag = 923; + public static final int mimikiumZHeld = 924; + public static final int lycaniumZHeld = 925; + public static final int kommoniumZHeld = 926; + public static final int solganiumZHeld = 927; + public static final int lunaliumZHeld = 928; + public static final int ultranecroziumZHeld = 929; + public static final int mimikiumZBag = 930; + public static final int lycaniumZBag = 931; + public static final int kommoniumZBag = 932; + public static final int zPowerRing = 933; + public static final int pinkPetal = 934; + public static final int orangePetal = 935; + public static final int bluePetal = 936; + public static final int redPetal = 937; + public static final int greenPetal = 938; + public static final int yellowPetal = 939; + public static final int purplePetal = 940; + public static final int rainbowFlower = 941; + public static final int surgeBadge = 942; + public static final int nSolarizerFuse = 943; + public static final int nLunarizerFuse = 944; + public static final int nSolarizerSeparate = 945; + public static final int nLunarizerSeparate = 946; + public static final int ilimaNormaliumZ = 947; + public static final int leftPokeBall = 948; + public static final int rotoHatch = 949; + public static final int rotoBargain = 950; + public static final int rotoPrizeMoney = 951; + public static final int rotoExpPoints = 952; + public static final int rotoFriendship = 953; + public static final int rotoEncounter = 954; + public static final int rotoStealth = 955; + public static final int rotoHPRestore = 956; + public static final int rotoPPRestore = 957; + public static final int rotoBoost = 958; + public static final int rotoCatch = 959; + + // These items are generally available in Let's Go Pikachu/Let's Go Eevee and onwards. + public static final int healthCandy = 960; + public static final int mightyCandy = 961; + public static final int toughCandy = 962; + public static final int smartCandy = 963; + public static final int courageCandy = 964; + public static final int quickCandy = 965; + public static final int healthCandyL = 966; + public static final int mightyCandyL = 967; + public static final int toughCandyL = 968; + public static final int smartCandyL = 969; + public static final int courageCandyL = 970; + public static final int quickCandyL = 971; + public static final int healthCandyXL = 972; + public static final int mightyCandyXL = 973; + public static final int toughCandyXL = 974; + public static final int smartCandyXL = 975; + public static final int courageCandyXL = 976; + public static final int quickCandyXL = 977; + public static final int bulbasaurCandy = 978; + public static final int charmanderCandy = 979; + public static final int squirtleCandy = 980; + public static final int caterpieCandy = 981; + public static final int weedleCandy = 982; + public static final int pidgeyCandy = 983; + public static final int rattataCandy = 984; + public static final int spearowCandy = 985; + public static final int ekansCandy = 986; + public static final int pikachuCandy = 987; + public static final int sandshrewCandy = 988; + public static final int nidoranFemaleCandy = 989; + public static final int nidoranMaleCandy = 990; + public static final int clefairyCandy = 991; + public static final int vulpixCandy = 992; + public static final int jigglypuffCandy = 993; + public static final int zubatCandy = 994; + public static final int oddishCandy = 995; + public static final int parasCandy = 996; + public static final int venonatCandy = 997; + public static final int diglettCandy = 998; + public static final int meowthCandy = 999; + public static final int psyduckCandy = 1000; + public static final int mankeyCandy = 1001; + public static final int growlitheCandy = 1002; + public static final int poliwagCandy = 1003; + public static final int abraCandy = 1004; + public static final int machopCandy = 1005; + public static final int bellsproutCandy = 1006; + public static final int tentacoolCandy = 1007; + public static final int geodudeCandy = 1008; + public static final int ponytaCandy = 1009; + public static final int slowpokeCandy = 1010; + public static final int magnemiteCandy = 1011; + public static final int farfetchdCandy = 1012; + public static final int doduoCandy = 1013; + public static final int seelCandy = 1014; + public static final int grimerCandy = 1015; + public static final int shellderCandy = 1016; + public static final int gastlyCandy = 1017; + public static final int onixCandy = 1018; + public static final int drowzeeCandy = 1019; + public static final int krabbyCandy = 1020; + public static final int voltorbCandy = 1021; + public static final int exeggcuteCandy = 1022; + public static final int cuboneCandy = 1023; + public static final int hitmonleeCandy = 1024; + public static final int hitmonchanCandy = 1025; + public static final int lickitungCandy = 1026; + public static final int koffingCandy = 1027; + public static final int rhyhornCandy = 1028; + public static final int chanseyCandy = 1029; + public static final int tangelaCandy = 1030; + public static final int kangaskhanCandy = 1031; + public static final int horseaCandy = 1032; + public static final int goldeenCandy = 1033; + public static final int staryuCandy = 1034; + public static final int mrMimeCandy = 1035; + public static final int scytherCandy = 1036; + public static final int jynxCandy = 1037; + public static final int electabuzzCandy = 1038; + public static final int magmarCandy = 1039; // Is item 1057 in Let's Go Pikachu/Let's Go Eevee, shifting all below items up. + public static final int pinsirCandy = 1040; + public static final int taurosCandy = 1041; + public static final int magikarpCandy = 1042; + public static final int laprasCandy = 1043; + public static final int dittoCandy = 1044; + public static final int eeveeCandy = 1045; + public static final int porygonCandy = 1046; + public static final int omanyteCandy = 1047; + public static final int kabutoCandy = 1048; + public static final int aerodactylCandy = 1049; + public static final int snorlaxCandy = 1050; + public static final int articunoCandy = 1051; + public static final int zapdosCandy = 1052; + public static final int moltresCandy = 1053; + public static final int dratiniCandy = 1054; + public static final int mewtwoCandy = 1055; + public static final int mewCandy = 1056; + public static final int meltanCandy = 1057; + + // These items are generally available in Sword/Shield v1.0.0 and onwards. + public static final int unused1058 = 1058; + public static final int unused1059 = 1059; + public static final int unused1060 = 1060; + public static final int unused1061 = 1061; + public static final int unused1062 = 1062; + public static final int unused1063 = 1063; + public static final int unused1064 = 1064; + public static final int unused1065 = 1065; + public static final int unused1066 = 1066; + public static final int unused1067 = 1067; + public static final int unused1068 = 1068; + public static final int unused1069 = 1069; + public static final int unused1070 = 1070; + public static final int unused1071 = 1071; + public static final int unused1072 = 1072; + public static final int unused1073 = 1073; + public static final int endorsement = 1074; + public static final int pokemonBoxLink = 1075; + public static final int wishingStar = 1076; + public static final int dynamaxBand = 1077; + public static final int unused1078 = 1078; + public static final int unused1079 = 1079; + public static final int fishingRodGalar = 1080; + public static final int rotomBike = 1081; + public static final int unused1082 = 1082; + public static final int unused1083 = 1083; + public static final int sausages = 1084; + public static final int bobsFoodTin = 1085; + public static final int bachsFoodTin = 1086; + public static final int tinOfBeans = 1087; + public static final int bread = 1088; + public static final int pasta = 1089; + public static final int mixedMushrooms = 1090; + public static final int smokePokeTail = 1091; + public static final int largeLeek = 1092; + public static final int fancyApple = 1093; + public static final int brittleBones = 1094; + public static final int packOfPotatoes = 1095; + public static final int pungentRoot = 1096; + public static final int saladMix = 1097; + public static final int friedFood = 1098; + public static final int boiledEgg = 1099; + public static final int campingGear = 1100; + public static final int unused1101 = 1101; + public static final int unused1102 = 1102; + public static final int rustedSword = 1103; + public static final int rustedShield = 1104; + public static final int fossilizedBird = 1105; + public static final int fossilizedFish = 1106; + public static final int fossilizedDrake = 1107; + public static final int fossilizedDino = 1108; + public static final int strawberrySweet = 1109; + public static final int loveSweet = 1110; + public static final int berrySweet = 1111; + public static final int cloverSweet = 1112; + public static final int flowerSweet = 1113; + public static final int starSweet = 1114; + public static final int ribbonSweet = 1115; + public static final int sweetApple = 1116; + public static final int tartApple = 1117; + public static final int throatSpray = 1118; + public static final int ejectPack = 1119; + public static final int heavyDutyBoots = 1120; + public static final int blunderPolicy = 1121; + public static final int roomService = 1122; + public static final int utilityUmbrella = 1123; + public static final int expCandyXS = 1124; + public static final int expCandyS = 1125; + public static final int expCandyM = 1126; + public static final int expCandyL = 1127; + public static final int expCandyXL = 1128; + public static final int dynamaxCandy = 1129; + public static final int tr00 = 1130; + public static final int tr01 = 1131; + public static final int tr02 = 1132; + public static final int tr03 = 1133; + public static final int tr04 = 1134; + public static final int tr05 = 1135; + public static final int tr06 = 1136; + public static final int tr07 = 1137; + public static final int tr08 = 1138; + public static final int tr09 = 1139; + public static final int tr10 = 1140; + public static final int tr11 = 1141; + public static final int tr12 = 1142; + public static final int tr13 = 1143; + public static final int tr14 = 1144; + public static final int tr15 = 1145; + public static final int tr16 = 1146; + public static final int tr17 = 1147; + public static final int tr18 = 1148; + public static final int tr19 = 1149; + public static final int tr20 = 1150; + public static final int tr21 = 1151; + public static final int tr22 = 1152; + public static final int tr23 = 1153; + public static final int tr24 = 1154; + public static final int tr25 = 1155; + public static final int tr26 = 1156; + public static final int tr27 = 1157; + public static final int tr28 = 1158; + public static final int tr29 = 1159; + public static final int tr30 = 1160; + public static final int tr31 = 1161; + public static final int tr32 = 1162; + public static final int tr33 = 1163; + public static final int tr34 = 1164; + public static final int tr35 = 1165; + public static final int tr36 = 1166; + public static final int tr37 = 1167; + public static final int tr38 = 1168; + public static final int tr39 = 1169; + public static final int tr40 = 1170; + public static final int tr41 = 1171; + public static final int tr42 = 1172; + public static final int tr43 = 1173; + public static final int tr44 = 1174; + public static final int tr45 = 1175; + public static final int tr46 = 1176; + public static final int tr47 = 1177; + public static final int tr48 = 1178; + public static final int tr49 = 1179; + public static final int tr50 = 1180; + public static final int tr51 = 1181; + public static final int tr52 = 1182; + public static final int tr53 = 1183; + public static final int tr54 = 1184; + public static final int tr55 = 1185; + public static final int tr56 = 1186; + public static final int tr57 = 1187; + public static final int tr58 = 1188; + public static final int tr59 = 1189; + public static final int tr60 = 1190; + public static final int tr61 = 1191; + public static final int tr62 = 1192; + public static final int tr63 = 1193; + public static final int tr64 = 1194; + public static final int tr65 = 1195; + public static final int tr66 = 1196; + public static final int tr67 = 1197; + public static final int tr68 = 1198; + public static final int tr69 = 1199; + public static final int tr70 = 1200; + public static final int tr71 = 1201; + public static final int tr72 = 1202; + public static final int tr73 = 1203; + public static final int tr74 = 1204; + public static final int tr75 = 1205; + public static final int tr76 = 1206; + public static final int tr77 = 1207; + public static final int tr78 = 1208; + public static final int tr79 = 1209; + public static final int tr80 = 1210; + public static final int tr81 = 1211; + public static final int tr82 = 1212; + public static final int tr83 = 1213; + public static final int tr84 = 1214; + public static final int tr85 = 1215; + public static final int tr86 = 1216; + public static final int tr87 = 1217; + public static final int tr88 = 1218; + public static final int tr89 = 1219; + public static final int tr90 = 1220; + public static final int tr91 = 1221; + public static final int tr92 = 1222; + public static final int tr93 = 1223; + public static final int tr94 = 1224; + public static final int tr95 = 1225; + public static final int tr96 = 1226; + public static final int tr97 = 1227; + public static final int tr98 = 1228; + public static final int tr99 = 1229; + public static final int tm00 = 1230; + public static final int lonelyMint = 1231; + public static final int adamantMint = 1232; + public static final int naughtyMint = 1233; + public static final int braveMint = 1234; + public static final int boldMint = 1235; + public static final int impishMint = 1236; + public static final int laxMint = 1237; + public static final int relaxedMint = 1238; + public static final int modestMint = 1239; + public static final int mildMint = 1240; + public static final int rashMint = 1241; + public static final int quietMint = 1242; + public static final int calmMint = 1243; + public static final int gentleMint = 1244; + public static final int carefulMint = 1245; + public static final int sassyMint = 1246; + public static final int timidMint = 1247; + public static final int hastyMint = 1248; + public static final int jollyMint = 1249; + public static final int naiveMint = 1250; + public static final int seriousMint = 1251; + public static final int wishingPiece = 1252; + public static final int crackedPot = 1253; + public static final int chippedPot = 1254; + public static final int hiTechEarbuds = 1255; + public static final int fruitBunch = 1256; + public static final int moomooCheese = 1257; + public static final int spiceMix = 1258; + public static final int freshCream = 1259; + public static final int packagedCurry = 1260; + public static final int coconutMilk = 1261; + public static final int instantNoodles = 1262; + public static final int precookedBurger = 1263; + public static final int gigantamix = 1264; + public static final int wishingChip = 1265; + public static final int rotomBikeWaterMode = 1266; + public static final int catchingCharm = 1267; + public static final int unused1268 = 1268; + public static final int oldLetter = 1269; + public static final int bandAutograph = 1270; + public static final int soniasBook = 1271; + public static final int unused1272 = 1272; + public static final int unused1273 = 1273; + public static final int unused1274 = 1274; + public static final int unused1275 = 1275; + public static final int unused1276 = 1276; + public static final int unused1277 = 1277; + public static final int rotomCatalog = 1278; + public static final int starAnd458 = 1279; + public static final int starAnd15 = 1280; + public static final int starAnd337 = 1281; + public static final int starAnd603 = 1282; + public static final int starAnd390 = 1283; + public static final int starSgr6879 = 1284; + public static final int starSgr6859 = 1285; + public static final int starSgr6913 = 1286; + public static final int starSgr7348 = 1287; + public static final int starSgr7121 = 1288; + public static final int starSgr6746 = 1289; + public static final int starSgr7194 = 1290; + public static final int starSgr7337 = 1291; + public static final int starSgr7343 = 1292; + public static final int starSgr6812 = 1293; + public static final int starSgr7116 = 1294; + public static final int starSgr7264 = 1295; + public static final int starSgr7597 = 1296; + public static final int starDel7882 = 1297; + public static final int starDel7906 = 1298; + public static final int starDel7852 = 1299; + public static final int starPsc596 = 1300; + public static final int starPsc361 = 1301; + public static final int starPsc510 = 1302; + public static final int starPsc437 = 1303; + public static final int starPsc8773 = 1304; + public static final int starLep1865 = 1305; + public static final int starLep1829 = 1306; + public static final int starBoo5340 = 1307; + public static final int starBoo5506 = 1308; + public static final int starBoo5435 = 1309; + public static final int starBoo5602 = 1310; + public static final int starBoo5733 = 1311; + public static final int starBoo5235 = 1312; + public static final int starBoo5351 = 1313; + public static final int starHya3748 = 1314; + public static final int starHya3903 = 1315; + public static final int starHya3418 = 1316; + public static final int starHya3482 = 1317; + public static final int starHya3845 = 1318; + public static final int starEri1084 = 1319; + public static final int starEri472 = 1320; + public static final int starEri1666 = 1321; + public static final int starEri897 = 1322; + public static final int starEri1231 = 1323; + public static final int starEri874 = 1324; + public static final int starEri1298 = 1325; + public static final int starEri1325 = 1326; + public static final int starEri984 = 1327; + public static final int starEri1464 = 1328; + public static final int starEri1393 = 1329; + public static final int starEri850 = 1330; + public static final int starTau1409 = 1331; + public static final int starTau1457 = 1332; + public static final int starTau1165 = 1333; + public static final int starTau1791 = 1334; + public static final int starTau1910 = 1335; + public static final int starTau1346 = 1336; + public static final int starTau1373 = 1337; + public static final int starTau1412 = 1338; + public static final int starCMa2491 = 1339; + public static final int starCMa2693 = 1340; + public static final int starCMa2294 = 1341; + public static final int starCMa2827 = 1342; + public static final int starCMa2282 = 1343; + public static final int starCMa2618 = 1344; + public static final int starCMa2657 = 1345; + public static final int starCMa2646 = 1346; + public static final int starUMa4905 = 1347; + public static final int starUMa4301 = 1348; + public static final int starUMa5191 = 1349; + public static final int starUMa5054 = 1350; + public static final int starUMa4295 = 1351; + public static final int starUMa4660 = 1352; + public static final int starUMa4554 = 1353; + public static final int starUMa4069 = 1354; + public static final int starUMa3569 = 1355; + public static final int starUMa3323 = 1356; + public static final int starUMa4033 = 1357; + public static final int starUMa4377 = 1358; + public static final int starUMa4375 = 1359; + public static final int starUMa4518 = 1360; + public static final int starUMa3594 = 1361; + public static final int starVir5056 = 1362; + public static final int starVir4825 = 1363; + public static final int starVir4932 = 1364; + public static final int starVir4540 = 1365; + public static final int starVir4689 = 1366; + public static final int starVir5338 = 1367; + public static final int starVir4910 = 1368; + public static final int starVir5315 = 1369; + public static final int starVir5359 = 1370; + public static final int starVir5409 = 1371; + public static final int starVir5107 = 1372; + public static final int starAri617 = 1373; + public static final int starAri553 = 1374; + public static final int starAri546 = 1375; + public static final int starAri951 = 1376; + public static final int starOri1713 = 1377; + public static final int starOri2061 = 1378; + public static final int starOri1790 = 1379; + public static final int starOri1903 = 1380; + public static final int starOri1948 = 1381; + public static final int starOri2004 = 1382; + public static final int starOri1852 = 1383; + public static final int starOri1879 = 1384; + public static final int starOri1899 = 1385; + public static final int starOri1543 = 1386; + public static final int starCas21 = 1387; + public static final int starCas168 = 1388; + public static final int starCas403 = 1389; + public static final int starCas153 = 1390; + public static final int starCas542 = 1391; + public static final int starCas219 = 1392; + public static final int starCas265 = 1393; + public static final int starCnc3572 = 1394; + public static final int starCnc3208 = 1395; + public static final int starCnc3461 = 1396; + public static final int starCnc3449 = 1397; + public static final int starCnc3429 = 1398; + public static final int starCnc3627 = 1399; + public static final int starCnc3268 = 1400; + public static final int starCnc3249 = 1401; + public static final int starCom4968 = 1402; + public static final int starCrv4757 = 1403; + public static final int starCrv4623 = 1404; + public static final int starCrv4662 = 1405; + public static final int starCrv4786 = 1406; + public static final int starAur1708 = 1407; + public static final int starAur2088 = 1408; + public static final int starAur1605 = 1409; + public static final int starAur2095 = 1410; + public static final int starAur1577 = 1411; + public static final int starAur1641 = 1412; + public static final int starAur1612 = 1413; + public static final int starPav7790 = 1414; + public static final int starCet911 = 1415; + public static final int starCet681 = 1416; + public static final int starCet188 = 1417; + public static final int starCet539 = 1418; + public static final int starCet804 = 1419; + public static final int starCep8974 = 1420; + public static final int starCep8162 = 1421; + public static final int starCep8238 = 1422; + public static final int starCep8417 = 1423; + public static final int starCen5267 = 1424; + public static final int starCen5288 = 1425; + public static final int starCen551 = 1426; + public static final int starCen5459 = 1427; + public static final int starCen5460 = 1428; + public static final int starCMi2943 = 1429; + public static final int starCMi2845 = 1430; + public static final int starEqu8131 = 1431; + public static final int starVul7405 = 1432; + public static final int starUMi424 = 1433; + public static final int starUMi5563 = 1434; + public static final int starUMi5735 = 1435; + public static final int starUMi6789 = 1436; + public static final int starCrt4287 = 1437; + public static final int starLyr7001 = 1438; + public static final int starLyr7178 = 1439; + public static final int starLyr7106 = 1440; + public static final int starLyr7298 = 1441; + public static final int starAra6585 = 1442; + public static final int starSco6134 = 1443; + public static final int starSco6527 = 1444; + public static final int starSco6553 = 1445; + public static final int starSco5953 = 1446; + public static final int starSco5984 = 1447; + public static final int starSco6508 = 1448; + public static final int starSco6084 = 1449; + public static final int starSco5944 = 1450; + public static final int starSco6630 = 1451; + public static final int starSco6027 = 1452; + public static final int starSco6247 = 1453; + public static final int starSco6252 = 1454; + public static final int starSco5928 = 1455; + public static final int starSco6241 = 1456; + public static final int starSco6165 = 1457; + public static final int starTri544 = 1458; + public static final int starLeo3982 = 1459; + public static final int starLeo4534 = 1460; + public static final int starLeo4357 = 1461; + public static final int starLeo4057 = 1462; + public static final int starLeo4359 = 1463; + public static final int starLeo4031 = 1464; + public static final int starLeo3852 = 1465; + public static final int starLeo3905 = 1466; + public static final int starLeo3773 = 1467; + public static final int starGru8425 = 1468; + public static final int starGru8636 = 1469; + public static final int starGru8353 = 1470; + public static final int starLib5685 = 1471; + public static final int starLib5531 = 1472; + public static final int starLib5787 = 1473; + public static final int starLib5603 = 1474; + public static final int starPup3165 = 1475; + public static final int starPup3185 = 1476; + public static final int starPup3045 = 1477; + public static final int starCyg7924 = 1478; + public static final int starCyg7417 = 1479; + public static final int starCyg7796 = 1480; + public static final int starCyg8301 = 1481; + public static final int starCyg7949 = 1482; + public static final int starCyg7528 = 1483; + public static final int starOct7228 = 1484; + public static final int starCol1956 = 1485; + public static final int starCol2040 = 1486; + public static final int starCol2177 = 1487; + public static final int starGem2990 = 1488; + public static final int starGem2891 = 1489; + public static final int starGem2421 = 1490; + public static final int starGem2473 = 1491; + public static final int starGem2216 = 1492; + public static final int starGem2777 = 1493; + public static final int starGem2650 = 1494; + public static final int starGem2286 = 1495; + public static final int starGem2484 = 1496; + public static final int starGem2930 = 1497; + public static final int starPeg8775 = 1498; + public static final int starPeg8781 = 1499; + public static final int starPeg39 = 1500; + public static final int starPeg8308 = 1501; + public static final int starPeg8650 = 1502; + public static final int starPeg8634 = 1503; + public static final int starPeg8684 = 1504; + public static final int starPeg8450 = 1505; + public static final int starPeg8880 = 1506; + public static final int starPeg8905 = 1507; + public static final int starOph6556 = 1508; + public static final int starOph6378 = 1509; + public static final int starOph6603 = 1510; + public static final int starOph6149 = 1511; + public static final int starOph6056 = 1512; + public static final int starOph6075 = 1513; + public static final int starSer5854 = 1514; + public static final int starSer7141 = 1515; + public static final int starSer5879 = 1516; + public static final int starHer6406 = 1517; + public static final int starHer6148 = 1518; + public static final int starHer6410 = 1519; + public static final int starHer6526 = 1520; + public static final int starHer6117 = 1521; + public static final int starHer6008 = 1522; + public static final int starPer936 = 1523; + public static final int starPer1017 = 1524; + public static final int starPer1131 = 1525; + public static final int starPer1228 = 1526; + public static final int starPer834 = 1527; + public static final int starPer941 = 1528; + public static final int starPhe99 = 1529; + public static final int starPhe338 = 1530; + public static final int starVel3634 = 1531; + public static final int starVel3485 = 1532; + public static final int starVel3734 = 1533; + public static final int starAqr8232 = 1534; + public static final int starAqr8414 = 1535; + public static final int starAqr8709 = 1536; + public static final int starAqr8518 = 1537; + public static final int starAqr7950 = 1538; + public static final int starAqr8499 = 1539; + public static final int starAqr8610 = 1540; + public static final int starAqr8264 = 1541; + public static final int starCru4853 = 1542; + public static final int starCru4730 = 1543; + public static final int starCru4763 = 1544; + public static final int starCru4700 = 1545; + public static final int starCru4656 = 1546; + public static final int starPsA8728 = 1547; + public static final int starTrA6217 = 1548; + public static final int starCap7776 = 1549; + public static final int starCap7754 = 1550; + public static final int starCap8278 = 1551; + public static final int starCap8322 = 1552; + public static final int starCap7773 = 1553; + public static final int starSge7479 = 1554; + public static final int starCar2326 = 1555; + public static final int starCar3685 = 1556; + public static final int starCar3307 = 1557; + public static final int starCar3699 = 1558; + public static final int starDra5744 = 1559; + public static final int starDra5291 = 1560; + public static final int starDra6705 = 1561; + public static final int starDra6536 = 1562; + public static final int starDra7310 = 1563; + public static final int starDra6688 = 1564; + public static final int starDra4434 = 1565; + public static final int starDra6370 = 1566; + public static final int starDra7462 = 1567; + public static final int starDra6396 = 1568; + public static final int starDra6132 = 1569; + public static final int starDra6636 = 1570; + public static final int starCVn4915 = 1571; + public static final int starCVn4785 = 1572; + public static final int starCVn4846 = 1573; + public static final int starAql7595 = 1574; + public static final int starAql7557 = 1575; + public static final int starAql7525 = 1576; + public static final int starAql7602 = 1577; + public static final int starAql7235 = 1578; + + // These items are generally available in Sword/Shield v1.2.0 and onwards. + public static final int maxHoney = 1579; + public static final int maxMushrooms = 1580; + public static final int galaricaTwig = 1581; + public static final int galaricaCuff = 1582; + public static final int styleCard = 1583; + public static final int armorPass = 1584; + public static final int rotomBikeSparkingWhite = 1585; + public static final int rotomBikeGlisteningBlack = 1586; + public static final int expCharm = 1587; + public static final int armoriteOre = 1588; + public static final int markCharm = 1589; + + // These items are generally available in Sword/Shield v1.3.0 and onwards. + public static final int reinsofUnityFuse = 1590; + public static final int reinsofUnitySeparate = 1591; + public static final int galaricaWreath = 1592; + public static final int legendaryClue1 = 1593; + public static final int legendaryClue2 = 1594; + public static final int legendaryClue3 = 1595; + public static final int legendaryClueQuestionMark = 1596; + public static final int crownPass = 1597; + public static final int woodenCrown = 1598; + public static final int radiantPetal = 1599; + public static final int whiteManeHair = 1600; + public static final int blackManeHair = 1601; + public static final int icerootCarrot = 1602; + public static final int shaderootCarrot = 1603; + public static final int dyniteOre = 1604; + public static final int carrotSeeds = 1605; + public static final int abilityPatch = 1606; + public static final int reinsofUnity = 1607; +} diff --git a/src/com/pkrandom/constants/Moves.java b/src/com/pkrandom/constants/Moves.java new file mode 100644 index 0000000..494e8f2 --- /dev/null +++ b/src/com/pkrandom/constants/Moves.java @@ -0,0 +1,854 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Moves.java - defines an index number constant for every Move. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class Moves { + // https://bulbapedia.bulbagarden.net/wiki/List_of_moves + public static final int pound = 1; + public static final int karateChop = 2; + public static final int doubleSlap = 3; + public static final int cometPunch = 4; + public static final int megaPunch = 5; + public static final int payDay = 6; + public static final int firePunch = 7; + public static final int icePunch = 8; + public static final int thunderPunch = 9; + public static final int scratch = 10; + public static final int viseGrip = 11; + public static final int guillotine = 12; + public static final int razorWind = 13; + public static final int swordsDance = 14; + public static final int cut = 15; + public static final int gust = 16; + public static final int wingAttack = 17; + public static final int whirlwind = 18; + public static final int fly = 19; + public static final int bind = 20; + public static final int slam = 21; + public static final int vineWhip = 22; + public static final int stomp = 23; + public static final int doubleKick = 24; + public static final int megaKick = 25; + public static final int jumpKick = 26; + public static final int rollingKick = 27; + public static final int sandAttack = 28; + public static final int headbutt = 29; + public static final int hornAttack = 30; + public static final int furyAttack = 31; + public static final int hornDrill = 32; + public static final int tackle = 33; + public static final int bodySlam = 34; + public static final int wrap = 35; + public static final int takeDown = 36; + public static final int thrash = 37; + public static final int doubleEdge = 38; + public static final int tailWhip = 39; + public static final int poisonSting = 40; + public static final int twineedle = 41; + public static final int pinMissile = 42; + public static final int leer = 43; + public static final int bite = 44; + public static final int growl = 45; + public static final int roar = 46; + public static final int sing = 47; + public static final int supersonic = 48; + public static final int sonicBoom = 49; + public static final int disable = 50; + public static final int acid = 51; + public static final int ember = 52; + public static final int flamethrower = 53; + public static final int mist = 54; + public static final int waterGun = 55; + public static final int hydroPump = 56; + public static final int surf = 57; + public static final int iceBeam = 58; + public static final int blizzard = 59; + public static final int psybeam = 60; + public static final int bubbleBeam = 61; + public static final int auroraBeam = 62; + public static final int hyperBeam = 63; + public static final int peck = 64; + public static final int drillPeck = 65; + public static final int submission = 66; + public static final int lowKick = 67; + public static final int counter = 68; + public static final int seismicToss = 69; + public static final int strength = 70; + public static final int absorb = 71; + public static final int megaDrain = 72; + public static final int leechSeed = 73; + public static final int growth = 74; + public static final int razorLeaf = 75; + public static final int solarBeam = 76; + public static final int poisonPowder = 77; + public static final int stunSpore = 78; + public static final int sleepPowder = 79; + public static final int petalDance = 80; + public static final int stringShot = 81; + public static final int dragonRage = 82; + public static final int fireSpin = 83; + public static final int thunderShock = 84; + public static final int thunderbolt = 85; + public static final int thunderWave = 86; + public static final int thunder = 87; + public static final int rockThrow = 88; + public static final int earthquake = 89; + public static final int fissure = 90; + public static final int dig = 91; + public static final int toxic = 92; + public static final int confusion = 93; + public static final int psychic = 94; + public static final int hypnosis = 95; + public static final int meditate = 96; + public static final int agility = 97; + public static final int quickAttack = 98; + public static final int rage = 99; + public static final int teleport = 100; + public static final int nightShade = 101; + public static final int mimic = 102; + public static final int screech = 103; + public static final int doubleTeam = 104; + public static final int recover = 105; + public static final int harden = 106; + public static final int minimize = 107; + public static final int smokescreen = 108; + public static final int confuseRay = 109; + public static final int withdraw = 110; + public static final int defenseCurl = 111; + public static final int barrier = 112; + public static final int lightScreen = 113; + public static final int haze = 114; + public static final int reflect = 115; + public static final int focusEnergy = 116; + public static final int bide = 117; + public static final int metronome = 118; + public static final int mirrorMove = 119; + public static final int selfDestruct = 120; + public static final int eggBomb = 121; + public static final int lick = 122; + public static final int smog = 123; + public static final int sludge = 124; + public static final int boneClub = 125; + public static final int fireBlast = 126; + public static final int waterfall = 127; + public static final int clamp = 128; + public static final int swift = 129; + public static final int skullBash = 130; + public static final int spikeCannon = 131; + public static final int constrict = 132; + public static final int amnesia = 133; + public static final int kinesis = 134; + public static final int softBoiled = 135; + public static final int highJumpKick = 136; + public static final int glare = 137; + public static final int dreamEater = 138; + public static final int poisonGas = 139; + public static final int barrage = 140; + public static final int leechLife = 141; + public static final int lovelyKiss = 142; + public static final int skyAttack = 143; + public static final int transform = 144; + public static final int bubble = 145; + public static final int dizzyPunch = 146; + public static final int spore = 147; + public static final int flash = 148; + public static final int psywave = 149; + public static final int splash = 150; + public static final int acidArmor = 151; + public static final int crabhammer = 152; + public static final int explosion = 153; + public static final int furySwipes = 154; + public static final int bonemerang = 155; + public static final int rest = 156; + public static final int rockSlide = 157; + public static final int hyperFang = 158; + public static final int sharpen = 159; + public static final int conversion = 160; + public static final int triAttack = 161; + public static final int superFang = 162; + public static final int slash = 163; + public static final int substitute = 164; + public static final int struggle = 165; + public static final int sketch = 166; + public static final int tripleKick = 167; + public static final int thief = 168; + public static final int spiderWeb = 169; + public static final int mindReader = 170; + public static final int nightmare = 171; + public static final int flameWheel = 172; + public static final int snore = 173; + public static final int curse = 174; + public static final int flail = 175; + public static final int conversion2 = 176; + public static final int aeroblast = 177; + public static final int cottonSpore = 178; + public static final int reversal = 179; + public static final int spite = 180; + public static final int powderSnow = 181; + public static final int protect = 182; + public static final int machPunch = 183; + public static final int scaryFace = 184; + public static final int feintAttack = 185; + public static final int sweetKiss = 186; + public static final int bellyDrum = 187; + public static final int sludgeBomb = 188; + public static final int mudSlap = 189; + public static final int octazooka = 190; + public static final int spikes = 191; + public static final int zapCannon = 192; + public static final int foresight = 193; + public static final int destinyBond = 194; + public static final int perishSong = 195; + public static final int icyWind = 196; + public static final int detect = 197; + public static final int boneRush = 198; + public static final int lockOn = 199; + public static final int outrage = 200; + public static final int sandstorm = 201; + public static final int gigaDrain = 202; + public static final int endure = 203; + public static final int charm = 204; + public static final int rollout = 205; + public static final int falseSwipe = 206; + public static final int swagger = 207; + public static final int milkDrink = 208; + public static final int spark = 209; + public static final int furyCutter = 210; + public static final int steelWing = 211; + public static final int meanLook = 212; + public static final int attract = 213; + public static final int sleepTalk = 214; + public static final int healBell = 215; + public static final int returnTheMoveNotTheKeyword = 216; + public static final int present = 217; + public static final int frustration = 218; + public static final int safeguard = 219; + public static final int painSplit = 220; + public static final int sacredFire = 221; + public static final int magnitude = 222; + public static final int dynamicPunch = 223; + public static final int megahorn = 224; + public static final int dragonBreath = 225; + public static final int batonPass = 226; + public static final int encore = 227; + public static final int pursuit = 228; + public static final int rapidSpin = 229; + public static final int sweetScent = 230; + public static final int ironTail = 231; + public static final int metalClaw = 232; + public static final int vitalThrow = 233; + public static final int morningSun = 234; + public static final int synthesis = 235; + public static final int moonlight = 236; + public static final int hiddenPower = 237; + public static final int crossChop = 238; + public static final int twister = 239; + public static final int rainDance = 240; + public static final int sunnyDay = 241; + public static final int crunch = 242; + public static final int mirrorCoat = 243; + public static final int psychUp = 244; + public static final int extremeSpeed = 245; + public static final int ancientPower = 246; + public static final int shadowBall = 247; + public static final int futureSight = 248; + public static final int rockSmash = 249; + public static final int whirlpool = 250; + public static final int beatUp = 251; + public static final int fakeOut = 252; + public static final int uproar = 253; + public static final int stockpile = 254; + public static final int spitUp = 255; + public static final int swallow = 256; + public static final int heatWave = 257; + public static final int hail = 258; + public static final int torment = 259; + public static final int flatter = 260; + public static final int willOWisp = 261; + public static final int memento = 262; + public static final int facade = 263; + public static final int focusPunch = 264; + public static final int smellingSalts = 265; + public static final int followMe = 266; + public static final int naturePower = 267; + public static final int charge = 268; + public static final int taunt = 269; + public static final int helpingHand = 270; + public static final int trick = 271; + public static final int rolePlay = 272; + public static final int wish = 273; + public static final int assist = 274; + public static final int ingrain = 275; + public static final int superpower = 276; + public static final int magicCoat = 277; + public static final int recycle = 278; + public static final int revenge = 279; + public static final int brickBreak = 280; + public static final int yawn = 281; + public static final int knockOff = 282; + public static final int endeavor = 283; + public static final int eruption = 284; + public static final int skillSwap = 285; + public static final int imprison = 286; + public static final int refresh = 287; + public static final int grudge = 288; + public static final int snatch = 289; + public static final int secretPower = 290; + public static final int dive = 291; + public static final int armThrust = 292; + public static final int camouflage = 293; + public static final int tailGlow = 294; + public static final int lusterPurge = 295; + public static final int mistBall = 296; + public static final int featherDance = 297; + public static final int teeterDance = 298; + public static final int blazeKick = 299; + public static final int mudSport = 300; + public static final int iceBall = 301; + public static final int needleArm = 302; + public static final int slackOff = 303; + public static final int hyperVoice = 304; + public static final int poisonFang = 305; + public static final int crushClaw = 306; + public static final int blastBurn = 307; + public static final int hydroCannon = 308; + public static final int meteorMash = 309; + public static final int astonish = 310; + public static final int weatherBall = 311; + public static final int aromatherapy = 312; + public static final int fakeTears = 313; + public static final int airCutter = 314; + public static final int overheat = 315; + public static final int odorSleuth = 316; + public static final int rockTomb = 317; + public static final int silverWind = 318; + public static final int metalSound = 319; + public static final int grassWhistle = 320; + public static final int tickle = 321; + public static final int cosmicPower = 322; + public static final int waterSpout = 323; + public static final int signalBeam = 324; + public static final int shadowPunch = 325; + public static final int extrasensory = 326; + public static final int skyUppercut = 327; + public static final int sandTomb = 328; + public static final int sheerCold = 329; + public static final int muddyWater = 330; + public static final int bulletSeed = 331; + public static final int aerialAce = 332; + public static final int icicleSpear = 333; + public static final int ironDefense = 334; + public static final int block = 335; + public static final int howl = 336; + public static final int dragonClaw = 337; + public static final int frenzyPlant = 338; + public static final int bulkUp = 339; + public static final int bounce = 340; + public static final int mudShot = 341; + public static final int poisonTail = 342; + public static final int covet = 343; + public static final int voltTackle = 344; + public static final int magicalLeaf = 345; + public static final int waterSport = 346; + public static final int calmMind = 347; + public static final int leafBlade = 348; + public static final int dragonDance = 349; + public static final int rockBlast = 350; + public static final int shockWave = 351; + public static final int waterPulse = 352; + public static final int doomDesire = 353; + public static final int psychoBoost = 354; + public static final int roost = 355; + public static final int gravity = 356; + public static final int miracleEye = 357; + public static final int wakeUpSlap = 358; + public static final int hammerArm = 359; + public static final int gyroBall = 360; + public static final int healingWish = 361; + public static final int brine = 362; + public static final int naturalGift = 363; + public static final int feint = 364; + public static final int pluck = 365; + public static final int tailwind = 366; + public static final int acupressure = 367; + public static final int metalBurst = 368; + public static final int uTurn = 369; + public static final int closeCombat = 370; + public static final int payback = 371; + public static final int assurance = 372; + public static final int embargo = 373; + public static final int fling = 374; + public static final int psychoShift = 375; + public static final int trumpCard = 376; + public static final int healBlock = 377; + public static final int wringOut = 378; + public static final int powerTrick = 379; + public static final int gastroAcid = 380; + public static final int luckyChant = 381; + public static final int meFirst = 382; + public static final int copycat = 383; + public static final int powerSwap = 384; + public static final int guardSwap = 385; + public static final int punishment = 386; + public static final int lastResort = 387; + public static final int worrySeed = 388; + public static final int suckerPunch = 389; + public static final int toxicSpikes = 390; + public static final int heartSwap = 391; + public static final int aquaRing = 392; + public static final int magnetRise = 393; + public static final int flareBlitz = 394; + public static final int forcePalm = 395; + public static final int auraSphere = 396; + public static final int rockPolish = 397; + public static final int poisonJab = 398; + public static final int darkPulse = 399; + public static final int nightSlash = 400; + public static final int aquaTail = 401; + public static final int seedBomb = 402; + public static final int airSlash = 403; + public static final int xScissor = 404; + public static final int bugBuzz = 405; + public static final int dragonPulse = 406; + public static final int dragonRush = 407; + public static final int powerGem = 408; + public static final int drainPunch = 409; + public static final int vacuumWave = 410; + public static final int focusBlast = 411; + public static final int energyBall = 412; + public static final int braveBird = 413; + public static final int earthPower = 414; + public static final int switcheroo = 415; + public static final int gigaImpact = 416; + public static final int nastyPlot = 417; + public static final int bulletPunch = 418; + public static final int avalanche = 419; + public static final int iceShard = 420; + public static final int shadowClaw = 421; + public static final int thunderFang = 422; + public static final int iceFang = 423; + public static final int fireFang = 424; + public static final int shadowSneak = 425; + public static final int mudBomb = 426; + public static final int psychoCut = 427; + public static final int zenHeadbutt = 428; + public static final int mirrorShot = 429; + public static final int flashCannon = 430; + public static final int rockClimb = 431; + public static final int defog = 432; + public static final int trickRoom = 433; + public static final int dracoMeteor = 434; + public static final int discharge = 435; + public static final int lavaPlume = 436; + public static final int leafStorm = 437; + public static final int powerWhip = 438; + public static final int rockWrecker = 439; + public static final int crossPoison = 440; + public static final int gunkShot = 441; + public static final int ironHead = 442; + public static final int magnetBomb = 443; + public static final int stoneEdge = 444; + public static final int captivate = 445; + public static final int stealthRock = 446; + public static final int grassKnot = 447; + public static final int chatter = 448; + public static final int judgment = 449; + public static final int bugBite = 450; + public static final int chargeBeam = 451; + public static final int woodHammer = 452; + public static final int aquaJet = 453; + public static final int attackOrder = 454; + public static final int defendOrder = 455; + public static final int healOrder = 456; + public static final int headSmash = 457; + public static final int doubleHit = 458; + public static final int roarOfTime = 459; + public static final int spacialRend = 460; + public static final int lunarDance = 461; + public static final int crushGrip = 462; + public static final int magmaStorm = 463; + public static final int darkVoid = 464; + public static final int seedFlare = 465; + public static final int ominousWind = 466; + public static final int shadowForce = 467; + public static final int honeClaws = 468; + public static final int wideGuard = 469; + public static final int guardSplit = 470; + public static final int powerSplit = 471; + public static final int wonderRoom = 472; + public static final int psyshock = 473; + public static final int venoshock = 474; + public static final int autotomize = 475; + public static final int ragePowder = 476; + public static final int telekinesis = 477; + public static final int magicRoom = 478; + public static final int smackDown = 479; + public static final int stormThrow = 480; + public static final int flameBurst = 481; + public static final int sludgeWave = 482; + public static final int quiverDance = 483; + public static final int heavySlam = 484; + public static final int synchronoise = 485; + public static final int electroBall = 486; + public static final int soak = 487; + public static final int flameCharge = 488; + public static final int coil = 489; + public static final int lowSweep = 490; + public static final int acidSpray = 491; + public static final int foulPlay = 492; + public static final int simpleBeam = 493; + public static final int entrainment = 494; + public static final int afterYou = 495; + public static final int round = 496; + public static final int echoedVoice = 497; + public static final int chipAway = 498; + public static final int clearSmog = 499; + public static final int storedPower = 500; + public static final int quickGuard = 501; + public static final int allySwitch = 502; + public static final int scald = 503; + public static final int shellSmash = 504; + public static final int healPulse = 505; + public static final int hex = 506; + public static final int skyDrop = 507; + public static final int shiftGear = 508; + public static final int circleThrow = 509; + public static final int incinerate = 510; + public static final int quash = 511; + public static final int acrobatics = 512; + public static final int reflectType = 513; + public static final int retaliate = 514; + public static final int finalGambit = 515; + public static final int bestow = 516; + public static final int inferno = 517; + public static final int waterPledge = 518; + public static final int firePledge = 519; + public static final int grassPledge = 520; + public static final int voltSwitch = 521; + public static final int struggleBug = 522; + public static final int bulldoze = 523; + public static final int frostBreath = 524; + public static final int dragonTail = 525; + public static final int workUp = 526; + public static final int electroweb = 527; + public static final int wildCharge = 528; + public static final int drillRun = 529; + public static final int dualChop = 530; + public static final int heartStamp = 531; + public static final int hornLeech = 532; + public static final int sacredSword = 533; + public static final int razorShell = 534; + public static final int heatCrash = 535; + public static final int leafTornado = 536; + public static final int steamroller = 537; + public static final int cottonGuard = 538; + public static final int nightDaze = 539; + public static final int psystrike = 540; + public static final int tailSlap = 541; + public static final int hurricane = 542; + public static final int headCharge = 543; + public static final int gearGrind = 544; + public static final int searingShot = 545; + public static final int technoBlast = 546; + public static final int relicSong = 547; + public static final int secretSword = 548; + public static final int glaciate = 549; + public static final int boltStrike = 550; + public static final int blueFlare = 551; + public static final int fieryDance = 552; + public static final int freezeShock = 553; + public static final int iceBurn = 554; + public static final int snarl = 555; + public static final int icicleCrash = 556; + public static final int vCreate = 557; + public static final int fusionFlare = 558; + public static final int fusionBolt = 559; + public static final int flyingPress = 560; + public static final int matBlock = 561; + public static final int belch = 562; + public static final int rototiller = 563; + public static final int stickyWeb = 564; + public static final int fellStinger = 565; + public static final int phantomForce = 566; + public static final int trickOrTreat = 567; + public static final int nobleRoar = 568; + public static final int ionDeluge = 569; + public static final int parabolicCharge = 570; + public static final int forestsCurse = 571; + public static final int petalBlizzard = 572; + public static final int freezeDry = 573; + public static final int disarmingVoice = 574; + public static final int partingShot = 575; + public static final int topsyTurvy = 576; + public static final int drainingKiss = 577; + public static final int craftyShield = 578; + public static final int flowerShield = 579; + public static final int grassyTerrain = 580; + public static final int mistyTerrain = 581; + public static final int electrify = 582; + public static final int playRough = 583; + public static final int fairyWind = 584; + public static final int moonblast = 585; + public static final int boomburst = 586; + public static final int fairyLock = 587; + public static final int kingsShield = 588; + public static final int playNice = 589; + public static final int confide = 590; + public static final int diamondStorm = 591; + public static final int steamEruption = 592; + public static final int hyperspaceHole = 593; + public static final int waterShuriken = 594; + public static final int mysticalFire = 595; + public static final int spikyShield = 596; + public static final int aromaticMist = 597; + public static final int eerieImpulse = 598; + public static final int venomDrench = 599; + public static final int powder = 600; + public static final int geomancy = 601; + public static final int magneticFlux = 602; + public static final int happyHour = 603; + public static final int electricTerrain = 604; + public static final int dazzlingGleam = 605; + public static final int celebrate = 606; + public static final int holdHands = 607; + public static final int babyDollEyes = 608; + public static final int nuzzle = 609; + public static final int holdBack = 610; + public static final int infestation = 611; + public static final int powerUpPunch = 612; + public static final int oblivionWing = 613; + public static final int thousandArrows = 614; + public static final int thousandWaves = 615; + public static final int landsWrath = 616; + public static final int lightOfRuin = 617; + public static final int originPulse = 618; + public static final int precipiceBlades = 619; + public static final int dragonAscent = 620; + public static final int hyperspaceFury = 621; + public static final int breakneckBlitzPhysical = 622; + public static final int breakneckBlitzSpecial = 623; + public static final int allOutPummelingPhysical = 624; + public static final int allOutPummelingSpecial = 625; + public static final int supersonicSkystrikePhysical = 626; + public static final int supersonicSkystrikeSpecial = 627; + public static final int acidDownpourPhysical = 628; + public static final int acidDownpourSpecial = 629; + public static final int tectonicRagePhysical = 630; + public static final int tectonicRageSpecial = 631; + public static final int continentalCrushPhysical = 632; + public static final int continentalCrushSpecial = 633; + public static final int savageSpinOutPhysical = 634; + public static final int savageSpinOutSpecial = 635; + public static final int neverEndingNightmarePhysical = 636; + public static final int neverEndingNightmareSpecial = 637; + public static final int corkscrewCrashPhysical = 638; + public static final int corkscrewCrashSpecial = 639; + public static final int infernoOverdrivePhysical = 640; + public static final int infernoOverdriveSpecial = 641; + public static final int hydroVortexPhysical = 642; + public static final int hydroVortexSpecial = 643; + public static final int bloomDoomPhysical = 644; + public static final int bloomDoomSpecial = 645; + public static final int gigavoltHavocPhysical = 646; + public static final int gigavoltHavocSpecial = 647; + public static final int shatteredPsychePhysical = 648; + public static final int shatteredPsycheSpecial = 649; + public static final int subzeroSlammerPhysical = 650; + public static final int subzeroSlammerSpecial = 651; + public static final int devastatingDrakePhysical = 652; + public static final int devastatingDrakeSpecial = 653; + public static final int blackHoleEclipsePhysical = 654; + public static final int blackHoleEclipseSpecial = 655; + public static final int twinkleTacklePhysical = 656; + public static final int twinkleTackleSpecial = 657; + public static final int catastropika = 658; + public static final int shoreUp = 659; + public static final int firstImpression = 660; + public static final int banefulBunker = 661; + public static final int spiritShackle = 662; + public static final int darkestLariat = 663; + public static final int sparklingAria = 664; + public static final int iceHammer = 665; + public static final int floralHealing = 666; + public static final int highHorsepower = 667; + public static final int strengthSap = 668; + public static final int solarBlade = 669; + public static final int leafage = 670; + public static final int spotlight = 671; + public static final int toxicThread = 672; + public static final int laserFocus = 673; + public static final int gearUp = 674; + public static final int throatChop = 675; + public static final int pollenPuff = 676; + public static final int anchorShot = 677; + public static final int psychicTerrain = 678; + public static final int lunge = 679; + public static final int fireLash = 680; + public static final int powerTrip = 681; + public static final int burnUp = 682; + public static final int speedSwap = 683; + public static final int smartStrike = 684; + public static final int purify = 685; + public static final int revelationDance = 686; + public static final int coreEnforcer = 687; + public static final int tropKick = 688; + public static final int instruct = 689; + public static final int beakBlast = 690; + public static final int clangingScales = 691; + public static final int dragonHammer = 692; + public static final int brutalSwing = 693; + public static final int auroraVeil = 694; + public static final int sinisterArrowRaid = 695; + public static final int maliciousMoonsault = 696; + public static final int oceanicOperetta = 697; + public static final int guardianOfAlola = 698; + public static final int soulStealing7StarStrike = 699; + public static final int stokedSparksurfer = 700; + public static final int pulverizingPancake = 701; + public static final int extremeEvoboost = 702; + public static final int genesisSupernova = 703; + public static final int shellTrap = 704; + public static final int fleurCannon = 705; + public static final int psychicFangs = 706; + public static final int stompingTantrum = 707; + public static final int shadowBone = 708; + public static final int accelerock = 709; + public static final int liquidation = 710; + public static final int prismaticLaser = 711; + public static final int spectralThief = 712; + public static final int sunsteelStrike = 713; + public static final int moongeistBeam = 714; + public static final int tearfulLook = 715; + public static final int zingZap = 716; + public static final int naturesMadness = 717; + public static final int multiAttack = 718; + public static final int tenMillionVoltThunderbolt = 719; + public static final int mindBlown = 720; + public static final int plasmaFists = 721; + public static final int photonGeyser = 722; + public static final int lightThatBurnsTheSky = 723; + public static final int searingSunrazeSmash = 724; + public static final int menacingMoonrazeMaelstrom = 725; + public static final int letsSnuggleForever = 726; + public static final int splinteredStormshards = 727; + public static final int clangorousSoulblaze = 728; + public static final int zippyZap = 729; + public static final int splishySplash = 730; + public static final int floatyFall = 731; + public static final int pikaPapow = 732; + public static final int bouncyBubble = 733; + public static final int buzzyBuzz = 734; + public static final int sizzlySlide = 735; + public static final int glitzyGlow = 736; + public static final int baddyBad = 737; + public static final int sappySeed = 738; + public static final int freezyFrost = 739; + public static final int sparklySwirl = 740; + public static final int veeveeVolley = 741; + public static final int doubleIronBash = 742; + public static final int maxGuard = 743; + public static final int dynamaxCannon = 744; + public static final int snipeShot = 745; + public static final int jawLock = 746; + public static final int stuffCheeks = 747; + public static final int noRetreat = 748; + public static final int tarShot = 749; + public static final int magicPowder = 750; + public static final int dragonDarts = 751; + public static final int teatime = 752; + public static final int octolock = 753; + public static final int boltBeak = 754; + public static final int fishiousRend = 755; + public static final int courtChange = 756; + public static final int maxFlare = 757; + public static final int maxFlutterby = 758; + public static final int maxLightning = 759; + public static final int maxStrike = 760; + public static final int maxKnuckle = 761; + public static final int maxPhantasm = 762; + public static final int maxHailstorm = 763; + public static final int maxOoze = 764; + public static final int maxGeyser = 765; + public static final int maxAirstream = 766; + public static final int maxStarfall = 767; + public static final int maxWyrmwind = 768; + public static final int maxMindstorm = 769; + public static final int maxRockfall = 770; + public static final int maxQuake = 771; + public static final int maxDarkness = 772; + public static final int maxOvergrowth = 773; + public static final int maxSteelspike = 774; + public static final int clangorousSoul = 775; + public static final int bodyPress = 776; + public static final int decorate = 777; + public static final int drumBeating = 778; + public static final int snapTrap = 779; + public static final int pyroBall = 780; + public static final int behemothBlade = 781; + public static final int behemothBash = 782; + public static final int auraWheel = 783; + public static final int breakingSwipe = 784; + public static final int branchPoke = 785; + public static final int overdrive = 786; + public static final int appleAcid = 787; + public static final int gravApple = 788; + public static final int spiritBreak = 789; + public static final int strangeSteam = 790; + public static final int lifeDew = 791; + public static final int obstruct = 792; + public static final int falseSurrender = 793; + public static final int meteorAssault = 794; + public static final int eternabeam = 795; + public static final int steelBeam = 796; + public static final int expandingForce = 797; + public static final int steelRoller = 798; + public static final int scaleShot = 799; + public static final int meteorBeam = 800; + public static final int shellSideArm = 801; + public static final int mistyExplosion = 802; + public static final int grassyGlide = 803; + public static final int risingVoltage = 804; + public static final int terrainPulse = 805; + public static final int skitterSmack = 806; + public static final int burningJealousy = 807; + public static final int lashOut = 808; + public static final int poltergeist = 809; + public static final int corrosiveGas = 810; + public static final int coaching = 811; + public static final int flipTurn = 812; + public static final int tripleAxel = 813; + public static final int dualWingbeat = 814; + public static final int scorchingSands = 815; + public static final int jungleHealing = 816; + public static final int wickedBlow = 817; + public static final int surgingStrikes = 818; + public static final int thunderCage = 819; + public static final int dragonEnergy = 820; + public static final int freezingGlare = 821; + public static final int fieryWrath = 822; + public static final int thunderousKick = 823; + public static final int glacialLance = 824; + public static final int astralBarrage = 825; + public static final int eerieSpell = 826; +} \ No newline at end of file diff --git a/src/com/pkrandom/constants/N3DSConstants.java b/src/com/pkrandom/constants/N3DSConstants.java new file mode 100644 index 0000000..f33fe23 --- /dev/null +++ b/src/com/pkrandom/constants/N3DSConstants.java @@ -0,0 +1,212 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- N3DSConstants.java - constants that are relevant for all of the 3DS --*/ +/*-- games --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.HashMap; +import java.util.Map; + +public class N3DSConstants { + + public static final int Type_XY = 0, Type_ORAS = 1, Type_SM = 2, Type_USUM = 3; + + private static final Map textVariableCodesXY = setupTextVariableCodes(Type_XY); + private static final Map textVariableCodesORAS = setupTextVariableCodes(Type_ORAS); + private static final Map textVariableCodesSM = setupTextVariableCodes(Type_SM); + + public static Map getTextVariableCodes(int romType) { + if (romType == Type_XY) { + return textVariableCodesXY; + } else if (romType == Type_ORAS) { + return textVariableCodesORAS; + } else if (romType == Type_SM || romType == Type_USUM) { + return textVariableCodesSM; + } + return new HashMap<>(); + } + + private static Map setupTextVariableCodes(int romType) { + Map map = new HashMap<>(); + + if (romType == Type_XY) { + map.put(0xFF00, "COLOR"); + map.put(0x0100, "TRNAME"); + map.put(0x0101, "PKNAME"); + map.put(0x0102, "PKNICK"); + map.put(0x0103, "TYPE"); + map.put(0x0105, "LOCATION"); + map.put(0x0106, "ABILITY"); + map.put(0x0107, "MOVE"); + map.put(0x0108, "ITEM1"); + map.put(0x0109, "ITEM2"); + map.put(0x010A, "sTRBAG"); + map.put(0x010B, "BOX"); + map.put(0x010D, "EVSTAT"); + map.put(0x0110, "OPOWER"); + map.put(0x0127, "RIBBON"); + map.put(0x0134, "MIINAME"); + map.put(0x013E, "WEATHER"); + map.put(0x0189, "TRNICK"); + map.put(0x018A, "1stchrTR"); + map.put(0x018B, "SHOUTOUT"); + map.put(0x018E, "BERRY"); + map.put(0x018F, "REMFEEL"); + map.put(0x0190, "REMQUAL"); + map.put(0x0191, "WEBSITE"); + map.put(0x019C, "CHOICECOS"); + map.put(0x01A1, "GSYNCID"); + map.put(0x0192, "PRVIDSAY"); + map.put(0x0193, "BTLTEST"); + map.put(0x0195, "GENLOC"); + map.put(0x0199, "CHOICEFOOD"); + map.put(0x019A, "HOTELITEM"); + map.put(0x019B, "TAXISTOP"); + map.put(0x019F, "MAISTITLE"); + map.put(0x1000, "ITEMPLUR0"); + map.put(0x1001, "ITEMPLUR1"); + map.put(0x1100, "GENDBR"); + map.put(0x1101, "NUMBRNCH"); + map.put(0x1302, "iCOLOR2"); + map.put(0x1303, "iCOLOR3"); + map.put(0x0200, "NUM1"); + map.put(0x0201, "NUM2"); + map.put(0x0202, "NUM3"); + map.put(0x0203, "NUM4"); + map.put(0x0204, "NUM5"); + map.put(0x0205, "NUM6"); + map.put(0x0206, "NUM7"); + map.put(0x0207, "NUM8"); + map.put(0x0208, "NUM9"); + } else if (romType == Type_ORAS) { + map.put(0xFF00, "COLOR"); + map.put(0x0100, "TRNAME"); + map.put(0x0101, "PKNAME"); + map.put(0x0102, "PKNICK"); + map.put(0x0103, "TYPE"); + map.put(0x0105, "LOCATION"); + map.put(0x0106, "ABILITY"); + map.put(0x0107, "MOVE"); + map.put(0x0108, "ITEM1"); + map.put(0x0109, "ITEM2"); + map.put(0x010A, "sTRBAG"); + map.put(0x010B, "BOX"); + map.put(0x010D, "EVSTAT"); + map.put(0x0110, "OPOWER"); + map.put(0x0127, "RIBBON"); + map.put(0x0134, "MIINAME"); + map.put(0x013E, "WEATHER"); + map.put(0x0189, "TRNICK"); + map.put(0x018A, "1stchrTR"); + map.put(0x018B, "SHOUTOUT"); + map.put(0x018E, "BERRY"); + map.put(0x018F, "REMFEEL"); + map.put(0x0190, "REMQUAL"); + map.put(0x0191, "WEBSITE"); + map.put(0x019C, "CHOICECOS"); + map.put(0x01A1, "GSYNCID"); + map.put(0x0192, "PRVIDSAY"); + map.put(0x0193, "BTLTEST"); + map.put(0x0195, "GENLOC"); + map.put(0x0199, "CHOICEFOOD"); + map.put(0x019A, "HOTELITEM"); + map.put(0x019B, "TAXISTOP"); + map.put(0x019F, "MAISTITLE"); + map.put(0x1000, "ITEMPLUR0"); + map.put(0x1001, "ITEMPLUR1"); + map.put(0x1100, "GENDBR"); + map.put(0x1101, "NUMBRNCH"); + map.put(0x1302, "iCOLOR2"); + map.put(0x1303, "iCOLOR3"); + map.put(0x0200, "NUM1"); + map.put(0x0201, "NUM2"); + map.put(0x0202, "NUM3"); + map.put(0x0203, "NUM4"); + map.put(0x0204, "NUM5"); + map.put(0x0205, "NUM6"); + map.put(0x0206, "NUM7"); + map.put(0x0207, "NUM8"); + map.put(0x0208, "NUM9"); + } else if (romType == Type_SM) { + map.put(0xFF00, "COLOR"); + map.put(0x0100, "TRNAME"); + map.put(0x0101, "PKNAME"); + map.put(0x0102, "PKNICK"); + map.put(0x0103, "TYPE"); + map.put(0x0105, "LOCATION"); + map.put(0x0106, "ABILITY"); + map.put(0x0107, "MOVE"); + map.put(0x0108, "ITEM1"); + map.put(0x0109, "ITEM2"); + map.put(0x010A, "sTRBAG"); + map.put(0x010B, "BOX"); + map.put(0x010D, "EVSTAT"); + map.put(0x0110, "OPOWER"); + map.put(0x0127, "RIBBON"); + map.put(0x0134, "MIINAME"); + map.put(0x013E, "WEATHER"); + map.put(0x0189, "TRNICK"); + map.put(0x018A, "1stchrTR"); + map.put(0x018B, "SHOUTOUT"); + map.put(0x018E, "BERRY"); + map.put(0x018F, "REMFEEL"); + map.put(0x0190, "REMQUAL"); + map.put(0x0191, "WEBSITE"); + map.put(0x019C, "CHOICECOS"); + map.put(0x01A1, "GSYNCID"); + map.put(0x0192, "PRVIDSAY"); + map.put(0x0193, "BTLTEST"); + map.put(0x0195, "GENLOC"); + map.put(0x0199, "CHOICEFOOD"); + map.put(0x019A, "HOTELITEM"); + map.put(0x019B, "TAXISTOP"); + map.put(0x019F, "MAISTITLE"); + map.put(0x1000, "ITEMPLUR0"); + map.put(0x1001, "ITEMPLUR1"); + map.put(0x1100, "GENDBR"); + map.put(0x1101, "NUMBRNCH"); + map.put(0x1302, "iCOLOR2"); + map.put(0x1303, "iCOLOR3"); + map.put(0x0200, "NUM1"); + map.put(0x0201, "NUM2"); + map.put(0x0202, "NUM3"); + map.put(0x0203, "NUM4"); + map.put(0x0204, "NUM5"); + map.put(0x0205, "NUM6"); + map.put(0x0206, "NUM7"); + map.put(0x0207, "NUM8"); + map.put(0x0208, "NUM9"); + } + return map; + } + + public static int getVariableCode(String name, int romType) { + Map map = getTextVariableCodes(romType); + for (int k: map.keySet()) { + if (map.get(k).equals(name)) { + return k; + } + } + return 0; + } +} diff --git a/src/com/pkrandom/constants/Species.java b/src/com/pkrandom/constants/Species.java new file mode 100644 index 0000000..74dba94 --- /dev/null +++ b/src/com/pkrandom/constants/Species.java @@ -0,0 +1,1407 @@ +package com.pkrandom.constants; + +/*----------------------------------------------------------------------------*/ +/*-- Species.java - defines a species number constant for every Pokemon. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class Species { + public static final int bulbasaur = 1; + public static final int ivysaur = 2; + public static final int venusaur = 3; + public static final int charmander = 4; + public static final int charmeleon = 5; + public static final int charizard = 6; + public static final int squirtle = 7; + public static final int wartortle = 8; + public static final int blastoise = 9; + public static final int caterpie = 10; + public static final int metapod = 11; + public static final int butterfree = 12; + public static final int weedle = 13; + public static final int kakuna = 14; + public static final int beedrill = 15; + public static final int pidgey = 16; + public static final int pidgeotto = 17; + public static final int pidgeot = 18; + public static final int rattata = 19; + public static final int raticate = 20; + public static final int spearow = 21; + public static final int fearow = 22; + public static final int ekans = 23; + public static final int arbok = 24; + public static final int pikachu = 25; + public static final int raichu = 26; + public static final int sandshrew = 27; + public static final int sandslash = 28; + public static final int nidoranFemale = 29; + public static final int nidorina = 30; + public static final int nidoqueen = 31; + public static final int nidoranMale = 32; + public static final int nidorino = 33; + public static final int nidoking = 34; + public static final int clefairy = 35; + public static final int clefable = 36; + public static final int vulpix = 37; + public static final int ninetales = 38; + public static final int jigglypuff = 39; + public static final int wigglytuff = 40; + public static final int zubat = 41; + public static final int golbat = 42; + public static final int oddish = 43; + public static final int gloom = 44; + public static final int vileplume = 45; + public static final int paras = 46; + public static final int parasect = 47; + public static final int venonat = 48; + public static final int venomoth = 49; + public static final int diglett = 50; + public static final int dugtrio = 51; + public static final int meowth = 52; + public static final int persian = 53; + public static final int psyduck = 54; + public static final int golduck = 55; + public static final int mankey = 56; + public static final int primeape = 57; + public static final int growlithe = 58; + public static final int arcanine = 59; + public static final int poliwag = 60; + public static final int poliwhirl = 61; + public static final int poliwrath = 62; + public static final int abra = 63; + public static final int kadabra = 64; + public static final int alakazam = 65; + public static final int machop = 66; + public static final int machoke = 67; + public static final int machamp = 68; + public static final int bellsprout = 69; + public static final int weepinbell = 70; + public static final int victreebel = 71; + public static final int tentacool = 72; + public static final int tentacruel = 73; + public static final int geodude = 74; + public static final int graveler = 75; + public static final int golem = 76; + public static final int ponyta = 77; + public static final int rapidash = 78; + public static final int slowpoke = 79; + public static final int slowbro = 80; + public static final int magnemite = 81; + public static final int magneton = 82; + public static final int farfetchd = 83; + public static final int doduo = 84; + public static final int dodrio = 85; + public static final int seel = 86; + public static final int dewgong = 87; + public static final int grimer = 88; + public static final int muk = 89; + public static final int shellder = 90; + public static final int cloyster = 91; + public static final int gastly = 92; + public static final int haunter = 93; + public static final int gengar = 94; + public static final int onix = 95; + public static final int drowzee = 96; + public static final int hypno = 97; + public static final int krabby = 98; + public static final int kingler = 99; + public static final int voltorb = 100; + public static final int electrode = 101; + public static final int exeggcute = 102; + public static final int exeggutor = 103; + public static final int cubone = 104; + public static final int marowak = 105; + public static final int hitmonlee = 106; + public static final int hitmonchan = 107; + public static final int lickitung = 108; + public static final int koffing = 109; + public static final int weezing = 110; + public static final int rhyhorn = 111; + public static final int rhydon = 112; + public static final int chansey = 113; + public static final int tangela = 114; + public static final int kangaskhan = 115; + public static final int horsea = 116; + public static final int seadra = 117; + public static final int goldeen = 118; + public static final int seaking = 119; + public static final int staryu = 120; + public static final int starmie = 121; + public static final int mrMime = 122; + public static final int scyther = 123; + public static final int jynx = 124; + public static final int electabuzz = 125; + public static final int magmar = 126; + public static final int pinsir = 127; + public static final int tauros = 128; + public static final int magikarp = 129; + public static final int gyarados = 130; + public static final int lapras = 131; + public static final int ditto = 132; + public static final int eevee = 133; + public static final int vaporeon = 134; + public static final int jolteon = 135; + public static final int flareon = 136; + public static final int porygon = 137; + public static final int omanyte = 138; + public static final int omastar = 139; + public static final int kabuto = 140; + public static final int kabutops = 141; + public static final int aerodactyl = 142; + public static final int snorlax = 143; + public static final int articuno = 144; + public static final int zapdos = 145; + public static final int moltres = 146; + public static final int dratini = 147; + public static final int dragonair = 148; + public static final int dragonite = 149; + public static final int mewtwo = 150; + public static final int mew = 151; + public static final int chikorita = 152; + public static final int bayleef = 153; + public static final int meganium = 154; + public static final int cyndaquil = 155; + public static final int quilava = 156; + public static final int typhlosion = 157; + public static final int totodile = 158; + public static final int croconaw = 159; + public static final int feraligatr = 160; + public static final int sentret = 161; + public static final int furret = 162; + public static final int hoothoot = 163; + public static final int noctowl = 164; + public static final int ledyba = 165; + public static final int ledian = 166; + public static final int spinarak = 167; + public static final int ariados = 168; + public static final int crobat = 169; + public static final int chinchou = 170; + public static final int lanturn = 171; + public static final int pichu = 172; + public static final int cleffa = 173; + public static final int igglybuff = 174; + public static final int togepi = 175; + public static final int togetic = 176; + public static final int natu = 177; + public static final int xatu = 178; + public static final int mareep = 179; + public static final int flaaffy = 180; + public static final int ampharos = 181; + public static final int bellossom = 182; + public static final int marill = 183; + public static final int azumarill = 184; + public static final int sudowoodo = 185; + public static final int politoed = 186; + public static final int hoppip = 187; + public static final int skiploom = 188; + public static final int jumpluff = 189; + public static final int aipom = 190; + public static final int sunkern = 191; + public static final int sunflora = 192; + public static final int yanma = 193; + public static final int wooper = 194; + public static final int quagsire = 195; + public static final int espeon = 196; + public static final int umbreon = 197; + public static final int murkrow = 198; + public static final int slowking = 199; + public static final int misdreavus = 200; + public static final int unown = 201; + public static final int wobbuffet = 202; + public static final int girafarig = 203; + public static final int pineco = 204; + public static final int forretress = 205; + public static final int dunsparce = 206; + public static final int gligar = 207; + public static final int steelix = 208; + public static final int snubbull = 209; + public static final int granbull = 210; + public static final int qwilfish = 211; + public static final int scizor = 212; + public static final int shuckle = 213; + public static final int heracross = 214; + public static final int sneasel = 215; + public static final int teddiursa = 216; + public static final int ursaring = 217; + public static final int slugma = 218; + public static final int magcargo = 219; + public static final int swinub = 220; + public static final int piloswine = 221; + public static final int corsola = 222; + public static final int remoraid = 223; + public static final int octillery = 224; + public static final int delibird = 225; + public static final int mantine = 226; + public static final int skarmory = 227; + public static final int houndour = 228; + public static final int houndoom = 229; + public static final int kingdra = 230; + public static final int phanpy = 231; + public static final int donphan = 232; + public static final int porygon2 = 233; + public static final int stantler = 234; + public static final int smeargle = 235; + public static final int tyrogue = 236; + public static final int hitmontop = 237; + public static final int smoochum = 238; + public static final int elekid = 239; + public static final int magby = 240; + public static final int miltank = 241; + public static final int blissey = 242; + public static final int raikou = 243; + public static final int entei = 244; + public static final int suicune = 245; + public static final int larvitar = 246; + public static final int pupitar = 247; + public static final int tyranitar = 248; + public static final int lugia = 249; + public static final int hoOh = 250; + public static final int celebi = 251; + public static final int treecko = 252; + public static final int grovyle = 253; + public static final int sceptile = 254; + public static final int torchic = 255; + public static final int combusken = 256; + public static final int blaziken = 257; + public static final int mudkip = 258; + public static final int marshtomp = 259; + public static final int swampert = 260; + public static final int poochyena = 261; + public static final int mightyena = 262; + public static final int zigzagoon = 263; + public static final int linoone = 264; + public static final int wurmple = 265; + public static final int silcoon = 266; + public static final int beautifly = 267; + public static final int cascoon = 268; + public static final int dustox = 269; + public static final int lotad = 270; + public static final int lombre = 271; + public static final int ludicolo = 272; + public static final int seedot = 273; + public static final int nuzleaf = 274; + public static final int shiftry = 275; + public static final int taillow = 276; + public static final int swellow = 277; + public static final int wingull = 278; + public static final int pelipper = 279; + public static final int ralts = 280; + public static final int kirlia = 281; + public static final int gardevoir = 282; + public static final int surskit = 283; + public static final int masquerain = 284; + public static final int shroomish = 285; + public static final int breloom = 286; + public static final int slakoth = 287; + public static final int vigoroth = 288; + public static final int slaking = 289; + public static final int nincada = 290; + public static final int ninjask = 291; + public static final int shedinja = 292; + public static final int whismur = 293; + public static final int loudred = 294; + public static final int exploud = 295; + public static final int makuhita = 296; + public static final int hariyama = 297; + public static final int azurill = 298; + public static final int nosepass = 299; + public static final int skitty = 300; + public static final int delcatty = 301; + public static final int sableye = 302; + public static final int mawile = 303; + public static final int aron = 304; + public static final int lairon = 305; + public static final int aggron = 306; + public static final int meditite = 307; + public static final int medicham = 308; + public static final int electrike = 309; + public static final int manectric = 310; + public static final int plusle = 311; + public static final int minun = 312; + public static final int volbeat = 313; + public static final int illumise = 314; + public static final int roselia = 315; + public static final int gulpin = 316; + public static final int swalot = 317; + public static final int carvanha = 318; + public static final int sharpedo = 319; + public static final int wailmer = 320; + public static final int wailord = 321; + public static final int numel = 322; + public static final int camerupt = 323; + public static final int torkoal = 324; + public static final int spoink = 325; + public static final int grumpig = 326; + public static final int spinda = 327; + public static final int trapinch = 328; + public static final int vibrava = 329; + public static final int flygon = 330; + public static final int cacnea = 331; + public static final int cacturne = 332; + public static final int swablu = 333; + public static final int altaria = 334; + public static final int zangoose = 335; + public static final int seviper = 336; + public static final int lunatone = 337; + public static final int solrock = 338; + public static final int barboach = 339; + public static final int whiscash = 340; + public static final int corphish = 341; + public static final int crawdaunt = 342; + public static final int baltoy = 343; + public static final int claydol = 344; + public static final int lileep = 345; + public static final int cradily = 346; + public static final int anorith = 347; + public static final int armaldo = 348; + public static final int feebas = 349; + public static final int milotic = 350; + public static final int castform = 351; + public static final int kecleon = 352; + public static final int shuppet = 353; + public static final int banette = 354; + public static final int duskull = 355; + public static final int dusclops = 356; + public static final int tropius = 357; + public static final int chimecho = 358; + public static final int absol = 359; + public static final int wynaut = 360; + public static final int snorunt = 361; + public static final int glalie = 362; + public static final int spheal = 363; + public static final int sealeo = 364; + public static final int walrein = 365; + public static final int clamperl = 366; + public static final int huntail = 367; + public static final int gorebyss = 368; + public static final int relicanth = 369; + public static final int luvdisc = 370; + public static final int bagon = 371; + public static final int shelgon = 372; + public static final int salamence = 373; + public static final int beldum = 374; + public static final int metang = 375; + public static final int metagross = 376; + public static final int regirock = 377; + public static final int regice = 378; + public static final int registeel = 379; + public static final int latias = 380; + public static final int latios = 381; + public static final int kyogre = 382; + public static final int groudon = 383; + public static final int rayquaza = 384; + public static final int jirachi = 385; + public static final int deoxys = 386; + public static final int turtwig = 387; + public static final int grotle = 388; + public static final int torterra = 389; + public static final int chimchar = 390; + public static final int monferno = 391; + public static final int infernape = 392; + public static final int piplup = 393; + public static final int prinplup = 394; + public static final int empoleon = 395; + public static final int starly = 396; + public static final int staravia = 397; + public static final int staraptor = 398; + public static final int bidoof = 399; + public static final int bibarel = 400; + public static final int kricketot = 401; + public static final int kricketune = 402; + public static final int shinx = 403; + public static final int luxio = 404; + public static final int luxray = 405; + public static final int budew = 406; + public static final int roserade = 407; + public static final int cranidos = 408; + public static final int rampardos = 409; + public static final int shieldon = 410; + public static final int bastiodon = 411; + public static final int burmy = 412; + public static final int wormadam = 413; + public static final int mothim = 414; + public static final int combee = 415; + public static final int vespiquen = 416; + public static final int pachirisu = 417; + public static final int buizel = 418; + public static final int floatzel = 419; + public static final int cherubi = 420; + public static final int cherrim = 421; + public static final int shellos = 422; + public static final int gastrodon = 423; + public static final int ambipom = 424; + public static final int drifloon = 425; + public static final int drifblim = 426; + public static final int buneary = 427; + public static final int lopunny = 428; + public static final int mismagius = 429; + public static final int honchkrow = 430; + public static final int glameow = 431; + public static final int purugly = 432; + public static final int chingling = 433; + public static final int stunky = 434; + public static final int skuntank = 435; + public static final int bronzor = 436; + public static final int bronzong = 437; + public static final int bonsly = 438; + public static final int mimeJr = 439; + public static final int happiny = 440; + public static final int chatot = 441; + public static final int spiritomb = 442; + public static final int gible = 443; + public static final int gabite = 444; + public static final int garchomp = 445; + public static final int munchlax = 446; + public static final int riolu = 447; + public static final int lucario = 448; + public static final int hippopotas = 449; + public static final int hippowdon = 450; + public static final int skorupi = 451; + public static final int drapion = 452; + public static final int croagunk = 453; + public static final int toxicroak = 454; + public static final int carnivine = 455; + public static final int finneon = 456; + public static final int lumineon = 457; + public static final int mantyke = 458; + public static final int snover = 459; + public static final int abomasnow = 460; + public static final int weavile = 461; + public static final int magnezone = 462; + public static final int lickilicky = 463; + public static final int rhyperior = 464; + public static final int tangrowth = 465; + public static final int electivire = 466; + public static final int magmortar = 467; + public static final int togekiss = 468; + public static final int yanmega = 469; + public static final int leafeon = 470; + public static final int glaceon = 471; + public static final int gliscor = 472; + public static final int mamoswine = 473; + public static final int porygonZ = 474; + public static final int gallade = 475; + public static final int probopass = 476; + public static final int dusknoir = 477; + public static final int froslass = 478; + public static final int rotom = 479; + public static final int uxie = 480; + public static final int mesprit = 481; + public static final int azelf = 482; + public static final int dialga = 483; + public static final int palkia = 484; + public static final int heatran = 485; + public static final int regigigas = 486; + public static final int giratina = 487; + public static final int cresselia = 488; + public static final int phione = 489; + public static final int manaphy = 490; + public static final int darkrai = 491; + public static final int shaymin = 492; + public static final int arceus = 493; + public static final int victini = 494; + public static final int snivy = 495; + public static final int servine = 496; + public static final int serperior = 497; + public static final int tepig = 498; + public static final int pignite = 499; + public static final int emboar = 500; + public static final int oshawott = 501; + public static final int dewott = 502; + public static final int samurott = 503; + public static final int patrat = 504; + public static final int watchog = 505; + public static final int lillipup = 506; + public static final int herdier = 507; + public static final int stoutland = 508; + public static final int purrloin = 509; + public static final int liepard = 510; + public static final int pansage = 511; + public static final int simisage = 512; + public static final int pansear = 513; + public static final int simisear = 514; + public static final int panpour = 515; + public static final int simipour = 516; + public static final int munna = 517; + public static final int musharna = 518; + public static final int pidove = 519; + public static final int tranquill = 520; + public static final int unfezant = 521; + public static final int blitzle = 522; + public static final int zebstrika = 523; + public static final int roggenrola = 524; + public static final int boldore = 525; + public static final int gigalith = 526; + public static final int woobat = 527; + public static final int swoobat = 528; + public static final int drilbur = 529; + public static final int excadrill = 530; + public static final int audino = 531; + public static final int timburr = 532; + public static final int gurdurr = 533; + public static final int conkeldurr = 534; + public static final int tympole = 535; + public static final int palpitoad = 536; + public static final int seismitoad = 537; + public static final int throh = 538; + public static final int sawk = 539; + public static final int sewaddle = 540; + public static final int swadloon = 541; + public static final int leavanny = 542; + public static final int venipede = 543; + public static final int whirlipede = 544; + public static final int scolipede = 545; + public static final int cottonee = 546; + public static final int whimsicott = 547; + public static final int petilil = 548; + public static final int lilligant = 549; + public static final int basculin = 550; + public static final int sandile = 551; + public static final int krokorok = 552; + public static final int krookodile = 553; + public static final int darumaka = 554; + public static final int darmanitan = 555; + public static final int maractus = 556; + public static final int dwebble = 557; + public static final int crustle = 558; + public static final int scraggy = 559; + public static final int scrafty = 560; + public static final int sigilyph = 561; + public static final int yamask = 562; + public static final int cofagrigus = 563; + public static final int tirtouga = 564; + public static final int carracosta = 565; + public static final int archen = 566; + public static final int archeops = 567; + public static final int trubbish = 568; + public static final int garbodor = 569; + public static final int zorua = 570; + public static final int zoroark = 571; + public static final int minccino = 572; + public static final int cinccino = 573; + public static final int gothita = 574; + public static final int gothorita = 575; + public static final int gothitelle = 576; + public static final int solosis = 577; + public static final int duosion = 578; + public static final int reuniclus = 579; + public static final int ducklett = 580; + public static final int swanna = 581; + public static final int vanillite = 582; + public static final int vanillish = 583; + public static final int vanilluxe = 584; + public static final int deerling = 585; + public static final int sawsbuck = 586; + public static final int emolga = 587; + public static final int karrablast = 588; + public static final int escavalier = 589; + public static final int foongus = 590; + public static final int amoonguss = 591; + public static final int frillish = 592; + public static final int jellicent = 593; + public static final int alomomola = 594; + public static final int joltik = 595; + public static final int galvantula = 596; + public static final int ferroseed = 597; + public static final int ferrothorn = 598; + public static final int klink = 599; + public static final int klang = 600; + public static final int klinklang = 601; + public static final int tynamo = 602; + public static final int eelektrik = 603; + public static final int eelektross = 604; + public static final int elgyem = 605; + public static final int beheeyem = 606; + public static final int litwick = 607; + public static final int lampent = 608; + public static final int chandelure = 609; + public static final int axew = 610; + public static final int fraxure = 611; + public static final int haxorus = 612; + public static final int cubchoo = 613; + public static final int beartic = 614; + public static final int cryogonal = 615; + public static final int shelmet = 616; + public static final int accelgor = 617; + public static final int stunfisk = 618; + public static final int mienfoo = 619; + public static final int mienshao = 620; + public static final int druddigon = 621; + public static final int golett = 622; + public static final int golurk = 623; + public static final int pawniard = 624; + public static final int bisharp = 625; + public static final int bouffalant = 626; + public static final int rufflet = 627; + public static final int braviary = 628; + public static final int vullaby = 629; + public static final int mandibuzz = 630; + public static final int heatmor = 631; + public static final int durant = 632; + public static final int deino = 633; + public static final int zweilous = 634; + public static final int hydreigon = 635; + public static final int larvesta = 636; + public static final int volcarona = 637; + public static final int cobalion = 638; + public static final int terrakion = 639; + public static final int virizion = 640; + public static final int tornadus = 641; + public static final int thundurus = 642; + public static final int reshiram = 643; + public static final int zekrom = 644; + public static final int landorus = 645; + public static final int kyurem = 646; + public static final int keldeo = 647; + public static final int meloetta = 648; + public static final int genesect = 649; + public static final int chespin = 650; + public static final int quilladin = 651; + public static final int chesnaught = 652; + public static final int fennekin = 653; + public static final int braixen = 654; + public static final int delphox = 655; + public static final int froakie = 656; + public static final int frogadier = 657; + public static final int greninja = 658; + public static final int bunnelby = 659; + public static final int diggersby = 660; + public static final int fletchling = 661; + public static final int fletchinder = 662; + public static final int talonflame = 663; + public static final int scatterbug = 664; + public static final int spewpa = 665; + public static final int vivillon = 666; + public static final int litleo = 667; + public static final int pyroar = 668; + public static final int flabébé = 669; + public static final int floette = 670; + public static final int florges = 671; + public static final int skiddo = 672; + public static final int gogoat = 673; + public static final int pancham = 674; + public static final int pangoro = 675; + public static final int furfrou = 676; + public static final int espurr = 677; + public static final int meowstic = 678; + public static final int honedge = 679; + public static final int doublade = 680; + public static final int aegislash = 681; + public static final int spritzee = 682; + public static final int aromatisse = 683; + public static final int swirlix = 684; + public static final int slurpuff = 685; + public static final int inkay = 686; + public static final int malamar = 687; + public static final int binacle = 688; + public static final int barbaracle = 689; + public static final int skrelp = 690; + public static final int dragalge = 691; + public static final int clauncher = 692; + public static final int clawitzer = 693; + public static final int helioptile = 694; + public static final int heliolisk = 695; + public static final int tyrunt = 696; + public static final int tyrantrum = 697; + public static final int amaura = 698; + public static final int aurorus = 699; + public static final int sylveon = 700; + public static final int hawlucha = 701; + public static final int dedenne = 702; + public static final int carbink = 703; + public static final int goomy = 704; + public static final int sliggoo = 705; + public static final int goodra = 706; + public static final int klefki = 707; + public static final int phantump = 708; + public static final int trevenant = 709; + public static final int pumpkaboo = 710; + public static final int gourgeist = 711; + public static final int bergmite = 712; + public static final int avalugg = 713; + public static final int noibat = 714; + public static final int noivern = 715; + public static final int xerneas = 716; + public static final int yveltal = 717; + public static final int zygarde = 718; + public static final int diancie = 719; + public static final int hoopa = 720; + public static final int volcanion = 721; + public static final int rowlet = 722; + public static final int dartrix = 723; + public static final int decidueye = 724; + public static final int litten = 725; + public static final int torracat = 726; + public static final int incineroar = 727; + public static final int popplio = 728; + public static final int brionne = 729; + public static final int primarina = 730; + public static final int pikipek = 731; + public static final int trumbeak = 732; + public static final int toucannon = 733; + public static final int yungoos = 734; + public static final int gumshoos = 735; + public static final int grubbin = 736; + public static final int charjabug = 737; + public static final int vikavolt = 738; + public static final int crabrawler = 739; + public static final int crabominable = 740; + public static final int oricorio = 741; + public static final int cutiefly = 742; + public static final int ribombee = 743; + public static final int rockruff = 744; + public static final int lycanroc = 745; + public static final int wishiwashi = 746; + public static final int mareanie = 747; + public static final int toxapex = 748; + public static final int mudbray = 749; + public static final int mudsdale = 750; + public static final int dewpider = 751; + public static final int araquanid = 752; + public static final int fomantis = 753; + public static final int lurantis = 754; + public static final int morelull = 755; + public static final int shiinotic = 756; + public static final int salandit = 757; + public static final int salazzle = 758; + public static final int stufful = 759; + public static final int bewear = 760; + public static final int bounsweet = 761; + public static final int steenee = 762; + public static final int tsareena = 763; + public static final int comfey = 764; + public static final int oranguru = 765; + public static final int passimian = 766; + public static final int wimpod = 767; + public static final int golisopod = 768; + public static final int sandygast = 769; + public static final int palossand = 770; + public static final int pyukumuku = 771; + public static final int typeNull = 772; + public static final int silvally = 773; + public static final int minior = 774; + public static final int komala = 775; + public static final int turtonator = 776; + public static final int togedemaru = 777; + public static final int mimikyu = 778; + public static final int bruxish = 779; + public static final int drampa = 780; + public static final int dhelmise = 781; + public static final int jangmoO = 782; + public static final int hakamoO = 783; + public static final int kommoO = 784; + public static final int tapuKoko = 785; + public static final int tapuLele = 786; + public static final int tapuBulu = 787; + public static final int tapuFini = 788; + public static final int cosmog = 789; + public static final int cosmoem = 790; + public static final int solgaleo = 791; + public static final int lunala = 792; + public static final int nihilego = 793; + public static final int buzzwole = 794; + public static final int pheromosa = 795; + public static final int xurkitree = 796; + public static final int celesteela = 797; + public static final int kartana = 798; + public static final int guzzlord = 799; + public static final int necrozma = 800; + public static final int magearna = 801; + public static final int marshadow = 802; + public static final int poipole = 803; + public static final int naganadel = 804; + public static final int stakataka = 805; + public static final int blacephalon = 806; + public static final int zeraora = 807; + public static final int meltan = 808; + public static final int melmetal = 809; + public static final int grookey = 810; + public static final int thwackey = 811; + public static final int rillaboom = 812; + public static final int scorbunny = 813; + public static final int raboot = 814; + public static final int cinderace = 815; + public static final int sobble = 816; + public static final int drizzile = 817; + public static final int inteleon = 818; + public static final int skwovet = 819; + public static final int greedent = 820; + public static final int rookidee = 821; + public static final int corvisquire = 822; + public static final int corviknight = 823; + public static final int blipbug = 824; + public static final int dottler = 825; + public static final int orbeetle = 826; + public static final int nickit = 827; + public static final int thievul = 828; + public static final int gossifleur = 829; + public static final int eldegoss = 830; + public static final int wooloo = 831; + public static final int dubwool = 832; + public static final int chewtle = 833; + public static final int drednaw = 834; + public static final int yamper = 835; + public static final int boltund = 836; + public static final int rolycoly = 837; + public static final int carkol = 838; + public static final int coalossal = 839; + public static final int applin = 840; + public static final int flapple = 841; + public static final int appletun = 842; + public static final int silicobra = 843; + public static final int sandaconda = 844; + public static final int cramorant = 845; + public static final int arrokuda = 846; + public static final int barraskewda = 847; + public static final int toxel = 848; + public static final int toxtricity = 849; + public static final int sizzlipede = 850; + public static final int centiskorch = 851; + public static final int clobbopus = 852; + public static final int grapploct = 853; + public static final int sinistea = 854; + public static final int polteageist = 855; + public static final int hatenna = 856; + public static final int hattrem = 857; + public static final int hatterene = 858; + public static final int impidimp = 859; + public static final int morgrem = 860; + public static final int grimmsnarl = 861; + public static final int obstagoon = 862; + public static final int perrserker = 863; + public static final int cursola = 864; + public static final int sirfetchd = 865; + public static final int mrRime = 866; + public static final int runerigus = 867; + public static final int milcery = 868; + public static final int alcremie = 869; + public static final int falinks = 870; + public static final int pincurchin = 871; + public static final int snom = 872; + public static final int frosmoth = 873; + public static final int stonjourner = 874; + public static final int eiscue = 875; + public static final int indeedee = 876; + public static final int morpeko = 877; + public static final int cufant = 878; + public static final int copperajah = 879; + public static final int dracozolt = 880; + public static final int arctozolt = 881; + public static final int dracovish = 882; + public static final int arctovish = 883; + public static final int duraludon = 884; + public static final int dreepy = 885; + public static final int drakloak = 886; + public static final int dragapult = 887; + public static final int zacian = 888; + public static final int zamazenta = 889; + public static final int eternatus = 890; + public static final int kubfu = 891; + public static final int urshifu = 892; + public static final int zarude = 893; + public static final int regieleki = 894; + public static final int regidrago = 895; + public static final int glastrier = 896; + public static final int spectrier = 897; + public static final int calyrex = 898; + + public static final class Gen4Formes { + public static final int deoxysA = 494; + public static final int deoxysD = 495; + public static final int deoxysS = 496; + public static final int wormadamS = 497; + public static final int wormadamT = 498; + public static final int giratinaO = 499; + public static final int shayminS = 500; + public static final int rotomH = 501; + public static final int rotomW = 502; + public static final int rotomFr = 503; + public static final int rotomFa = 504; + public static final int rotomM = 505; + } + + public static final class Gen5Formes { + public static final int deoxysA = 650; + public static final int deoxysD = 651; + public static final int deoxysS = 652; + public static final int wormadamS = 653; + public static final int wormadamT = 654; + public static final int shayminS = 655; + public static final int giratinaO = 656; + public static final int rotomH = 657; + public static final int rotomW = 658; + public static final int rotomFr = 659; + public static final int rotomFa = 660; + public static final int rotomM = 661; + public static final int castformF = 662; + public static final int castformW = 663; + public static final int castformI = 664; + public static final int basculinB = 665; + public static final int darmanitanZ = 666; + public static final int meloettaP = 667; + public static final int kyuremW = 668; + public static final int kyuremB = 669; + public static final int keldeoCosmetic1 = 670; + public static final int tornadusT = 671; + public static final int thundurusT = 672; + public static final int landorusT = 673; + } + + public static final class Gen6Formes { + public static final int deoxysA = 722; + public static final int deoxysD = 723; + public static final int deoxysS = 724; + public static final int wormadamS = 725; + public static final int wormadamT = 726; + public static final int shayminS = 727; + public static final int giratinaO = 728; + public static final int rotomH = 729; + public static final int rotomW = 730; + public static final int rotomFr = 731; + public static final int rotomFa = 732; + public static final int rotomM = 733; + public static final int castformF = 734; + public static final int castformW = 735; + public static final int castformI = 736; + public static final int cherrimCosmetic1 = 737; + public static final int basculinB = 738; + public static final int darmanitanZ = 739; + public static final int meloettaP = 740; + public static final int kyuremW = 741; + public static final int kyuremB = 742; + public static final int keldeoCosmetic1= 743; + public static final int tornadusT = 744; + public static final int thundurusT = 745; + public static final int landorusT = 746; + public static final int gengarMega = 747; + public static final int meowsticF = 748; + public static final int furfrouCosmetic1 = 749; + public static final int furfrouCosmetic2 = 750; + public static final int furfrouCosmetic3 = 751; + public static final int furfrouCosmetic4 = 752; + public static final int furfrouCosmetic5 = 753; + public static final int furfrouCosmetic6 = 754; + public static final int furfrouCosmetic7 = 755; + public static final int furfrouCosmetic8 = 756; + public static final int furfrouCosmetic9 = 757; + public static final int gardevoirMega = 758; + public static final int ampharosMega = 759; + public static final int venusaurMega = 760; + public static final int charizardMegaX = 761; + public static final int charizardMegaY = 762; + public static final int mewtwoMegaX = 763; + public static final int mewtwoMegaY = 764; + public static final int blazikenMega = 765; + public static final int medichamMega = 766; + public static final int houndoomMega = 767; + public static final int aggronMega = 768; + public static final int banetteMega = 769; + public static final int tyranitarMega = 770; + public static final int scizorMega = 771; + public static final int pinsirMega = 772; + public static final int aerodactylMega = 773; + public static final int lucarioMega = 774; + public static final int abomasnowMega = 775; + public static final int aegislashB = 776; + public static final int blastoiseMega = 777; + public static final int kangaskhanMega = 778; + public static final int gyaradosMega = 779; + public static final int absolMega = 780; + public static final int alakazamMega = 781; + public static final int heracrossMega = 782; + public static final int mawileMega = 783; + public static final int manectricMega = 784; + public static final int garchompMega = 785; + public static final int latiosMega = 786; + public static final int latiasMega = 787; + public static final int pumpkabooCosmetic1 = 788; + public static final int pumpkabooCosmetic2 = 789; + public static final int pumpkabooCosmetic3 = 790; + public static final int gourgeistCosmetic1 = 791; + public static final int gourgeistCosmetic2 = 792; + public static final int gourgeistCosmetic3 = 793; + public static final int floetteCosmetic1 = 794; + public static final int floetteCosmetic2 = 795; + public static final int floetteCosmetic3 = 796; + public static final int floetteCosmetic4 = 797; + public static final int floetteE = 798; + public static final int swampertMega = 799; + public static final int sceptileMega = 800; + public static final int sableyeMega = 801; + public static final int altariaMega = 802; + public static final int galladeMega = 803; + public static final int audinoMega = 804; + public static final int sharpedoMega = 805; + public static final int slowbroMega = 806; + public static final int steelixMega = 807; + public static final int pidgeotMega = 808; + public static final int glalieMega = 809; + public static final int diancieMega = 810; + public static final int metagrossMega = 811; + public static final int kyogreP = 812; + public static final int groudonP = 813; + public static final int rayquazaMega = 814; + public static final int pikachuCosmetic1 = 815; + public static final int pikachuCosmetic2 = 816; + public static final int pikachuCosmetic3 = 817; + public static final int pikachuCosmetic4 = 818; + public static final int pikachuCosmetic5 = 819; + public static final int pikachuCosmetic6 = 820; + public static final int hoopaU = 821; + public static final int cameruptMega = 822; + public static final int lopunnyMega = 823; + public static final int salamenceMega = 824; + public static final int beedrillMega = 825; + } + + public static final class SMFormes { + public static final int deoxysA = 803; + public static final int deoxysD = 804; + public static final int deoxysS = 805; + public static final int wormadamS = 806; + public static final int wormadamT = 807; + public static final int shayminS = 808; + public static final int giratinaO = 809; + public static final int rotomH = 810; + public static final int rotomW = 811; + public static final int rotomFr = 812; + public static final int rotomFa = 813; + public static final int rotomM = 814; + public static final int castformF = 815; + public static final int castformW = 816; + public static final int castformI = 817; + public static final int cherrimCosmetic1 = 818; + public static final int shellosCosmetic1 = 819; + public static final int gastrodonCosmetic1 = 820; + public static final int basculinB = 821; + public static final int darmanitanZ = 822; + public static final int meloettaP = 823; + public static final int kyuremW = 824; + public static final int kyuremB = 825; + public static final int keldeoCosmetic1 = 826; + public static final int tornadusT = 827; + public static final int thundurusT = 828; + public static final int landorusT = 829; + public static final int gengarMega = 830; + public static final int meowsticF = 831; + public static final int furfrouCosmetic1 = 832; + public static final int furfrouCosmetic2 = 833; + public static final int furfrouCosmetic3 = 834; + public static final int furfrouCosmetic4 = 835; + public static final int furfrouCosmetic5 = 836; + public static final int furfrouCosmetic6 = 837; + public static final int furfrouCosmetic7 = 838; + public static final int furfrouCosmetic8 = 839; + public static final int furfrouCosmetic9 = 840; + public static final int gardevoirMega = 841; + public static final int ampharosMega = 842; + public static final int venusaurMega = 843; + public static final int charizardMegaX = 844; + public static final int charizardMegaY = 845; + public static final int mewtwoMegaX = 846; + public static final int mewtwoMegaY = 847; + public static final int blazikenMega = 848; + public static final int medichamMega = 849; + public static final int houndoomMega = 850; + public static final int aggronMega = 851; + public static final int banetteMega = 852; + public static final int tyranitarMega = 853; + public static final int scizorMega = 854; + public static final int pinsirMega = 855; + public static final int aerodactylMega = 856; + public static final int lucarioMega = 857; + public static final int abomasnowMega = 858; + public static final int aegislashB = 859; + public static final int blastoiseMega = 860; + public static final int kangaskhanMega = 861; + public static final int gyaradosMega = 862; + public static final int absolMega = 863; + public static final int alakazamMega = 864; + public static final int heracrossMega = 865; + public static final int mawileMega = 866; + public static final int manectricMega = 867; + public static final int garchompMega = 868; + public static final int latiosMega = 869; + public static final int latiasMega = 870; + public static final int pumpkabooCosmetic1 = 871; + public static final int pumpkabooCosmetic2 = 872; + public static final int pumpkabooCosmetic3 = 873; + public static final int gourgeistCosmetic1 = 874; + public static final int gourgeistCosmetic2 = 875; + public static final int gourgeistCosmetic3 = 876; + public static final int floetteCosmetic1 = 877; + public static final int floetteCosmetic2 = 878; + public static final int floetteCosmetic3 = 879; + public static final int floetteCosmetic4 = 880; + public static final int floetteE = 881; + public static final int swampertMega = 882; + public static final int sceptileMega = 883; + public static final int sableyeMega = 884; + public static final int altariaMega = 885; + public static final int galladeMega = 886; + public static final int audinoMega = 887; + public static final int sharpedoMega = 888; + public static final int slowbroMega = 889; + public static final int steelixMega = 890; + public static final int pidgeotMega = 891; + public static final int glalieMega = 892; + public static final int diancieMega = 893; + public static final int metagrossMega = 894; + public static final int kyogreP = 895; + public static final int groudonP = 896; + public static final int rayquazaMega = 897; + public static final int hoopaU = 898; + public static final int cameruptMega = 899; + public static final int lopunnyMega = 900; + public static final int salamenceMega = 901; + public static final int beedrillMega = 902; + public static final int wishiwashiS = 903; + public static final int oricorioE = 904; + public static final int oricorioP = 905; + public static final int oricorioG = 906; + public static final int lycanrocM = 907; + public static final int rattataA = 908; + public static final int raticateA = 909; + public static final int raticateACosmetic1 = 910; + public static final int raichuA = 911; + public static final int sandshrewA = 912; + public static final int sandslashA = 913; + public static final int vulpixA = 914; + public static final int ninetalesA = 915; + public static final int meowthA = 916; + public static final int persianA = 917; + public static final int geodudeA = 918; + public static final int gravelerA = 919; + public static final int golemA = 920; + public static final int grimerA = 921; + public static final int mukA = 922; + public static final int exeggutorA = 923; + public static final int marowakA = 924; + public static final int greninjaCosmetic1 = 925; + public static final int greninjaA = 926; + public static final int zygarde10 = 927; + public static final int zygarde10Cosmetic1 = 928; + public static final int zygardeCosmetic1 = 929; + public static final int zygardeC = 930; + public static final int miniorCosmetic1 = 931; + public static final int miniorCosmetic2 = 932; + public static final int miniorCosmetic3 = 933; + public static final int miniorCosmetic4 = 934; + public static final int miniorCosmetic5 = 935; + public static final int miniorCosmetic6 = 936; + public static final int miniorC = 937; + public static final int miniorCCosmetic1 = 938; + public static final int miniorCCosmetic2 = 939; + public static final int miniorCCosmetic3 = 940; + public static final int miniorCCosmetic4 = 941; + public static final int miniorCCosmetic5 = 942; + public static final int miniorCCosmetic6 = 943; + public static final int diglettA = 944; + public static final int dugtrioA = 945; + public static final int mimikyuCosmetic1 = 946; + public static final int mimikyuCosmetic2 = 947; + public static final int mimikyuCosmetic3 = 948; + public static final int magearnaCosmetic1 = 949; + public static final int pikachuCosmetic1 = 950; + public static final int pikachuCosmetic2 = 951; + public static final int pikachuCosmetic3 = 952; + public static final int pikachuCosmetic4 = 953; + public static final int pikachuCosmetic5 = 954; + public static final int pikachuCosmetic6 = 955; + public static final int gumshoosCosmetic1 = 956; + public static final int vikavoltCosmetic1 = 957; + public static final int lurantisCosmetic1 = 958; + public static final int salazzleCosmetic1 = 959; + public static final int kommoOCosmetic1 = 960; + } + + public static final class USUMFormes { + public static final int deoxysA = 808; + public static final int deoxysD = 809; + public static final int deoxysS = 810; + public static final int wormadamS = 811; + public static final int wormadamT = 812; + public static final int shayminS = 813; + public static final int giratinaO = 814; + public static final int rotomH = 815; + public static final int rotomW = 816; + public static final int rotomFr = 817; + public static final int rotomFa = 818; + public static final int rotomM = 819; + public static final int castformF = 820; + public static final int castformW = 821; + public static final int castformI = 822; + public static final int cherrimCosmetic1 = 823; + public static final int shellosCosmetic1 = 824; + public static final int gastrodonCosmetic1 = 825; + public static final int basculinB = 826; + public static final int darmanitanZ = 827; + public static final int meloettaP = 828; + public static final int kyuremW = 829; + public static final int kyuremB = 830; + public static final int keldeoCosmetic1 = 831; + public static final int tornadusT = 832; + public static final int thundurusT = 833; + public static final int landorusT = 834; + public static final int gengarMega = 835; + public static final int meowsticF = 836; + public static final int furfrouCosmetic1 = 837; + public static final int furfrouCosmetic2 = 838; + public static final int furfrouCosmetic3 = 839; + public static final int furfrouCosmetic4 = 840; + public static final int furfrouCosmetic5 = 841; + public static final int furfrouCosmetic6 = 842; + public static final int furfrouCosmetic7 = 843; + public static final int furfrouCosmetic8 = 844; + public static final int furfrouCosmetic9 = 845; + public static final int gardevoirMega = 846; + public static final int ampharosMega = 847; + public static final int venusaurMega = 848; + public static final int charizardMegaX = 849; + public static final int charizardMegaY = 850; + public static final int mewtwoMegaX = 851; + public static final int mewtwoMegaY = 852; + public static final int blazikenMega = 853; + public static final int medichamMega = 854; + public static final int houndoomMega = 855; + public static final int aggronMega = 856; + public static final int banetteMega = 857; + public static final int tyranitarMega = 858; + public static final int scizorMega = 859; + public static final int pinsirMega = 860; + public static final int aerodactylMega = 861; + public static final int lucarioMega = 862; + public static final int abomasnowMega = 863; + public static final int aegislashB = 864; + public static final int blastoiseMega = 865; + public static final int kangaskhanMega = 866; + public static final int gyaradosMega = 867; + public static final int absolMega = 868; + public static final int alakazamMega = 869; + public static final int heracrossMega = 870; + public static final int mawileMega = 871; + public static final int manectricMega = 872; + public static final int garchompMega = 873; + public static final int latiosMega = 874; + public static final int latiasMega = 875; + public static final int pumpkabooCosmetic1 = 876; + public static final int pumpkabooCosmetic2 = 877; + public static final int pumpkabooCosmetic3 = 878; + public static final int gourgeistCosmetic1 = 879; + public static final int gourgeistCosmetic2 = 880; + public static final int gourgeistCosmetic3 = 881; + public static final int floetteCosmetic1 = 882; + public static final int floetteCosmetic2 = 883; + public static final int floetteCosmetic3 = 884; + public static final int floetteCosmetic4 = 885; + public static final int floetteE = 886; + public static final int swampertMega = 887; + public static final int sceptileMega = 888; + public static final int sableyeMega = 889; + public static final int altariaMega = 890; + public static final int galladeMega = 891; + public static final int audinoMega = 892; + public static final int sharpedoMega = 893; + public static final int slowbroMega = 894; + public static final int steelixMega = 895; + public static final int pidgeotMega = 896; + public static final int glalieMega = 897; + public static final int diancieMega = 898; + public static final int metagrossMega = 899; + public static final int kyogreP = 900; + public static final int groudonP = 901; + public static final int rayquazaMega = 902; + public static final int hoopaU = 903; + public static final int cameruptMega = 904; + public static final int lopunnyMega = 905; + public static final int salamenceMega = 906; + public static final int beedrillMega = 907; + public static final int wishiwashiS = 908; + public static final int oricorioE = 909; + public static final int oricorioP = 910; + public static final int oricorioG = 911; + public static final int lycanrocM = 912; + public static final int lycanrocD = 913; + public static final int rattataA = 914; + public static final int raticateA = 915; + public static final int raticateACosmetic1 = 916; + public static final int raichuA = 917; + public static final int sandshrewA = 918; + public static final int sandslashA = 919; + public static final int vulpixA = 920; + public static final int ninetalesA = 921; + public static final int meowthA = 922; + public static final int persianA = 923; + public static final int geodudeA = 924; + public static final int gravelerA = 925; + public static final int golemA = 926; + public static final int grimerA = 927; + public static final int mukA = 928; + public static final int exeggutorA = 929; + public static final int marowakA = 930; + public static final int marowakACosmetic1 = 931; + public static final int greninjaCosmetic1 = 932; + public static final int greninjaA = 933; + public static final int zygarde10 = 934; + public static final int zygarde10Cosmetic1 = 935; + public static final int zygardeCosmetic1 = 936; + public static final int zygardeC = 937; + public static final int miniorCosmetic1 = 938; + public static final int miniorCosmetic2 = 939; + public static final int miniorCosmetic3 = 940; + public static final int miniorCosmetic4 = 941; + public static final int miniorCosmetic5 = 942; + public static final int miniorCosmetic6 = 943; + public static final int miniorC = 944; + public static final int miniorCCosmetic1 = 945; + public static final int miniorCCosmetic2 = 946; + public static final int miniorCCosmetic3 = 947; + public static final int miniorCCosmetic4 = 948; + public static final int miniorCCosmetic5 = 949; + public static final int miniorCCosmetic6 = 950; + public static final int diglettA = 951; + public static final int dugtrioA = 952; + public static final int mimikyuCosmetic1 = 953; + public static final int mimikyuCosmetic2 = 954; + public static final int mimikyuCosmetic3 = 955; + public static final int magearnaCosmetic1 = 956; + public static final int pikachuCosmetic1 = 957; + public static final int pikachuCosmetic2 = 958; + public static final int pikachuCosmetic3 = 959; + public static final int pikachuCosmetic4 = 960; + public static final int pikachuCosmetic5 = 961; + public static final int pikachuCosmetic6 = 962; + public static final int pikachuCosmetic7 = 963; + public static final int gumshoosCosmetic1 = 964; + public static final int vikavoltCosmetic1 = 965; + public static final int lurantisCosmetic1 = 966; + public static final int salazzleCosmetic1 = 967; + public static final int kommoOCosmetic1 = 968; + public static final int necrozmaDM = 969; + public static final int necrozmaDW = 970; + public static final int necrozmaU = 971; + public static final int araquanidCosmetic1 = 972; + public static final int togedemaruCosmetic1 = 973; + public static final int ribombeeCosmetic1 = 974; + public static final int rockruffCosmetic1 = 975; + + } +} diff --git a/src/com/pkrandom/ctr/AMX.java b/src/com/pkrandom/ctr/AMX.java new file mode 100644 index 0000000..d99ba7f --- /dev/null +++ b/src/com/pkrandom/ctr/AMX.java @@ -0,0 +1,227 @@ +package com.pkrandom.ctr; + +/*----------------------------------------------------------------------------*/ +/*-- AMX.java - class for handling AMX script archives --*/ +/*-- --*/ +/*-- Contains code based on "pk3DS", copyright (C) Kaphotics --*/ +/*-- Contains code based on "pkNX", copyright (C) Kaphotics --*/ +/*-- Contains code based on "poketools", copyright (C) FireyFly --*/ +/*-- Additional contributions by the UPR-ZX team --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.FileFunctions; +import com.pkrandom.exceptions.RandomizerIOException; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class AMX { + + public byte[] decData; + public int scriptOffset = 0; + + private int amxMagic = 0x0A0AF1E0; + private int amxMagicDebug = 0x0A0AF1EF; + private long mask = 0xFF; + + private int length; + + private int scriptInstrStart; + private int scriptMovementStart; + private int finalOffset; + private int allocatedMemory; + + private int compLength; + private int decompLength; + + private int ptrOffset; + private int ptrCount; + + private byte[] extraData; + + public AMX(byte[] data, int scriptNum) throws IOException { + int found = 0; + for (int i = 0; i < data.length - 3; i++) { + int val = FileFunctions.readFullInt(data,i); + if (val == amxMagic) { + if (found == scriptNum) { + int length = FileFunctions.readFullInt(data,i-4); + readHeaderAndDecompress(Arrays.copyOfRange(data,i-4,i-4+length)); + scriptOffset = i-4; + break; + } else { + found++; + } + } + } + } + + public AMX(byte[] encData) throws IOException { + readHeaderAndDecompress(encData); + } + + // Credit to the creators of pk3DS (Kaphotics et al) + private void readHeaderAndDecompress(byte[] encData) throws IOException { + length = FileFunctions.readFullInt(encData,0); + int magic = FileFunctions.readFullInt(encData,4); + if (magic != amxMagic) { + throw new IOException(); + } + + ptrOffset = FileFunctions.read2ByteInt(encData,8); + ptrCount = FileFunctions.read2ByteInt(encData,0xA); + + scriptInstrStart = FileFunctions.readFullInt(encData,0xC); + scriptMovementStart = FileFunctions.readFullInt(encData,0x10); + finalOffset = FileFunctions.readFullInt(encData,0x14); + allocatedMemory = FileFunctions.readFullInt(encData,0x18); + + compLength = length - scriptInstrStart; + byte[] compressedBytes = Arrays.copyOfRange(encData,scriptInstrStart,length); + decompLength = finalOffset - scriptInstrStart; + + decData = decompressBytes(compressedBytes, decompLength); + extraData = Arrays.copyOfRange(encData,0x1C,scriptInstrStart); + } + + // Credit to FireyFly + private byte[] decompressBytes(byte[] data, int length) { + byte[] code = new byte[length]; + int i = 0, j = 0, x = 0, f = 0; + while (i < code.length) { + int b = data[f++]; + int v = b & 0x7F; + if (++j == 1) { + x = ((((v >>> 6 == 0 ? 1 : 0) - 1 ) << 6) | v); + } else { + x = (x << 7) | (v & 0xFF); + } + if ((b & 0x80) != 0) continue; + code[i++] = (byte)(x & 0xFF); + code[i++] = (byte)((x >>> 8) & 0xFF); + code[i++] = (byte)((x >>> 16) & 0xFF); + code[i++] = (byte)((x >>> 24) & 0xFF); + j = 0; + } + return code; + } + + public byte[] getBytes() { + + ByteBuffer bbuf = ByteBuffer.allocate(length*2); + + bbuf.order(ByteOrder.LITTLE_ENDIAN); + + bbuf.putInt(length); + bbuf.putInt(amxMagic); + bbuf.putShort((short)ptrOffset); + bbuf.putShort((short)ptrCount); + bbuf.putInt(scriptInstrStart); + bbuf.putInt(scriptMovementStart); + bbuf.putInt(finalOffset); + bbuf.putInt(allocatedMemory); + bbuf.put(extraData); + bbuf.put(compressScript(decData)); + bbuf.flip(); + bbuf.putInt(bbuf.limit()); + + return Arrays.copyOfRange(bbuf.array(),0,bbuf.limit()); + } + + private byte[] compressScript(byte[] data) { + if (data == null || data.length % 4 != 0) { + return null; + } + ByteBuffer inBuf = ByteBuffer.wrap(data); + inBuf.order(ByteOrder.LITTLE_ENDIAN); + + ByteArrayOutputStream out = new ByteArrayOutputStream(compLength); + + try { + while (inBuf.position() < data.length) { + compressBytes(inBuf, out); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + return out.toByteArray(); + } + + // Modified version of the AMX script compression algorithm from pkNX + private void compressBytes(ByteBuffer inBuf, ByteArrayOutputStream out) throws IOException { + List bytes = new ArrayList<>(); + int instructionTemp = inBuf.getInt(inBuf.position()); + long instruction = Integer.toUnsignedLong(instructionTemp); + boolean sign = (instruction & 0x80000000) > 0; + + // Signed (negative) values are handled opposite of unsigned (positive) values. + // Positive values are "done" when we've shifted the value down to zero, but + // we don't need to store the highest 1s in a signed value. We handle this by + // tracking the loop via a NOTed shadow copy of the instruction if it's signed. + int shadowTemp = sign ? ~instructionTemp : instructionTemp; + long shadow = Integer.toUnsignedLong(shadowTemp); + do + { + long least7 = instruction & 0b01111111; + byte byteVal = (byte)least7; + + if (bytes.size() > 0) + { + // Continuation bit on all but the lowest byte + byteVal |= 0x80; + } + + bytes.add(byteVal); + + instruction >>= 7; + shadow >>= 7; + } + while (shadow != 0); + + if (bytes.size() < 5) + { + // Ensure "sign bit" (bit just to the right of highest continuation bit) is + // correct. Add an extra empty continuation byte if we need to. Values can't + // be longer than 5 bytes, though. + + int signBit = sign ? 0x40 : 0x00; + + if ((bytes.get(bytes.size() - 1) & 0x40) != signBit) + bytes.add((byte)(sign ? 0xFF : 0x80)); + } + + // Reverse for endianess + for (int i = 0; i < bytes.size() / 2; i++) { + byte temp = bytes.get(i); + bytes.set(i, bytes.get(bytes.size() - i - 1)); + bytes.set(bytes.size() - i - 1, temp); + } + + byte[] ret = new byte[bytes.size()]; + for (int i = 0; i < ret.length; i++) { + ret[i] = bytes.get(i); + } + + inBuf.position(inBuf.position() + 4); + out.write(ret); + } +} diff --git a/src/com/pkrandom/ctr/BFLIM.java b/src/com/pkrandom/ctr/BFLIM.java new file mode 100644 index 0000000..5bbb71a --- /dev/null +++ b/src/com/pkrandom/ctr/BFLIM.java @@ -0,0 +1,203 @@ +package com.pkrandom.ctr; + +/*----------------------------------------------------------------------------*/ +/*-- BFLIM.java - class for reading/parsing BFLIM images. --*/ +/*-- Note that this class is optimized around handling Gen 7 --*/ +/*-- Pokemon icons, and won't work for all types of BFLIMs --*/ +/*-- --*/ +/*-- Code based on "Switch Toolbox", copyright (C) KillzXGaming --*/ +/*-- --*/ +/*-- Ported to Java by UPR-ZX Team under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.FileFunctions; + +import java.awt.image.BufferedImage; + +public class BFLIM { + + private int width; + private int height; + private byte[] imageData; + private Header header; + private Image image; + + public BFLIM(byte[] bflimBytes) { + if (bflimBytes.length < 0x28) { + throw new IllegalArgumentException("Invalid BFLIM: not long enough to contain a header"); + } + header = new Header(bflimBytes); + image = new Image(bflimBytes); + width = image.width; + height = image.height; + imageData = new byte[image.imageSize]; + System.arraycopy(bflimBytes, 0, imageData, 0, image.imageSize); + } + + @SuppressWarnings("SuspiciousNameCombination") + public BufferedImage getImage() { + // Swap width and height, because the image is rendered on its side + int swappedWidth = height; + int swappedHeight = width; + int[] decodedImageData = decodeBlock(imageData, swappedWidth, swappedHeight); + int[] colorData = convertToColorData(decodedImageData); + int[] correctedColorData = rearrangeImage(colorData, width, height); + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int color = correctedColorData[x + (y * this.width)]; + image.setRGB(x, y, color); + } + } + return image; + } + + private static int[] SwizzleLUT = { + 0, 1, 8, 9, 2, 3, 10, 11, + 16, 17, 24, 25, 18, 19, 26, 27, + 4, 5, 12, 13, 6, 7, 14, 15, + 20, 21, 28, 29, 22, 23, 30, 31, + 32, 33, 40, 41, 34, 35, 42, 43, + 48, 49, 56, 57, 50, 51, 58, 59, + 36, 37, 44, 45, 38, 39, 46, 47, + 52, 53, 60, 61, 54, 55, 62, 63 + }; + + private int[] decodeBlock(byte[] data, int width, int height) { + int[] output = new int[width * height * 4]; + int inputOffset = 0; + for (int ty = 0; ty < height; ty += 8) { + for (int tx = 0; tx < width; tx += 8) { + for (int px = 0; px < 64; px++) { + int x = SwizzleLUT[px] & 7; + int y = (SwizzleLUT[px] - x) >> 3; + int outputOffset = (tx + x + ((height - 1 - (ty + y)) * width)) * 4; + int value = FileFunctions.read2ByteInt(data, inputOffset); + if (image.format == 7) { + decodeRGBA5551(output, outputOffset, value); + } else if (image.format == 8) { + decodeRGBA4(output, outputOffset, value); + } else { + throw new IllegalArgumentException("Unsupported BFLIM: unsupported image format"); + } + inputOffset += 2; + } + } + } + return output; + } + + private int[] convertToColorData(int[] decodedImageData) { + int[] output = new int[decodedImageData.length / 4]; + for (int i = 0; i < decodedImageData.length; i += 4) { + int a = decodedImageData[i]; + int b = decodedImageData[i + 1]; + int g = decodedImageData[i + 2]; + int r = decodedImageData[i + 3]; + int color = (a << 24) | (b << 16) | (g << 8) | r; + output[i / 4] = color; + } + return output; + } + + private int[] rearrangeImage(int[] colorData, int width, int height) { + int[] output = new int[colorData.length]; + for (int destY = 0; destY < height; destY++) { + for (int destX = 0; destX < width; destX++) { + int srcX = height - destY - 1; + int srcY = width - destX - 1; + int srcIndex = srcX + (srcY * height); + int destIndex = destX + (destY * width); + output[destIndex] = colorData[srcIndex]; + } + } + return output; + } + + private static void decodeRGBA5551(int[] output, int outputOffset, int value) { + int R = ((value >> 1) & 0x1f) << 3; + int G = ((value >> 6) & 0x1f) << 3; + int B = ((value >> 11) & 0x1f) << 3; + int A = (value & 1) * 0xFF; + R = R | (R >> 5); + G = G | (G >> 5); + B = B | (B >> 5); + output[outputOffset] = A; + output[outputOffset + 1] = B; + output[outputOffset + 2] = G; + output[outputOffset + 3] = R; + } + + private static void decodeRGBA4(int[] output, int outputOffset, int value) { + int R = ((value >> 4) & 0xf); + int G = ((value >> 8) & 0xf); + int B = ((value >> 12) & 0xf); + int A = (value & 1) | (value << 4); + R = R | (R << 4); + G = G | (G << 4); + B = B | (B << 4); + output[outputOffset] = A; + output[outputOffset + 1] = B; + output[outputOffset + 2] = G; + output[outputOffset + 3] = R; + } + + private class Header { + public int version; + + public Header(byte[] bflimBytes) { + int headerOffset = bflimBytes.length - 0x28; + int signature = FileFunctions.readFullIntBigEndian(bflimBytes, headerOffset); + if (signature != 0x464C494D) { + throw new IllegalArgumentException("Invalid BFLIM: cannot find FLIM header"); + } + boolean bigEndian = FileFunctions.read2ByteInt(bflimBytes, headerOffset + 4) == 0xFFFE; + if (bigEndian) { + throw new IllegalArgumentException("Unsupported BFLIM: this is a big endian BFLIM"); + } + int headerSize = FileFunctions.read2ByteInt(bflimBytes, headerOffset + 6); + if (headerSize != 0x14) { + throw new IllegalArgumentException("Invalid BFLIM: header length does not equal 0x14"); + } + version = FileFunctions.readFullInt(bflimBytes, headerOffset + 8); + } + } + + private class Image { + public int size; + public short width; + public short height; + public short alignment; + public byte format; + public byte flags; + public int imageSize; + + public Image(byte[] bflimBytes) { + int imageHeaderOffset = bflimBytes.length - 0x14; + int signature = FileFunctions.readFullIntBigEndian(bflimBytes, imageHeaderOffset); + if (signature != 0x696D6167) { + throw new IllegalArgumentException("Invalid BFLIM: cannot find imag header"); + } + size = FileFunctions.readFullInt(bflimBytes, imageHeaderOffset + 4); + width = (short) FileFunctions.read2ByteInt(bflimBytes, imageHeaderOffset + 8); + height = (short) FileFunctions.read2ByteInt(bflimBytes, imageHeaderOffset + 10); + alignment = (short) FileFunctions.read2ByteInt(bflimBytes, imageHeaderOffset + 12); + format = bflimBytes[imageHeaderOffset + 14]; + flags = bflimBytes[imageHeaderOffset + 15]; + imageSize = FileFunctions.readFullInt(bflimBytes, imageHeaderOffset + 16); + } + } +} diff --git a/src/com/pkrandom/ctr/GARCArchive.java b/src/com/pkrandom/ctr/GARCArchive.java new file mode 100644 index 0000000..cd504c4 --- /dev/null +++ b/src/com/pkrandom/ctr/GARCArchive.java @@ -0,0 +1,388 @@ +package com.pkrandom.ctr; + +/*----------------------------------------------------------------------------*/ +/*-- GARCArchive.java - class for packing/unpacking GARC archives --*/ +/*-- --*/ +/*-- Code based on "pk3DS", copyright (C) Kaphotics --*/ +/*-- --*/ +/*-- Ported to Java by UPR-ZX Team under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import cuecompressors.BLZCoder; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.*; + +public class GARCArchive { + + private final int VER_4 = 0x0400; + private final int VER_6 = 0x0600; + private int version; + private final int garcHeaderSize_4 = 0x1C; + private final int garcHeaderSize_6 = 0x24; + private final String garcMagic = "CRAG"; + private final String fatoMagic = "OTAF"; + private final String fatbMagic = "BTAF"; + private final String fimbMagic = "BMIF"; + private boolean skipDecompression = true; + + public List> files = new ArrayList<>(); + private Map isCompressed = new TreeMap<>(); + private List compressThese = null; + + private GARCFrame garc; + private FATOFrame fato; + private FATBFrame fatb; + private FIMBFrame fimb; + + public GARCArchive() { + + } + + public GARCArchive(byte[] data, boolean skipDecompression) throws IOException { + this.skipDecompression = skipDecompression; + boolean success = readFrames(data); + if (!success) { + throw new IOException("Invalid GARC file"); + } + files = fimb.files; + } + + public GARCArchive(byte[] data, List compressedThese) throws IOException { + this.compressThese = compressedThese; + boolean success = readFrames(data); + if (!success) { + throw new IOException("Invalid GARC file"); + } + files = fimb.files; + } + + private boolean readFrames(byte[] data) { + if (data.length <= 0) { + System.out.println("Empty GARC"); + return false; + } + ByteBuffer bbuf = ByteBuffer.wrap(data); + bbuf.order(ByteOrder.LITTLE_ENDIAN); + // GARC + byte[] magicBuf = new byte[4]; + bbuf.get(magicBuf); + String magic = new String(magicBuf); + if (!magic.equals(garcMagic)) { + return false; + } + garc = new GARCFrame(); + garc.headerSize = bbuf.getInt(); + garc.endianness = bbuf.getShort(); + garc.version = bbuf.getShort(); + int frameCount = bbuf.getInt(); + if (frameCount != 4) { + return false; + } + garc.dataOffset = bbuf.getInt(); + garc.fileSize = bbuf.getInt(); + if (garc.version == VER_4) { + garc.contentLargestUnpadded = bbuf.getInt(); + garc.contentPadToNearest = 4; + version = 4; + } else if (garc.version == VER_6) { + garc.contentLargestPadded = bbuf.getInt(); + garc.contentLargestUnpadded = bbuf.getInt(); + garc.contentPadToNearest = bbuf.getInt(); + version = 6; + } else { + return false; + } + + // FATO + fato = new FATOFrame(); + bbuf.get(magicBuf); + magic = new String(magicBuf); + if (!magic.equals(fatoMagic)) { + return false; + } + fato.headerSize = bbuf.getInt(); + fato.entryCount = bbuf.getShort(); + fato.padding = bbuf.getShort(); + fato.entries = new int[fato.entryCount]; + for (int i = 0; i < fato.entryCount; i++) { + fato.entries[i] = bbuf.getInt(); + } + + // FATB + fatb = new FATBFrame(); + bbuf.get(magicBuf); + magic = new String(magicBuf); + if (!magic.equals(fatbMagic)) { + return false; + } + fatb.headerSize = bbuf.getInt(); + fatb.fileCount = bbuf.getInt(); + fatb.entries = new FATBEntry[fatb.fileCount]; + for (int i = 0; i < fatb.fileCount; i++) { + fatb.entries[i] = new FATBEntry(); + fatb.entries[i].vector = bbuf.getInt(); + fatb.entries[i].subEntries = new TreeMap<>(); + int bitVector = fatb.entries[i].vector; + int counter = 0; + for (int b = 0; b < 32; b++) { + boolean exists = (bitVector & 1) == 1; + bitVector >>>= 1; + if (!exists) continue; + FATBSubEntry subEntry = new FATBSubEntry(); + subEntry.start = bbuf.getInt(); + subEntry.end = bbuf.getInt(); + subEntry.length = bbuf.getInt(); + fatb.entries[i].subEntries.put(b,subEntry); + counter++; + } + fatb.entries[i].isFolder = counter > 1; + } + + // FIMB + fimb = new FIMBFrame(); + bbuf.get(magicBuf); + magic = new String(magicBuf); + if (!magic.equals(fimbMagic)) { + return false; + } + fimb.headerSize = bbuf.getInt(); + fimb.dataSize = bbuf.getInt(); + fimb.files = new ArrayList<>(); + for (int i = 0; i < fatb.fileCount; i++) { + FATBEntry entry = fatb.entries[i]; + Map files = new TreeMap<>(); + for (int k: entry.subEntries.keySet()) { + FATBSubEntry subEntry = entry.subEntries.get(k); + bbuf.position(garc.dataOffset + subEntry.start); + byte[] file = new byte[subEntry.length]; + boolean compressed = compressThese == null ? + bbuf.get(bbuf.position()) == 0x11 && !skipDecompression : + bbuf.get(bbuf.position()) == 0x11 && compressThese.get(i); + bbuf.get(file); + if (compressed) { + try { + files.put(k,new BLZCoder(null).BLZ_DecodePub(file,"GARC")); + isCompressed.put(i,true); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } else { + files.put(k,file); + isCompressed.put(i,false); + } + } + fimb.files.add(files); + } + return true; + } + + public void updateFiles(List> files) { + fimb.files = files; + } + + public byte[] getBytes() throws IOException { + int garcHeaderSize = garc.version == VER_4 ? garcHeaderSize_4 : garcHeaderSize_6; + ByteBuffer garcBuf = ByteBuffer.allocate(garcHeaderSize); + garcBuf.order(ByteOrder.LITTLE_ENDIAN); + garcBuf.put(garcMagic.getBytes()); + garcBuf.putInt(garcHeaderSize); + garcBuf.putShort((short)0xFEFF); + garcBuf.putShort(version == 4 ? (short)VER_4 : (short)VER_6); + garcBuf.putInt(4); + + ByteBuffer fatoBuf = ByteBuffer.allocate(fato.headerSize); + fatoBuf.order(ByteOrder.LITTLE_ENDIAN); + fatoBuf.put(fatoMagic.getBytes()); + fatoBuf.putInt(fato.headerSize); + fatoBuf.putShort((short)fato.entryCount); + fatoBuf.putShort((short)fato.padding); + + ByteBuffer fatbBuf = ByteBuffer.allocate(fatb.headerSize); + fatbBuf.order(ByteOrder.LITTLE_ENDIAN); + fatbBuf.put(fatbMagic.getBytes()); + fatbBuf.putInt(fatb.headerSize); + fatbBuf.putInt(fatb.fileCount); + + ByteBuffer fimbHeaderBuf = ByteBuffer.allocate(fimb.headerSize); + fimbHeaderBuf.order(ByteOrder.LITTLE_ENDIAN); + fimbHeaderBuf.put(fimbMagic.getBytes()); + fimbHeaderBuf.putInt(fimb.headerSize); + + ByteArrayOutputStream fimbPayloadStream = new ByteArrayOutputStream(); // Unknown size, can't use ByteBuffer + + int fimbOffset = 0; + int largestSize = 0; + int largestPadded = 0; + for (int i = 0; i < fimb.files.size(); i++) { + Map directory = fimb.files.get(i); + int bitVector = 0; + int totalLength = 0; + for (int k: directory.keySet()) { + bitVector |= (1 << k); + byte[] file = directory.get(k); + if (isCompressed.get(i)) { + file = new BLZCoder(null).BLZ_EncodePub(file,false,false,"GARC"); + } + fimbPayloadStream.write(file); + totalLength += file.length; + } + + int paddingRequired = totalLength % garc.contentPadToNearest; + if (paddingRequired != 0) { + paddingRequired = garc.contentPadToNearest - paddingRequired; + } + + if (totalLength > largestSize) { + largestSize = totalLength; + } + if (totalLength + paddingRequired > largestPadded) { + largestPadded = totalLength + paddingRequired; + } + + for (int j = 0; j < paddingRequired; j++) { + fimbPayloadStream.write(fato.padding & 0xFF); + } + + fatoBuf.putInt(fatbBuf.position() - 12); + + fatbBuf.putInt(bitVector); + fatbBuf.putInt(fimbOffset); + fimbOffset = fimbPayloadStream.size(); + fatbBuf.putInt(fimbOffset); + fatbBuf.putInt(totalLength); + } + + int dataOffset = garcHeaderSize + fatoBuf.position() + fatbBuf.position() + fimb.headerSize; + garcBuf.putInt(dataOffset); + garcBuf.putInt(dataOffset + fimbOffset); + if (garc.version == VER_4) { + garcBuf.putInt(largestSize); + } else if (garc.version == VER_6) { + garcBuf.putInt(largestPadded); + garcBuf.putInt(largestSize); + garcBuf.putInt(garc.contentPadToNearest); + } + fimbHeaderBuf.putInt(fimbPayloadStream.size()); + + garcBuf.flip(); + fatoBuf.flip(); + fatbBuf.flip(); + fimbHeaderBuf.flip(); + + byte[] fullArray = new byte[garcBuf.limit() + fatoBuf.limit() + fatbBuf.limit() + fimbHeaderBuf.limit() + fimbPayloadStream.size()]; + System.arraycopy(garcBuf.array(), + 0, + fullArray, + 0, + garcBuf.limit()); + System.arraycopy(fatoBuf.array(), + 0, + fullArray, + garcBuf.limit(), + fatoBuf.limit()); + System.arraycopy(fatbBuf.array(), + 0, + fullArray, + garcBuf.limit()+fatoBuf.limit(), + fatbBuf.limit()); + System.arraycopy(fimbHeaderBuf.array(), + 0, + fullArray, + garcBuf.limit()+fatoBuf.limit()+fatbBuf.limit(), + fimbHeaderBuf.limit()); +// garcBuf.get(fullArray); +// fatoBuf.get(fullArray,garcBuf.limit(),fatoBuf.limit()); +// fatbBuf.get(fullArray,garcBuf.limit()+fatoBuf.limit(),fatbBuf.limit()); +// fimbHeaderBuf.get(fullArray,garcBuf.limit()+fatoBuf.limit()+fatbBuf.limit(),fimbHeaderBuf.limit()); + System.arraycopy(fimbPayloadStream.toByteArray(), + 0, + fullArray, + garcBuf.limit()+fatoBuf.limit()+fatbBuf.limit()+fimbHeaderBuf.limit(), + fimbPayloadStream.size()); + return fullArray; + } + + + + public byte[] getFile(int index) { + return fimb.files.get(index).get(0); + } + + public byte[] getFile(int index, int subIndex) { + return fimb.files.get(index).get(subIndex); + } + + public void setFile(int index, byte[] data) { + fimb.files.get(index).put(0,data); + } + + public Map getDirectory(int index) { + return fimb.files.get(index); + } + + private class GARCFrame { + int headerSize; + int endianness; + int version; + int dataOffset; + int fileSize; + + int contentLargestPadded; + int contentLargestUnpadded; + int contentPadToNearest; + } + + private class FATOFrame { + int headerSize; + int entryCount; + int padding; + + int[] entries; + } + + private class FATBFrame { + int headerSize; + int fileCount; + FATBEntry[] entries; + } + + private class FATBEntry { + int vector; + boolean isFolder; + Map subEntries; + } + + private class FATBSubEntry { + boolean exists; + int start; + int end; + int length; + int padding; + } + + private class FIMBFrame { + int headerSize; + int dataSize; + List> files; + } +} diff --git a/src/com/pkrandom/ctr/Mini.java b/src/com/pkrandom/ctr/Mini.java new file mode 100644 index 0000000..82daa08 --- /dev/null +++ b/src/com/pkrandom/ctr/Mini.java @@ -0,0 +1,102 @@ +package com.pkrandom.ctr; + +/*----------------------------------------------------------------------------*/ +/*-- Mini.java - class for packing/unpacking Mini archives --*/ +/*-- --*/ +/*-- Code based on "pk3DS", copyright (C) Kaphotics --*/ +/*-- --*/ +/*-- Ported to Java by UPR-ZX Team under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.FileFunctions; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class Mini { + public static byte[] PackMini(byte[][] fileData, String identifier) throws IOException { + // Create new Binary with the relevant header bytes + byte[] data = new byte[4]; + data[0] = (byte) identifier.charAt(0); + data[1] = (byte) identifier.charAt(1); + ByteBuffer buf = ByteBuffer.allocate(2); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putShort((short) fileData.length); + System.arraycopy(buf.array(), 0, data, 2, 2); + + int count = fileData.length; + int dataOffset = 4 + 4 + (count * 4); + + // Start the data filling + ByteArrayOutputStream dataOut = new ByteArrayOutputStream(); + ByteArrayOutputStream offsetMap = new ByteArrayOutputStream(); + // For each file... + for (int i = 0; i < count; i++) { + int fileOffset = dataOut.size() + dataOffset; + buf = ByteBuffer.allocate(4); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(fileOffset); + offsetMap.write(buf.array()); + dataOut.write(fileData[i]); + + // Pad with zeroes until len % 4 == 0 + while (dataOut.size() % 4 != 0) { + dataOut.write((byte) 0); + } + } + // Cap the file + buf = ByteBuffer.allocate(4); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(dataOut.size() + dataOffset); + offsetMap.write(buf.array()); + + ByteArrayOutputStream newPack = new ByteArrayOutputStream(); + ByteArrayOutputStream header = new ByteArrayOutputStream(); + header.write(data); + header.writeTo(newPack); + offsetMap.writeTo(newPack); + dataOut.writeTo(newPack); + return newPack.toByteArray(); + } + + public static byte[][] UnpackMini(byte[] fileData, String identifier) { + if (fileData == null || fileData.length < 4) { + return null; + } + + if (identifier.charAt(0) != fileData[0] || identifier.charAt(1) != fileData[1]) { + return null; + } + + int count = FileFunctions.read2ByteInt(fileData, 2); + int ctr = 4; + int start = FileFunctions.readFullInt(fileData, ctr); + ctr += 4; + byte[][] returnData = new byte[count][]; + for (int i = 0; i < count; i++) { + int end = FileFunctions.readFullInt(fileData, ctr); + ctr += 4; + int len = end - start; + byte[] data = new byte[len]; + System.arraycopy(fileData, start, data, 0, len); + returnData[i] = data; + start = end; + } + return returnData; + } +} diff --git a/src/com/pkrandom/ctr/NCCH.java b/src/com/pkrandom/ctr/NCCH.java new file mode 100644 index 0000000..9a326a5 --- /dev/null +++ b/src/com/pkrandom/ctr/NCCH.java @@ -0,0 +1,1024 @@ +package com.pkrandom.ctr; + +/*----------------------------------------------------------------------------*/ +/*-- NCCH.java - a base class for dealing with 3DS NCCH ROM images. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.FileFunctions; +import com.pkrandom.SysConstants; +import com.pkrandom.exceptions.CannotWriteToLocationException; +import com.pkrandom.exceptions.EncryptedROMException; +import com.pkrandom.exceptions.RandomizerIOException; +import cuecompressors.BLZCoder; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.*; +import java.util.*; + +public class NCCH { + private String romFilename; + private RandomAccessFile baseRom; + private long ncchStartingOffset; + private String productCode; + private String titleId; + private int version; + private long exefsOffset, romfsOffset, fileDataOffset; + private ExefsFileHeader codeFileHeader; + private SMDH smdh; + private List extraExefsFiles; + private List fileMetadataList; + private Map romfsFiles; + private boolean romOpen; + private String tmpFolder; + private boolean writingEnabled; + private boolean codeCompressed, codeOpen, codeChanged; + private byte[] codeRamstored; + + // Public so the base game can read it from the game update NCCH + public long originalCodeCRC, originalRomfsHeaderCRC; + + private static final int media_unit_size = 0x200; + private static final int header_and_exheader_size = 0xA00; + private static final int ncsd_magic = 0x4E435344; + private static final int cia_header_size = 0x2020; + private static final int ncch_magic = 0x4E434348; + private static final int ncch_and_ncsd_magic_offset = 0x100; + private static final int exefs_header_size = 0x200; + private static final int romfs_header_size = 0x5C; + private static final int romfs_magic_1 = 0x49564643; + private static final int romfs_magic_2 = 0x00000100; + private static final int level3_header_size = 0x28; + private static final int metadata_unused = 0xFFFFFFFF; + + public NCCH(String filename, String productCode, String titleId) throws IOException { + this.romFilename = filename; + this.baseRom = new RandomAccessFile(filename, "r"); + this.ncchStartingOffset = NCCH.getCXIOffsetInFile(filename); + this.productCode = productCode; + this.titleId = titleId; + this.romOpen = true; + + if (this.ncchStartingOffset != -1) { + this.version = this.readVersionFromFile(); + } + + // TMP folder? + String rawFilename = new File(filename).getName(); + String dataFolder = "tmp_" + rawFilename.substring(0, rawFilename.lastIndexOf('.')); + // remove nonsensical chars + dataFolder = dataFolder.replaceAll("[^A-Za-z0-9_]+", ""); + File tmpFolder = new File(SysConstants.ROOT_PATH + dataFolder); + tmpFolder.mkdirs(); + if (tmpFolder.canWrite()) { + writingEnabled = true; + this.tmpFolder = SysConstants.ROOT_PATH + dataFolder + File.separator; + tmpFolder.deleteOnExit(); + } else { + writingEnabled = false; + } + + // The below code handles things "wrong" with regards to encrypted ROMs. We just + // blindly treat the ROM as decrypted and try to parse all of its data, when we + // *should* be looking at the header of the ROM to determine if the ROM is encrypted. + // Unfortunately, many people have poorly-decrypted ROMs that do not properly set + // the bytes on the NCCH header, so we can't assume that the header is telling the + // truth. If we read the whole ROM without crashing, then it's probably decrypted. + try { + readFileSystem(); + } catch (Exception ex) { + if (!this.isDecrypted()) { + throw new EncryptedROMException(ex); + } else { + throw ex; + } + } + } + + public void reopenROM() throws IOException { + if (!this.romOpen) { + baseRom = new RandomAccessFile(this.romFilename, "r"); + romOpen = true; + } + } + + public void closeROM() throws IOException { + if (this.romOpen && baseRom != null) { + baseRom.close(); + baseRom = null; + romOpen = false; + } + } + + private void readFileSystem() throws IOException { + exefsOffset = ncchStartingOffset + FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x1A0) * media_unit_size; + romfsOffset = ncchStartingOffset + FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x1B0) * media_unit_size; + baseRom.seek(ncchStartingOffset + 0x20D); + byte systemControlInfoFlags = baseRom.readByte(); + codeCompressed = (systemControlInfoFlags & 0x01) != 0; + readExefs(); + readRomfs(); + } + + private void readExefs() throws IOException { + System.out.println("NCCH: Reading exefs..."); + byte[] exefsHeaderData = new byte[exefs_header_size]; + baseRom.seek(exefsOffset); + baseRom.readFully(exefsHeaderData); + + ExefsFileHeader[] fileHeaders = new ExefsFileHeader[10]; + for (int i = 0; i < 10; i++) { + fileHeaders[i] = new ExefsFileHeader(exefsHeaderData, i * 0x10); + } + + extraExefsFiles = new ArrayList<>(); + for (ExefsFileHeader fileHeader : fileHeaders) { + if (fileHeader.isValid() && fileHeader.filename.equals(".code")) { + codeFileHeader = fileHeader; + } else if (fileHeader.isValid()) { + extraExefsFiles.add(fileHeader); + } + + if (fileHeader.isValid() && fileHeader.filename.equals("icon")) { + byte[] smdhBytes = new byte[fileHeader.size]; + baseRom.seek(exefsOffset + 0x200 + fileHeader.offset); + baseRom.readFully(smdhBytes); + smdh = new SMDH(smdhBytes); + } + } + System.out.println("NCCH: Done reading exefs"); + } + + private void readRomfs() throws IOException { + System.out.println("NCCH: Reading romfs..."); + byte[] romfsHeaderData = new byte[romfs_header_size]; + baseRom.seek(romfsOffset); + baseRom.readFully(romfsHeaderData); + originalRomfsHeaderCRC = FileFunctions.getCRC32(romfsHeaderData); + int magic1 = FileFunctions.readFullIntBigEndian(romfsHeaderData, 0x00); + int magic2 = FileFunctions.readFullIntBigEndian(romfsHeaderData, 0x04); + if (magic1 != romfs_magic_1 || magic2 != romfs_magic_2) { + System.err.println("NCCH: romfs does not contain magic values"); + // Not a valid romfs + return; + } + int masterHashSize = FileFunctions.readFullInt(romfsHeaderData, 0x08); + int level3HashBlockSize = 1 << FileFunctions.readFullInt(romfsHeaderData, 0x4C); + long level3Offset = romfsOffset + alignLong(0x60 + masterHashSize, level3HashBlockSize); + + byte[] level3HeaderData = new byte[level3_header_size]; + baseRom.seek(level3Offset); + baseRom.readFully(level3HeaderData); + int headerLength = FileFunctions.readFullInt(level3HeaderData, 0x00); + if (headerLength != level3_header_size) { + // Not a valid romfs + System.err.println("NCCH: romfs does not have a proper level 3 header"); + return; + } + int directoryMetadataOffset = FileFunctions.readFullInt(level3HeaderData, 0x0C); + int directoryMetadataLength = FileFunctions.readFullInt(level3HeaderData, 0x10); + int fileMetadataOffset = FileFunctions.readFullInt(level3HeaderData, 0x1c); + int fileMetadataLength = FileFunctions.readFullInt(level3HeaderData, 0x20); + int fileDataOffsetFromHeaderStart = FileFunctions.readFullInt(level3HeaderData, 0x24); + fileDataOffset = level3Offset + fileDataOffsetFromHeaderStart; + + byte[] directoryMetadataBlock = new byte[directoryMetadataLength]; + baseRom.seek(level3Offset + directoryMetadataOffset); + baseRom.readFully(directoryMetadataBlock); + byte[] fileMetadataBlock = new byte[fileMetadataLength]; + baseRom.seek(level3Offset + fileMetadataOffset); + baseRom.readFully(fileMetadataBlock); + fileMetadataList = new ArrayList<>(); + romfsFiles = new TreeMap<>(); + visitDirectory(0, "", directoryMetadataBlock, fileMetadataBlock); + System.out.println("NCCH: Done reading romfs"); + } + + private void visitDirectory(int offset, String rootPath, byte[] directoryMetadataBlock, byte[] fileMetadataBlock) { + DirectoryMetadata metadata = new DirectoryMetadata(directoryMetadataBlock, offset); + String currentPath = rootPath; + if (!metadata.name.equals("")) { + currentPath = rootPath + metadata.name + "/"; + } + + if (metadata.firstFileOffset != metadata_unused) { + visitFile(metadata.firstFileOffset, currentPath, fileMetadataBlock); + } + if (metadata.firstChildDirectoryOffset != metadata_unused) { + visitDirectory(metadata.firstChildDirectoryOffset, currentPath, directoryMetadataBlock, fileMetadataBlock); + } + if (metadata.siblingDirectoryOffset != metadata_unused) { + visitDirectory(metadata.siblingDirectoryOffset, rootPath, directoryMetadataBlock, fileMetadataBlock); + } + } + + private void visitFile(int offset, String rootPath, byte[] fileMetadataBlock) { + FileMetadata metadata = new FileMetadata(fileMetadataBlock, offset); + String currentPath = rootPath + metadata.name; + System.out.println("NCCH: Visiting file " + currentPath); + RomfsFile file = new RomfsFile(this); + file.offset = fileDataOffset + metadata.fileDataOffset; + file.size = (int) metadata.fileDataLength; // no Pokemon game has a file larger than unsigned int max + file.fullPath = currentPath; + metadata.file = file; + fileMetadataList.add(metadata); + romfsFiles.put(currentPath, file); + if (metadata.siblingFileOffset != metadata_unused) { + visitFile(metadata.siblingFileOffset, rootPath, fileMetadataBlock); + } + } + + public void saveAsNCCH(String filename, String gameAcronym, long seed) throws IOException, NoSuchAlgorithmException { + this.reopenROM(); + + // Initialize new ROM + RandomAccessFile fNew = new RandomAccessFile(filename, "rw"); + + // Read the header and exheader and write it to the output ROM + byte[] header = new byte[header_and_exheader_size]; + baseRom.seek(ncchStartingOffset); + baseRom.readFully(header); + fNew.write(header); + + // Just in case they were set wrong in the original header, let's correctly set the + // bytes in the header to indicate the output ROM is decrypted + byte[] flags = new byte[8]; + baseRom.seek(ncchStartingOffset + 0x188); + baseRom.readFully(flags); + flags[3] = 0; + flags[7] = 4; + fNew.seek(0x188); + fNew.write(flags); + + // The logo is small enough (8KB) to just read the whole thing into memory. Write it to the new ROM directly + // after the header, then update the new ROM's logo offset + long logoOffset = ncchStartingOffset + FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x198) * media_unit_size; + long logoLength = FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x19C) * media_unit_size; + if (logoLength > 0) { + byte[] logo = new byte[(int) logoLength]; + baseRom.seek(logoOffset); + baseRom.readFully(logo); + long newLogoOffset = header_and_exheader_size; + fNew.seek(newLogoOffset); + fNew.write(logo); + fNew.seek(0x198); + fNew.write((int) newLogoOffset / media_unit_size); + } + + // The plain region is even smaller (1KB) so repeat the same process + long plainOffset = ncchStartingOffset + FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x190) * media_unit_size; + long plainLength = FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x194) * media_unit_size; + if (plainLength > 0) { + byte[] plain = new byte[(int) plainLength]; + baseRom.seek(plainOffset); + baseRom.readFully(plain); + long newPlainOffset = header_and_exheader_size + logoLength; + fNew.seek(newPlainOffset); + fNew.write(plain); + fNew.seek(0x190); + fNew.write((int) newPlainOffset / media_unit_size); + } + + // Update the SMDH so that Citra displays the seed in the title + smdh.setAllDescriptions(gameAcronym + " randomizer seed: " + seed); + smdh.setAllPublishers("Universal Pokemon Randomizer ZX"); + + // Now, reconstruct the exefs based on our new version of .code and our new SMDH + long newExefsOffset = header_and_exheader_size + logoLength + plainLength; + long newExefsLength = rebuildExefs(fNew, newExefsOffset); + fNew.seek(0x1A0); + fNew.write((int) newExefsOffset / media_unit_size); + fNew.seek(0x1A4); + fNew.write((int) newExefsLength / media_unit_size); + + // Then, reconstruct the romfs + // TODO: Fix the yet-unsolved alignment issues in rebuildRomfs when you remove this align + long newRomfsOffset = alignLong(header_and_exheader_size + logoLength + plainLength + newExefsLength, 4096); + long newRomfsLength = rebuildRomfs(fNew, newRomfsOffset); + fNew.seek(0x1B0); + fNew.write((int) newRomfsOffset / media_unit_size); + fNew.seek(0x1B4); + fNew.write((int) newRomfsLength / media_unit_size); + + // Lastly, reconstruct the superblock hashes + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + int exefsHashRegionSize = FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x1A8) * media_unit_size; + byte[] exefsDataToHash = new byte[exefsHashRegionSize]; + fNew.seek(newExefsOffset); + fNew.readFully(exefsDataToHash); + byte[] exefsSuperblockHash = digest.digest(exefsDataToHash); + fNew.seek(0x1C0); + fNew.write(exefsSuperblockHash); + int romfsHashRegionSize = FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x1B8) * media_unit_size; + byte[] romfsDataToHash = new byte[romfsHashRegionSize]; + fNew.seek(newRomfsOffset); + fNew.readFully(romfsDataToHash); + byte[] romfsSuperblockHash = digest.digest(romfsDataToHash); + fNew.seek(0x1E0); + fNew.write(romfsSuperblockHash); + + // While totally optional, let's zero out the NCCH signature so that + // it's clear this isn't a properly-signed ROM + byte[] zeroedSignature = new byte[0x100]; + fNew.seek(0x0); + fNew.write(zeroedSignature); + fNew.close(); + } + + private long rebuildExefs(RandomAccessFile fNew, long newExefsOffset) throws IOException, NoSuchAlgorithmException { + System.out.println("NCCH: Rebuilding exefs..."); + byte[] code = getCode(); + if (codeCompressed) { + code = new BLZCoder(null).BLZ_EncodePub(code, false, true, ".code"); + } + + // Create a new ExefsFileHeader for our updated .code + ExefsFileHeader newCodeHeader = new ExefsFileHeader(); + newCodeHeader.filename = codeFileHeader.filename; + newCodeHeader.size = code.length; + newCodeHeader.offset = 0; + + // For all the file headers, write them to the new ROM and store them in order for hashing later + ExefsFileHeader[] newHeaders = new ExefsFileHeader[10]; + newHeaders[0] = newCodeHeader; + fNew.seek(newExefsOffset); + fNew.write(newCodeHeader.asBytes()); + for (int i = 0; i < extraExefsFiles.size(); i++) { + ExefsFileHeader header = extraExefsFiles.get(i); + newHeaders[i + 1] = header; + fNew.write(header.asBytes()); + } + + // Write the file data, then hash the data and write the hashes in reverse order + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + long endingOffset = 0; + for (int i = 0; i < newHeaders.length; i++) { + ExefsFileHeader header = newHeaders[i]; + if (header != null) { + byte[] data; + if (header.filename.equals(".code")) { + data = code; + } else if (header.filename.equals("icon")) { + data = smdh.getBytes(); + } else { + long dataOffset = exefsOffset + 0x200 + header.offset; + data = new byte[header.size]; + baseRom.seek(dataOffset); + baseRom.readFully(data); + } + fNew.seek(newExefsOffset + 0x200 + header.offset); + fNew.write(data); + byte[] hash = digest.digest(data); + fNew.seek(newExefsOffset + 0x200 - ((i + 1) * 0x20)); + fNew.write(hash); + endingOffset = newExefsOffset + 0x200 + header.offset + header.size; + } + } + + // Pad to media unit size + fNew.seek(endingOffset); + long exefsLength = endingOffset - newExefsOffset; + while (exefsLength % media_unit_size != 0) { + fNew.writeByte(0); + exefsLength++; + } + + System.out.println("NCCH: Done rebuilding exefs"); + return exefsLength; + } + + private long rebuildRomfs(RandomAccessFile fNew, long newRomfsOffset) throws IOException, NoSuchAlgorithmException { + System.out.println("NCCH: Rebuilding romfs..."); + + // Start by copying the romfs header straight from the original ROM. We'll update the + // header as we continue to build the romfs + byte[] romfsHeaderData = new byte[romfs_header_size]; + baseRom.seek(romfsOffset); + baseRom.readFully(romfsHeaderData); + fNew.seek(newRomfsOffset); + fNew.write(romfsHeaderData); + + // Now find the level 3 (file data) offset, since the first thing we need to do is write the + // updated file data. We're assuming here that the master hash size is smaller than the level 3 + // hash block size, which it almost certainly will because we're not adding large amounts of data + // to the romfs + int masterHashSize = FileFunctions.readFullInt(romfsHeaderData, 0x08); + int level3HashBlockSize = 1 << FileFunctions.readFullInt(romfsHeaderData, 0x4C); + long level3Offset = romfsOffset + alignLong(0x60 + masterHashSize, level3HashBlockSize); + long newLevel3Offset = newRomfsOffset + alignLong(0x60 + masterHashSize, level3HashBlockSize); + + // Copy the level 3 header straight from the original ROM. Since we're not adding or + // removing any files, the File/Directory tables should have the same offsets and lengths + byte[] level3HeaderData = new byte[level3_header_size]; + baseRom.seek(level3Offset); + baseRom.readFully(level3HeaderData); + fNew.seek(newLevel3Offset); + fNew.write(level3HeaderData); + + // Write out both hash tables and the directory metadata table. Since we're not adding or removing + // any files/directories, we can just use what's in the base ROM for this. + int directoryHashTableOffset = FileFunctions.readFullInt(level3HeaderData, 0x04); + int directoryHashTableLength = FileFunctions.readFullInt(level3HeaderData, 0x08); + int directoryMetadataTableOffset = FileFunctions.readFullInt(level3HeaderData, 0x0C); + int directoryMetadataTableLength = FileFunctions.readFullInt(level3HeaderData, 0x10); + int fileHashTableOffset = FileFunctions.readFullInt(level3HeaderData, 0x14); + int fileHashTableLength = FileFunctions.readFullInt(level3HeaderData, 0x18); + byte[] directoryHashTable = new byte[directoryHashTableLength]; + baseRom.seek(level3Offset + directoryHashTableOffset); + baseRom.readFully(directoryHashTable); + fNew.seek(newLevel3Offset + directoryHashTableOffset); + fNew.write(directoryHashTable); + byte[] directoryMetadataTable = new byte[directoryMetadataTableLength]; + baseRom.seek(level3Offset + directoryMetadataTableOffset); + baseRom.readFully(directoryMetadataTable); + fNew.seek(newLevel3Offset + directoryMetadataTableOffset); + fNew.write(directoryMetadataTable); + byte[] fileHashTable = new byte[fileHashTableLength]; + baseRom.seek(level3Offset + fileHashTableOffset); + baseRom.readFully(fileHashTable); + fNew.seek(newLevel3Offset + fileHashTableOffset); + fNew.write(fileHashTable); + + // Now reconstruct the file metadata table. It may need to be changed if any file grew or shrunk + int fileMetadataTableOffset = FileFunctions.readFullInt(level3HeaderData, 0x1C); + int fileMetadataTableLength = FileFunctions.readFullInt(level3HeaderData, 0x20); + byte[] newFileMetadataTable = updateFileMetadataTable(fileMetadataTableLength); + fNew.seek(newLevel3Offset + fileMetadataTableOffset); + fNew.write(newFileMetadataTable); + + // Using the new file metadata table, output the file data + int fileDataOffset = FileFunctions.readFullInt(level3HeaderData, 0x24); + long endOfFileDataOffset = 0; + for (FileMetadata metadata : fileMetadataList) { + System.out.println("NCCH: Writing file " + metadata.file.fullPath + " to romfs"); + // Users have sent us bug reports with really bizarre errors here that seem to indicate + // broken metadata; do this in a try-catch solely so we can log the metadata if we fail + try { + byte[] fileData; + if (metadata.file.fileChanged) { + fileData = metadata.file.getOverrideContents(); + } else { + fileData = new byte[metadata.file.size]; + baseRom.seek(metadata.file.offset); + baseRom.readFully(fileData); + } + long currentDataOffset = newLevel3Offset + fileDataOffset + metadata.fileDataOffset; + fNew.seek(currentDataOffset); + fNew.write(fileData); + endOfFileDataOffset = currentDataOffset + fileData.length; + } catch (Exception e) { + String message = String.format("Error when building romfs: File: %s, offset: %s, size: %s", + metadata.file.fullPath, metadata.offset, metadata.file.size); + throw new RandomizerIOException(message, e); + } + } + + // Now that level 3 (file data) is done, construct level 2 (hashes of file data) + // Note that in the ROM, level 1 comes *before* level 2, so we need to calculate + // level 1 length and offset as well. + long newLevel3EndingOffset = endOfFileDataOffset; + long newLevel3HashdataSize = newLevel3EndingOffset - newLevel3Offset; + long numberOfLevel3HashBlocks = alignLong(newLevel3HashdataSize, level3HashBlockSize) / level3HashBlockSize; + int level2HashBlockSize = 1 << FileFunctions.readFullInt(romfsHeaderData, 0x34); + long newLevel2HashdataSize = numberOfLevel3HashBlocks * 0x20; + long numberOfLevel2HashBlocks = alignLong(newLevel2HashdataSize, level2HashBlockSize) / level2HashBlockSize; + int level1HashBlockSize = 1 << FileFunctions.readFullInt(romfsHeaderData, 0x1C); + long newLevel1HashdataSize = numberOfLevel2HashBlocks * 0x20; + long newLevel1Offset = newLevel3Offset + alignLong(newLevel3HashdataSize, level3HashBlockSize); + long newLevel2Offset = newLevel1Offset + alignLong(newLevel1HashdataSize, level1HashBlockSize); + long newFileEndingOffset = alignLong(newLevel2Offset + newLevel2HashdataSize, level2HashBlockSize); + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] dataToHash = new byte[level3HashBlockSize]; + for (long i = 0; i < numberOfLevel3HashBlocks; i++) { + fNew.seek(newLevel3Offset + (i * level3HashBlockSize)); + fNew.readFully(dataToHash); + byte[] hash = digest.digest(dataToHash); + fNew.seek(newLevel2Offset + (i * 0x20)); + fNew.write(hash); + } + while (fNew.getFilePointer() != newFileEndingOffset) { + fNew.writeByte(0); + } + + // Now that level 2 (hashes of file data) is done, construct level 1 (hashes of + // hashes of file data) and the master hash/level 0 (hashes of level 1) + dataToHash = new byte[level2HashBlockSize]; + for (long i = 0; i < numberOfLevel2HashBlocks; i++) { + fNew.seek(newLevel2Offset + (i * level2HashBlockSize)); + fNew.readFully(dataToHash); + byte[] hash = digest.digest(dataToHash); + fNew.seek(newLevel1Offset + (i * 0x20)); + fNew.write(hash); + } + long numberOfLevel1HashBlocks = alignLong(newLevel1HashdataSize, level1HashBlockSize) / level1HashBlockSize; + dataToHash = new byte[level1HashBlockSize]; + for (long i = 0; i < numberOfLevel1HashBlocks; i++) { + fNew.seek(newLevel1Offset + (i * level1HashBlockSize)); + fNew.readFully(dataToHash); + byte[] hash = digest.digest(dataToHash); + fNew.seek(newRomfsOffset + 0x60 + (i * 0x20)); + fNew.write(hash); + } + + // Lastly, update the header and return the size of the new romfs + long level1LogicalOffset = 0; + long level2LogicalOffset = alignLong(newLevel1HashdataSize, level1HashBlockSize); + long level3LogicalOffset = alignLong(level2LogicalOffset + newLevel2HashdataSize, level2HashBlockSize); + FileFunctions.writeFullInt(romfsHeaderData, 0x08, (int) numberOfLevel1HashBlocks * 0x20); + FileFunctions.writeFullLong(romfsHeaderData, 0x0C, level1LogicalOffset); + FileFunctions.writeFullLong(romfsHeaderData, 0x14, newLevel1HashdataSize); + FileFunctions.writeFullLong(romfsHeaderData, 0x24, level2LogicalOffset); + FileFunctions.writeFullLong(romfsHeaderData, 0x2C, newLevel2HashdataSize); + FileFunctions.writeFullLong(romfsHeaderData, 0x3C, level3LogicalOffset); + FileFunctions.writeFullLong(romfsHeaderData, 0x44, newLevel3HashdataSize); + fNew.seek(newRomfsOffset); + fNew.write(romfsHeaderData); + long currentLength = newFileEndingOffset - newRomfsOffset; + long newRomfsLength = alignLong(currentLength, media_unit_size); + fNew.seek(newFileEndingOffset); + while (fNew.getFilePointer() < newRomfsOffset + newRomfsLength) { + fNew.writeByte(0); + } + + System.out.println("NCCH: Done rebuilding romfs"); + return newRomfsLength; + } + + private byte[] updateFileMetadataTable(int fileMetadataTableLength) { + fileMetadataList.sort((FileMetadata f1, FileMetadata f2) -> (int) (f1.fileDataOffset - f2.fileDataOffset)); + byte[] fileMetadataTable = new byte[fileMetadataTableLength]; + int currentTableOffset = 0; + long currentFileDataOffset = 0; + for (FileMetadata metadata : fileMetadataList) { + metadata.fileDataOffset = currentFileDataOffset; + if (metadata.file.fileChanged) { + metadata.fileDataLength = metadata.file.size; + } + byte[] metadataBytes = metadata.asBytes(); + System.arraycopy(metadataBytes, 0, fileMetadataTable, currentTableOffset, metadataBytes.length); + currentTableOffset += metadataBytes.length; + currentFileDataOffset += metadata.fileDataLength; + } + return fileMetadataTable; + } + + public void saveAsLayeredFS(String outputPath) throws IOException { + String layeredFSRootPath = outputPath + File.separator + titleId + File.separator; + File layeredFSRootDir = new File(layeredFSRootPath); + if (!layeredFSRootDir.exists()) { + layeredFSRootDir.mkdirs(); + } else { + purgeDirectory(layeredFSRootDir); + } + String romfsRootPath = layeredFSRootPath + "romfs" + File.separator; + File romfsDir = new File(romfsRootPath); + if (!romfsDir.exists()) { + romfsDir.mkdirs(); + } + + if (codeChanged) { + byte[] code = getCode(); + FileOutputStream fos = new FileOutputStream(new File(layeredFSRootPath + "code.bin")); + fos.write(code); + fos.close(); + } + + for (Map.Entry entry : romfsFiles.entrySet()) { + RomfsFile file = entry.getValue(); + if (file.fileChanged) { + writeRomfsFileToLayeredFS(file, romfsRootPath); + } + } + } + + private void purgeDirectory(File directory) { + for (File file : directory.listFiles()) { + if (file.isDirectory()) { + purgeDirectory(file); + } + file.delete(); + } + } + + private void writeRomfsFileToLayeredFS(RomfsFile file, String layeredFSRootPath) throws IOException { + String[] romfsPathComponents = file.fullPath.split("/"); + StringBuffer buffer = new StringBuffer(layeredFSRootPath); + for (int i = 0; i < romfsPathComponents.length - 1; i++) { + buffer.append(romfsPathComponents[i]); + buffer.append(File.separator); + File currentDir = new File(buffer.toString()); + if (!currentDir.exists()) { + currentDir.mkdirs(); + } + } + buffer.append(romfsPathComponents[romfsPathComponents.length - 1]); + String romfsFilePath = buffer.toString(); + FileOutputStream fos = new FileOutputStream(new File(romfsFilePath)); + fos.write(file.getOverrideContents()); + fos.close(); + } + + public boolean isDecrypted() throws IOException { + // This is the way you're *supposed* to tell if a ROM is decrypted. Specifically, this + // is checking the noCrypto flag on the NCCH bitflags. + long ncchFlagOffset = ncchStartingOffset + 0x188; + byte[] ncchFlags = new byte[8]; + baseRom.seek(ncchFlagOffset); + baseRom.readFully(ncchFlags); + if ((ncchFlags[7] & 0x4) != 0) { + return true; + } + + // However, some poorly-decrypted ROMs don't set this flag. So our heuristic for detecting + // if they're decrypted is to check whether the battle CRO exists, since all 3DS Pokemon + // games and updates have this file. If the game is *really* encrypted, then the odds of us + // successfully extracting this exact name from the metadata tables is like one in a billion. + return romfsFiles != null && (romfsFiles.containsKey("DllBattle.cro") || romfsFiles.containsKey("Battle.cro")); + } + + // Retrieves a decompressed version of .code (the game's executable). + // The first time this is called, it will retrieve it straight from the + // exefs. Future calls will rely on a cached version to speed things up. + // If writing is enabled, it will cache the decompressed version to the + // tmpFolder; otherwise, it will store it in RAM. + public byte[] getCode() throws IOException { + if (!codeOpen) { + codeOpen = true; + byte[] code = new byte[codeFileHeader.size]; + + // File header offsets are from the start of the exefs but *exclude* the + // size of the exefs header, so we need to add it back ourselves. + baseRom.seek(exefsOffset + exefs_header_size + codeFileHeader.offset); + baseRom.readFully(code); + originalCodeCRC = FileFunctions.getCRC32(code); + + if (codeCompressed) { + code = new BLZCoder(null).BLZ_DecodePub(code, ".code"); + } + + // Now actually make the copy or w/e + if (writingEnabled) { + File arm9file = new File(tmpFolder + ".code"); + FileOutputStream fos = new FileOutputStream(arm9file); + fos.write(code); + fos.close(); + arm9file.deleteOnExit(); + this.codeRamstored = null; + return code; + } else { + this.codeRamstored = code; + byte[] newcopy = new byte[code.length]; + System.arraycopy(code, 0, newcopy, 0, code.length); + return newcopy; + } + } else { + if (writingEnabled) { + return FileFunctions.readFileFullyIntoBuffer(tmpFolder + ".code"); + } else { + byte[] newcopy = new byte[this.codeRamstored.length]; + System.arraycopy(this.codeRamstored, 0, newcopy, 0, this.codeRamstored.length); + return newcopy; + } + } + } + + public void writeCode(byte[] code) throws IOException { + if (!codeOpen) { + getCode(); + } + codeChanged = true; + if (writingEnabled) { + FileOutputStream fos = new FileOutputStream(new File(tmpFolder + ".code")); + fos.write(code); + fos.close(); + } else { + if (this.codeRamstored.length == code.length) { + // copy new in + System.arraycopy(code, 0, this.codeRamstored, 0, code.length); + } else { + // make new array + this.codeRamstored = null; + this.codeRamstored = new byte[code.length]; + System.arraycopy(code, 0, this.codeRamstored, 0, code.length); + } + } + } + + public boolean hasFile(String filename) { + return romfsFiles.containsKey(filename); + } + + // returns null if file doesn't exist + public byte[] getFile(String filename) throws IOException { + if (romfsFiles.containsKey(filename)) { + return romfsFiles.get(filename).getContents(); + } else { + return null; + } + } + + public void writeFile(String filename, byte[] data) throws IOException { + if (romfsFiles.containsKey(filename)) { + romfsFiles.get(filename).writeOverride(data); + } + } + + public void printRomDiagnostics(PrintStream logStream, NCCH gameUpdate) { + Path p = Paths.get(this.romFilename); + logStream.println("File name: " + p.getFileName().toString()); + if (gameUpdate == null) { + logStream.println(".code: " + String.format("%08X", this.originalCodeCRC)); + } else { + logStream.println(".code: " + String.format("%08X", gameUpdate.originalCodeCRC)); + } + logStream.println("romfs header: " + String.format("%08X", this.originalRomfsHeaderCRC)); + if (gameUpdate != null) { + logStream.println("romfs header (game update): " + String.format("%08X", gameUpdate.originalRomfsHeaderCRC)); + } + List fileList = new ArrayList<>(); + Map baseRomfsFileDiagnostics = this.getRomfsFilesDiagnostics(); + Map updateRomfsFileDiagnostics = new HashMap<>(); + if (gameUpdate != null) { + updateRomfsFileDiagnostics = gameUpdate.getRomfsFilesDiagnostics(); + } + for (Map.Entry entry : updateRomfsFileDiagnostics.entrySet()) { + baseRomfsFileDiagnostics.remove(entry.getKey()); + fileList.add(entry.getValue()); + } + for (Map.Entry entry : baseRomfsFileDiagnostics.entrySet()) { + fileList.add(entry.getValue()); + } + Collections.sort(fileList); + for (String fileLog : fileList) { + logStream.println(fileLog); + } + } + + public Map getRomfsFilesDiagnostics() { + Map fileDiagnostics = new HashMap<>(); + for (Map.Entry entry : romfsFiles.entrySet()) { + if (entry.getValue().originalCRC != 0) { + fileDiagnostics.put(entry.getKey(), entry.getKey() + ": " + String.format("%08X", entry.getValue().originalCRC)); + } + } + return fileDiagnostics; + } + + public String getTmpFolder() { + return tmpFolder; + } + + public RandomAccessFile getBaseRom() { + return baseRom; + } + + public boolean isWritingEnabled() { + return writingEnabled; + } + + public String getProductCode() { + return productCode; + } + + public String getTitleId() { + return titleId; + } + + public int getVersion() { + return version; + } + + public static int alignInt(int num, int alignment) { + int mask = ~(alignment - 1); + return (num + (alignment - 1)) & mask; + } + + public static long alignLong(long num, long alignment) { + long mask = ~(alignment - 1); + return (num + (alignment - 1)) & mask; + } + + private int readVersionFromFile() { + try { + // Only CIAs can define a version in their TMD. If this is a different ROM type, + // just exit out early. + int magic = FileFunctions.readBigEndianIntFromFile(this.baseRom, ncch_and_ncsd_magic_offset); + if (magic == ncch_magic || magic == ncsd_magic) { + return 0; + } + + // For CIAs, we need to read the title metadata (TMD) in order to retrieve the version. + // The TMD is after the certificate chain and ticket. + int certChainSize = FileFunctions.readIntFromFile(this.baseRom, 0x08); + int ticketSize = FileFunctions.readIntFromFile(this.baseRom, 0x0C); + long certChainOffset = NCCH.alignLong(cia_header_size, 64); + long ticketOffset = NCCH.alignLong(certChainOffset + certChainSize, 64); + long tmdOffset = NCCH.alignLong(ticketOffset + ticketSize, 64); + + // At the start of the TMD is a signature whose length varies based on what type of signature it is. + int signatureType = FileFunctions.readBigEndianIntFromFile(this.baseRom, tmdOffset); + int signatureSize, paddingSize; + switch (signatureType) { + case 0x010003: + signatureSize = 0x200; + paddingSize = 0x3C; + break; + case 0x010004: + signatureSize = 0x100; + paddingSize = 0x3C; + break; + case 0x010005: + signatureSize = 0x3C; + paddingSize = 0x40; + break; + default: + signatureSize = -1; + paddingSize = -1; + break; + } + if (signatureSize == -1) { + // This shouldn't happen in practice, since all used and valid signature types are represented + // in the above switch. However, if we can't find the right signature type, then it's probably + // an invalid CIA anyway, so we're unlikely to get good version information out of it. + return 0; + } + + // After the signature is the TMD header, which actually contains the version information. + long tmdHeaderOffset = tmdOffset + 4 + signatureSize + paddingSize; + return FileFunctions.read2ByteBigEndianIntFromFile(this.baseRom, tmdHeaderOffset + 0x9C); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + // At the bare minimum, a 3DS game consists of what's known as a CXI file, which + // is just an NCCH that contains executable code. However, 3DS games are packaged + // in various containers that can hold other NCCH files like the game manual and + // firmware updates, among other things. This function's determines the location + // of the CXI regardless of the container. + public static long getCXIOffsetInFile(String filename) { + try { + RandomAccessFile rom = new RandomAccessFile(filename, "r"); + int ciaHeaderSize = FileFunctions.readIntFromFile(rom, 0x00); + if (ciaHeaderSize == cia_header_size) { + // This *might* be a CIA; let's do our best effort to try to get + // a CXI out of this. + int certChainSize = FileFunctions.readIntFromFile(rom, 0x08); + int ticketSize = FileFunctions.readIntFromFile(rom, 0x0C); + int tmdFileSize = FileFunctions.readIntFromFile(rom, 0x10); + + // If this is *really* a CIA, we'll find our CXI at the beginning of the + // content section, which is after the certificate chain, ticket, and TMD + long certChainOffset = NCCH.alignLong(ciaHeaderSize, 64); + long ticketOffset = NCCH.alignLong(certChainOffset + certChainSize, 64); + long tmdOffset = NCCH.alignLong(ticketOffset + ticketSize, 64); + long contentOffset = NCCH.alignLong(tmdOffset + tmdFileSize, 64); + int magic = FileFunctions.readBigEndianIntFromFile(rom, contentOffset + ncch_and_ncsd_magic_offset); + if (magic == ncch_magic) { + // This CIA's content contains a valid CXI! + return contentOffset; + } + } + + // We don't put the following code in an else-block because there *might* + // exist a totally-valid CXI or CCI whose first four bytes just so + // *happen* to be the same as the first four bytes of a CIA file. + int magic = FileFunctions.readBigEndianIntFromFile(rom, ncch_and_ncsd_magic_offset); + rom.close(); + if (magic == ncch_magic) { + // Magic is NCCH, so this just a straight-up NCCH/CXI; there is no container + // around the game data. Thus, the CXI offset is the beginning of the file. + return 0; + } else if (magic == ncsd_magic) { + // Magic is NCSD, so this is almost certainly a CCI. The CXI is always + // a fixed distance away from the start. + return 0x4000; + } else { + // This doesn't seem to be a valid 3DS file. + return -1; + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private class ExefsFileHeader { + public String filename; + public int offset; + public int size; + + public ExefsFileHeader() { } + + public ExefsFileHeader(byte[] exefsHeaderData, int fileHeaderOffset) { + byte[] filenameBytes = new byte[0x8]; + System.arraycopy(exefsHeaderData, fileHeaderOffset, filenameBytes, 0, 0x8); + this.filename = new String(filenameBytes, StandardCharsets.UTF_8).trim(); + this.offset = FileFunctions.readFullInt(exefsHeaderData, fileHeaderOffset + 0x08); + this.size = FileFunctions.readFullInt(exefsHeaderData, fileHeaderOffset + 0x0C); + } + + public boolean isValid() { + return this.filename != "" && this.size != 0; + } + + public byte[] asBytes() { + byte[] output = new byte[0x10]; + byte[] filenameBytes = this.filename.getBytes(StandardCharsets.UTF_8); + System.arraycopy(filenameBytes, 0, output, 0, filenameBytes.length); + FileFunctions.writeFullInt(output, 0x08, this.offset); + FileFunctions.writeFullInt(output, 0x0C, this.size); + return output; + } + } + + private class DirectoryMetadata { + public int parentDirectoryOffset; + public int siblingDirectoryOffset; + public int firstChildDirectoryOffset; + public int firstFileOffset; + public int nextDirectoryInHashBucketOffset; + public int nameLength; + public String name; + + public DirectoryMetadata(byte[] directoryMetadataBlock, int offset) { + parentDirectoryOffset = FileFunctions.readFullInt(directoryMetadataBlock, offset); + siblingDirectoryOffset = FileFunctions.readFullInt(directoryMetadataBlock, offset + 0x04); + firstChildDirectoryOffset = FileFunctions.readFullInt(directoryMetadataBlock, offset + 0x08); + firstFileOffset = FileFunctions.readFullInt(directoryMetadataBlock, offset + 0x0C); + nextDirectoryInHashBucketOffset = FileFunctions.readFullInt(directoryMetadataBlock, offset + 0x10); + nameLength = FileFunctions.readFullInt(directoryMetadataBlock, offset + 0x14); + name = ""; + if (nameLength != metadata_unused) { + byte[] nameBytes = new byte[nameLength]; + System.arraycopy(directoryMetadataBlock, offset + 0x18, nameBytes, 0, nameLength); + name = new String(nameBytes, StandardCharsets.UTF_16LE).trim(); + } + } + } + + private class FileMetadata { + public int offset; + public int parentDirectoryOffset; + public int siblingFileOffset; + public long fileDataOffset; + public long fileDataLength; + public int nextFileInHashBucketOffset; + public int nameLength; + public String name; + public RomfsFile file; // used only for rebuilding CXI + + public FileMetadata(byte[] fileMetadataBlock, int offset) { + this.offset = offset; + parentDirectoryOffset = FileFunctions.readFullInt(fileMetadataBlock, offset); + siblingFileOffset = FileFunctions.readFullInt(fileMetadataBlock, offset + 0x04); + fileDataOffset = FileFunctions.readFullLong(fileMetadataBlock, offset + 0x08); + fileDataLength = FileFunctions.readFullLong(fileMetadataBlock, offset + 0x10); + nextFileInHashBucketOffset = FileFunctions.readFullInt(fileMetadataBlock, offset + 0x18); + nameLength = FileFunctions.readFullInt(fileMetadataBlock, offset + 0x1C); + name = ""; + if (nameLength != metadata_unused) { + byte[] nameBytes = new byte[nameLength]; + System.arraycopy(fileMetadataBlock, offset + 0x20, nameBytes, 0, nameLength); + name = new String(nameBytes, StandardCharsets.UTF_16LE).trim(); + } + } + + public byte[] asBytes() { + int metadataLength = 0x20; + if (nameLength != metadata_unused) { + metadataLength += alignInt(nameLength, 4); + } + byte[] output = new byte[metadataLength]; + FileFunctions.writeFullInt(output, 0x00, this.parentDirectoryOffset); + FileFunctions.writeFullInt(output, 0x04, this.siblingFileOffset); + FileFunctions.writeFullLong(output, 0x08, this.fileDataOffset); + FileFunctions.writeFullLong(output, 0x10, this.fileDataLength); + FileFunctions.writeFullInt(output, 0x18, this.nextFileInHashBucketOffset); + FileFunctions.writeFullInt(output, 0x1C, this.nameLength); + if (!name.equals("")) { + byte[] nameBytes = name.getBytes(StandardCharsets.UTF_16LE); + System.arraycopy(nameBytes, 0, output, 0x20, nameBytes.length); + } + return output; + } + } +} diff --git a/src/com/pkrandom/ctr/RomfsFile.java b/src/com/pkrandom/ctr/RomfsFile.java new file mode 100644 index 0000000..30e9f7c --- /dev/null +++ b/src/com/pkrandom/ctr/RomfsFile.java @@ -0,0 +1,121 @@ +package com.pkrandom.ctr; + +/*----------------------------------------------------------------------------*/ +/*-- RomfsFile.java - an entry in the romfs filesystem --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.FileFunctions; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class RomfsFile { + + private NCCH parent; + public long offset; + public int size; + public String fullPath; + private Extracted status = Extracted.NOT; + private String extFilename; + public byte[] data; + public boolean fileChanged = false; + public long originalCRC; + + public RomfsFile(NCCH parent) { + this.parent = parent; + } + + public byte[] getContents() throws IOException { + if (this.status == Extracted.NOT) { + // extract file + parent.reopenROM(); + RandomAccessFile rom = parent.getBaseRom(); + byte[] buf = new byte[this.size]; + rom.seek(this.offset); + rom.readFully(buf); + originalCRC = FileFunctions.getCRC32(buf); + if (parent.isWritingEnabled()) { + // make a file + String tmpDir = parent.getTmpFolder(); + this.extFilename = fullPath.replaceAll("[^A-Za-z0-9_\\.]+", ""); + File tmpFile = new File(tmpDir + extFilename); + FileOutputStream fos = new FileOutputStream(tmpFile); + fos.write(buf); + fos.close(); + tmpFile.deleteOnExit(); + this.status = Extracted.TO_FILE; + this.data = null; + return buf; + } else { + this.status = Extracted.TO_RAM; + this.data = buf; + byte[] newcopy = new byte[buf.length]; + System.arraycopy(buf, 0, newcopy, 0, buf.length); + return newcopy; + } + } else if (this.status == Extracted.TO_RAM) { + byte[] newcopy = new byte[this.data.length]; + System.arraycopy(this.data, 0, newcopy, 0, this.data.length); + return newcopy; + } else { + String tmpDir = parent.getTmpFolder(); + return FileFunctions.readFileFullyIntoBuffer(tmpDir + this.extFilename); + } + } + + public void writeOverride(byte[] data) throws IOException { + if (status == Extracted.NOT) { + // temp extract + getContents(); + } + fileChanged = true; + size = data.length; + if (status == Extracted.TO_FILE) { + String tmpDir = parent.getTmpFolder(); + FileOutputStream fos = new FileOutputStream(new File(tmpDir + this.extFilename)); + fos.write(data); + fos.close(); + } else { + if (this.data.length == data.length) { + // copy new in + System.arraycopy(data, 0, this.data, 0, data.length); + } else { + // make new array + this.data = null; + this.data = new byte[data.length]; + System.arraycopy(data, 0, this.data, 0, data.length); + } + } + } + + // returns null if no override + public byte[] getOverrideContents() throws IOException { + if (status == Extracted.NOT) { + return null; + } + return getContents(); + } + + private enum Extracted { + NOT, TO_FILE, TO_RAM + } +} diff --git a/src/com/pkrandom/ctr/SMDH.java b/src/com/pkrandom/ctr/SMDH.java new file mode 100644 index 0000000..070809b --- /dev/null +++ b/src/com/pkrandom/ctr/SMDH.java @@ -0,0 +1,118 @@ +package com.pkrandom.ctr; + +/*----------------------------------------------------------------------------*/ +/*-- NCCH.java - a base class for dealing with 3DS SMDH (icon.bin) files. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.FileFunctions; + +import java.nio.charset.StandardCharsets; + +public class SMDH { + + private byte[] data; + private String[] shortDescriptions = new String[12]; + private String[] longDescriptions = new String[12]; + private String[] publishers = new String[12]; + + private static final int smdh_magic = 0x48444D53; + private static final int length_of_title = 0x200; + private static final int short_description_length = 0x80; + private static final int long_description_length = 0x100; + private static final int publisher_length = 0x80; + + public SMDH(byte[] smdhData) { + data = smdhData; + if (this.isValid()) { + readDescriptionsAndPublishers(); + } + } + + public byte[] getBytes() { + return data; + } + + public void setAllDescriptions(String newDescription) { + byte[] newDescriptionBytes = newDescription.getBytes(StandardCharsets.UTF_16LE); + if (newDescriptionBytes.length <= short_description_length) { + for (int i = 0; i < 12; i++) { + shortDescriptions[i] = newDescription; + longDescriptions[i] = newDescription; + } + writeDescriptionsAndPublishers(); + } + } + + public void setAllPublishers(String newPublisher) { + byte[] newPublisherBytes = newPublisher.getBytes(StandardCharsets.UTF_16LE); + if (newPublisherBytes.length <= publisher_length) { + for (int i = 0; i < 12; i++) { + publishers[i] = newPublisher; + } + writeDescriptionsAndPublishers(); + } + } + + private boolean isValid() { + int magic = FileFunctions.readFullInt(data, 0x0); + return magic == smdh_magic; + } + + private void readDescriptionsAndPublishers() { + for (int i = 0; i < 12; i++) { + int shortDescriptionOffset = 0x08 + (length_of_title * i); + byte[] shortDescriptionBytes = new byte[short_description_length]; + System.arraycopy(data, shortDescriptionOffset, shortDescriptionBytes, 0, short_description_length); + shortDescriptions[i] = new String(shortDescriptionBytes, StandardCharsets.UTF_16LE).trim(); + + int longDescriptionOffset = 0x88 + (length_of_title * i); + byte[] longDescriptionBytes = new byte[long_description_length]; + System.arraycopy(data, longDescriptionOffset, longDescriptionBytes, 0, long_description_length); + longDescriptions[i] = new String(longDescriptionBytes, StandardCharsets.UTF_16LE).trim(); + + int publisherOffset = 0x188 + (length_of_title * i); + byte[] publisherBytes = new byte[publisher_length]; + System.arraycopy(data, publisherOffset, publisherBytes, 0, publisher_length); + publishers[i] = new String(publisherBytes, StandardCharsets.UTF_16LE).trim(); + } + } + + private void writeDescriptionsAndPublishers() { + for (int i = 0; i < 12; i++) { + byte[] emptyShortDescription = new byte[short_description_length]; + int shortDescriptionOffset = 0x08 + (length_of_title * i); + byte[] shortDescriptionBytes = shortDescriptions[i].getBytes(StandardCharsets.UTF_16LE); + System.arraycopy(emptyShortDescription, 0, data, shortDescriptionOffset, short_description_length); + System.arraycopy(shortDescriptionBytes, 0, data, shortDescriptionOffset, shortDescriptionBytes.length); + + byte[] emptyLongDescription = new byte[long_description_length]; + int longDescriptionOffset = 0x88 + (length_of_title * i); + byte[] longDescriptionBytes = longDescriptions[i].getBytes(StandardCharsets.UTF_16LE); + System.arraycopy(emptyLongDescription, 0, data, longDescriptionOffset, long_description_length); + System.arraycopy(longDescriptionBytes, 0, data, longDescriptionOffset, longDescriptionBytes.length); + + byte[] emptyPublisher = new byte[publisher_length]; + int publisherOffset = 0x188 + (length_of_title * i); + byte[] publisherBytes = publishers[i].getBytes(StandardCharsets.UTF_16LE); + System.arraycopy(emptyPublisher, 0, data, publisherOffset, publisher_length); + System.arraycopy(publisherBytes, 0, data, publisherOffset, publisherBytes.length); + } + } +} diff --git a/src/com/pkrandom/exceptions/CannotWriteToLocationException.java b/src/com/pkrandom/exceptions/CannotWriteToLocationException.java new file mode 100644 index 0000000..1948de2 --- /dev/null +++ b/src/com/pkrandom/exceptions/CannotWriteToLocationException.java @@ -0,0 +1,36 @@ +package com.pkrandom.exceptions; + +/*----------------------------------------------------------------------------*/ +/*-- CannotWriteToLocationException.java - exception for when a user tries --*/ +/*-- to write to a place where they --*/ +/*-- don't have permissions. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class CannotWriteToLocationException extends RuntimeException { + public CannotWriteToLocationException(Exception e) { + super(e); + } + + public CannotWriteToLocationException(String text) { + super(text); + } +} diff --git a/src/com/pkrandom/exceptions/EncryptedROMException.java b/src/com/pkrandom/exceptions/EncryptedROMException.java new file mode 100644 index 0000000..4d787fc --- /dev/null +++ b/src/com/pkrandom/exceptions/EncryptedROMException.java @@ -0,0 +1,35 @@ +package com.pkrandom.exceptions; + +/*----------------------------------------------------------------------------*/ +/*-- EncryptedROMException.java - exception for when an encrypted 3DS ROM --*/ +/*-- is provided by the user. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class EncryptedROMException extends RuntimeException { + public EncryptedROMException(Exception e) { + super(e); + } + + public EncryptedROMException(String text) { + super(text); + } +} diff --git a/src/com/pkrandom/exceptions/InvalidSupplementFilesException.java b/src/com/pkrandom/exceptions/InvalidSupplementFilesException.java new file mode 100755 index 0000000..4c62aed --- /dev/null +++ b/src/com/pkrandom/exceptions/InvalidSupplementFilesException.java @@ -0,0 +1,50 @@ +package com.pkrandom.exceptions; + +/*----------------------------------------------------------------------------*/ +/*-- InvalidSupplementFilesException.java - thrown when the trainer class --*/ +/*-- or trainer name files found are--*/ +/*-- different from those of the --*/ +/*-- preset creator. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class InvalidSupplementFilesException extends Exception { + + /** + * + */ + private static final long serialVersionUID = -3778498838677886358L; + + public enum Type { + UNKNOWN, TOO_SHORT, CUSTOM_NAMES + } + + private final Type type; + + public InvalidSupplementFilesException(Type type, String message) { + super(message); + this.type = type; + } + + public Type getType() { + return type; + } +} diff --git a/src/com/pkrandom/exceptions/RandomizationException.java b/src/com/pkrandom/exceptions/RandomizationException.java new file mode 100644 index 0000000..ef3ac34 --- /dev/null +++ b/src/com/pkrandom/exceptions/RandomizationException.java @@ -0,0 +1,34 @@ +package com.pkrandom.exceptions; + +/*----------------------------------------------------------------------------*/ +/*-- RandomizerException.java - thrown for various errors during --*/ +/*-- randomization --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class RandomizationException extends RuntimeException { + public RandomizationException(String text) { + super(text); + } + + private static final long serialVersionUID = -771695719222719664L; + +} diff --git a/src/com/pkrandom/exceptions/RandomizerIOException.java b/src/com/pkrandom/exceptions/RandomizerIOException.java new file mode 100644 index 0000000..68a618c --- /dev/null +++ b/src/com/pkrandom/exceptions/RandomizerIOException.java @@ -0,0 +1,40 @@ +package com.pkrandom.exceptions; + +/*----------------------------------------------------------------------------*/ +/*-- RandomizerIOException.java - generic exception for various IO errors --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class RandomizerIOException extends RuntimeException { + public RandomizerIOException(Exception e) { + super(e); + } + + public RandomizerIOException(String text) { + super(text); + } + + public RandomizerIOException(String text, Exception e) { + super(text, e); + } + + private static final long serialVersionUID = -8174099615381353972L; +} diff --git a/src/com/pkrandom/newgui/Bundle.properties b/src/com/pkrandom/newgui/Bundle.properties new file mode 100644 index 0000000..4ff82f2 --- /dev/null +++ b/src/com/pkrandom/newgui/Bundle.properties @@ -0,0 +1,638 @@ +# To change this template, choose Tools | Templates +# and open the template in the editor. + +GUI.versionLabel.text=Pokemon Randomizer +GUI.generalOptionsPanel.title=General Options +GUI.limitPokemonCheckBox.text=Limit Pokemon +GUI.limitPokemonCheckBox.toolTipText=Select this to allow yourself to limit the Pokemon used by the randomization.
If this box isn't checked all Pokemon will be allowed. +GUI.raceModeCheckBox.toolTipText=Select this to enable certain things which are useful for a speedrun race of the ROM you create.
The ability to save a log file will be disabled, and a check value will be generated.
You can send this value around with the preset file to ensure that everyone has the same ROM to race with. +GUI.raceModeCheckBox.text=Race Mode +GUI.noIrregularAltFormesCheckBox.text=No Irregular Alt Formes +GUI.noIrregularAltFormesCheckBox.toolTipText=Bans "irregular" alternate formes from the Pokemon pool when "Allow Alternate Formes" is selected for Wild Pokemon, Trainer Pokemon, Evolutions, etc.
Irregular formes are those that normally cannot exist outside of battle (such as Mega Evolutions and other in-battle transformations like Darmanitan-Z, Zygarde-C, etc),
as well as the "Fusion" Pokemon (Kyurem-B/W and Necrozma-DM/DW).
These alternate formes still exist and will get randomized base stats, abilities and so on (according to your settings),
but they can only be accessed through their regular means (such as by Mega Evolving or transforming because of an Ability). +GUI.romInformationPanel.title=ROM Information +GUI.noRomLoaded=NO ROM LOADED +GUI.openROMButton.toolTipText= +GUI.openROMButton.text=Open ROM +GUI.randomizeSaveButton.text=Randomize (Save) +GUI.premadeSeedButton.text=Premade Seed +GUI.settingsButton.toolTipText= +GUI.settingsButton.text=Settings +GUI.loadSettingsButton.toolTipText=Clicking this button allows you to load predefined settings from a file.
If the file was created with a game which has less randomization options
than the one you are randomizing now, those options will be set to "Unchanged"/Off.
If you load from a newer game, everything that isn't supported will be ignored. +GUI.loadSettingsButton.text=Load Settings +GUI.saveSettingsButton.toolTipText=Clicking this will allow you to save the current randomization settings as a file.
You can then load these settings when you are randomizing any ROM.
The way in which ROMs with more or less features than the current one
are handled is described when you hover over the "Load Settings" button. +GUI.saveSettingsButton.text=Save Settings +GUI.pokemonTraitsPanel.title=Pokemon Traits +GUI.pbsPanel.title=Pokemon Base Statistics +GUI.pbsUnchangedRadioButton.toolTipText=Don't change Pokemon stats from the base at all. +GUI.pbsUnchangedRadioButton.text=Unchanged +GUI.pbsShuffleRadioButton.toolTipText=Shuffle each Pokemon's stats.
For example, its base Attack may be swapped with its base Special Attack, etc.
This does not make any Pokemon stronger or weaker. +GUI.pbsShuffleRadioButton.text=Shuffle +GUI.pbsRandomRadioButton.toolTipText=Randomizes each Pokemon's stats, as long as they fall within the original base stat total.
This could make Pokemon stronger or weaker if they get unlucky or lucky rolls on stats they need.
Evolutions of a Pokemon will be treated separately from that Pokemon unless you check Follow Evolutions. +GUI.pbsRandomRadioButton.text=Random +GUI.pbsLegendariesSlowRadioButton.toolTipText=All legendaries get the "Slow" EXP Curve. +GUI.pbsLegendariesSlowRadioButton.text=Legendaries: Slow +GUI.pbsStrongLegendariesSlowRadioButton.toolTipText=Strong legendaries get the "Slow" EXP Curve.
This includes all legendaries with >600 BST. +GUI.pbsStrongLegendariesSlowRadioButton.text=Strong Legendaries: Slow +GUI.pbsAllMediumFastRadioButton.toolTipText=All Pokemon get the selected EXP Curve. +GUI.pbsAllMediumFastRadioButton.text=All Pokemon +GUI.pbsStandardizeEXPCurvesCheckBox.toolTipText=When this is selected, every Pokemon's EXP curve will be changed to the selected EXP curve below,
except for the Pokemon forced to use the "Slow" EXP curve, chosen with the settings to the right.
This will cause Pokemon to be better or worse based more on their stats/moves/type rather than the difficulty of leveling them;
it will also allow for more potential evolutions if you randomize evolutions, since a Pokemon only can evolve into a Pokemon with the same EXP curve.

If you're not sure which EXP Curve to pick, Medium Fast and Medium Slow are generally good curves (most Pokemon use these in the regular games). +GUI.pbsStandardizeEXPCurvesCheckBox.text=Standardize EXP Curves to: +GUI.pbsFollowEvolutionsCheckBox.toolTipText=When this is selected and base stats are shuffled or randomized, evolutions of a Pokemon will inherit from their base form's rolls.
Shuffle: The same new ordering of stats will be used.
Randomized: The evolution will have the same proportion of stats as the base form. +GUI.pbsFollowEvolutionsCheckBox.text=Follow Evolutions +GUI.pbsUpdateBaseStatsCheckBox.toolTipText=Select this to update the base stats of Pokemon in the loaded game to their values in the generation chosen to the right.
This does NOT:
* Add any new Pokemon to the game
* Split Special Attack and Special Defense in Gen 1. +GUI.pbsUpdateBaseStatsCheckBox.text=Update Base Stats to Generation: +GUI.ptPanel.title=Pokemon Types +GUI.ptUnchangedRadioButton.toolTipText=Don't change Pokemon types at all. +GUI.ptUnchangedRadioButton.text=Unchanged +GUI.ptRandomFollowEvolutionsRadioButton.toolTipText=Randomize the types of each Pokemon, but make most evolutions copy the base Pokemon (except perhaps adding an extra secondary type).
Evolutions that don't copy types are the Eeveelutions, among others. +GUI.ptRandomFollowEvolutionsRadioButton.text=Random (follow evolutions) +GUI.ptRandomCompletelyRadioButton.toolTipText=Randomize Pokemon types completely.
Evolutions of a Pokemon are completely separate from that Pokemon, so the type of a Pokemon will likely completely change every evolution. +GUI.ptRandomCompletelyRadioButton.text=Random (completely) +GUI.ptIsDualTypeCheckBox.text=Force Dual Typing +GUI.paPanel.title=Pokemon Abilities +GUI.paUnchangedRadioButton.toolTipText=Don't change Pokemon abilities from the base at all. +GUI.paUnchangedRadioButton.text=Unchanged +GUI.paRandomRadioButton.toolTipText=Give each Pokemon new abilities.
Each Pokemon will have a base ability, and a 50% chance of having a 2nd ability different from the first.
(50% of the species will have this ability if it is made).
In Generation 5 games, each Pokemon will also receive a new random "Dream World" ability.
Pokemon such as Shedinja which have Wonder Guard as their ability will keep it to maintain balance. +GUI.paRandomRadioButton.text=Random +GUI.paAllowWonderGuardCheckBox.toolTipText=If this is checked, Wonder Guard will be able to be chosen as any Pokemon's ability.
This can lead to some very overpowered/broken Pokemon.
USE WITH CAUTION! +GUI.paAllowWonderGuardCheckBox.text=Allow Wonder Guard +GUI.paFollowEvolutionsCheckBox.toolTipText=When this is selected and abilities are randomized, non-split evolutions of a Pokemon will inherit that Pokemon's random abilities. +GUI.paFollowEvolutionsCheckBox.text=Follow Evolutions +GUI.paBanLabel.text=Ban... +GUI.paTrappingAbilitiesCheckBox.toolTipText=When abilities are randomized, ban abilities that can prevent the opponent from fleeing/switching out from being selected.
\nThis bans Arena Trap, Magnet Pull and Shadow Tag.
\nHas no effect if abilities are not randomized.
\nPokemon that have a trapping ability to begin with will still have their abilities randomized, unlike how Wonder Guard is treated. +GUI.paTrappingAbilitiesCheckBox.text=Trapping Abilities +GUI.paNegativeAbilitiesCheckBox.toolTipText=When abilities are randomized, ban abilities that are purely negative in nature from being chosen.
\nThis bans Defeatist, Slow Start, Truant, Klutz, Stall.
\nHas no effect if abilities are not randomized.
\nPokemon that have a negative ability to begin with will still have their abilities randomized, unlike how Wonder Guard is treated. +GUI.paNegativeAbilitiesCheckBox.text=Negative Abilities +GUI.paBadAbilitiesCheckBox.toolTipText=When abilities are randomized, ban bad abilities.
\nThis bans Minus, Plus, Anticipation, Forewarn, Frisk, Honey Gather, Aura Break, Receiver, Power of Alchemy.
If "Double Battle Mode" for Trainers is not selected, it also bans Friend Guard, Healer, Telepathy, Symbiosis, Battery.
\nHas no effect if abilities are not randomized. +GUI.paBadAbilitiesCheckBox.text=Bad Abilities +GUI.paEnsureTwoAbilitiesCheckbox.toolTipText=When abilities are randomized, ensure each Pokemon has two abilities. +GUI.paEnsureTwoAbilitiesCheckbox.text=Ensure Two Abilities +GUI.pePanel.title=Pokemon Evolutions +GUI.peUnchangedRadioButton.toolTipText=Don't randomize Pokemon evolutions. +GUI.peUnchangedRadioButton.text=Unchanged +GUI.peRandomRadioButton.toolTipText=Randomize which species each Pokemon evolves into.
\nEvolutions will be mostly unrestricted unless you check some boxes to the right, but the EXP curves of the Pokemon must match. +GUI.peRandomRadioButton.text=Random +GUI.peRandomEveryLevelRadioButton.toolTipText=Randomize which species each Pokemon evolves into while forcing them to evolve every single level.
All existing evolutions for a given Pokemon will be removed and replaced with a single Level 1 evolution.
Evolutions will be mostly unrestricted unless you check some boxes to the right, but the EXP curves of the Pokemon must match.
Evolution "loops" are expected and intended with this setting. +GUI.peRandomEveryLevelRadioButton.text=Random Every Level +GUI.peSimilarStrengthCheckBox.toolTipText=When this is checked, random evolutions will prefer Pokemon with a similar BST to the original evolution target.
Has less precedence than all other modifiers below, so it may not always be strictly enforced. +GUI.peSimilarStrengthCheckBox.text=Similar Strength +GUI.peSameTypingCheckBox.toolTipText=When selected, randomized evolutions will share at least one type between the source and target if possible. +GUI.peSameTypingCheckBox.text=Same Typing +GUI.peLimitEvolutionsToThreeCheckBox.toolTipText=When selected, there will not be any evolution chains created that are longer than three stages. +GUI.peLimitEvolutionsToThreeCheckBox.text=Limit Evolutions to Three Stages +GUI.peForceChangeCheckBox.toolTipText=When selected, every evolution of a Pokemon will be randomized to something different than what it was originally. +GUI.peForceChangeCheckBox.text=Force Change +GUI.peChangeImpossibleEvosCheckBox.toolTipText=If this is checked, every evolution that isn't possible to do without trading in the current game will be changed.
Takes effect regardless of whether evolutions are randomized or not.
Some of the types that will be changed are:
  • "Normal" trade evolutions
  • Trade evolutions with another condition, such as held item or Pokemon traded for
  • Day/night evolutions if there isn't day/night in the game
  • Contest-stat evolutions if there aren't contests in the game
  • Location-based evolutions, if those locations don't exist in the game
  • Move-based evolutions, only if you randomize movesets
+GUI.peChangeImpossibleEvosCheckBox.text=Change Impossible Evolutions +GUI.peMakeEvolutionsEasierCheckBox.toolTipText=If this is checked, Pokemon that evolve at a very high level will evolve at lower levels to make them more viable for shorter playthroughs.
Specifically, every Pokemon will evolve to its final stage by level 40, and three-stage evolutions will reach their middle stage by no later than level 30.
Additionally, Pokemon that normally evolve by having another specific Pokemon in the party will instead evolve at level 35.
Lastly, Pokemon that evolve via friendship will evolve at 160 happiness (down from 220), similar to how it works in the Switch Pokemon games.
Takes effect regardless of whether evolutions are randomized or not. +GUI.peMakeEvolutionsEasierCheckBox.text=Make Evolutions Easier +GUI.peRemoveTimeBasedEvolutions.toolTipText=If this is checked, evolutions that require a certain time of day will now be possible regardless of the in-game time.
Split time-based evolutions (like Eevee => Espeon/Umbreon and Rockruff => Lycanroc) will be changed to stone evolutions instead. +GUI.peRemoveTimeBasedEvolutions.text=Remove Time-Based Evolutions +GUI.startersStaticsTradesPanel=Starters, Statics & Trades +GUI.spPanel.title=Starter Pokemon +GUI.spUnchangedRadioButton.toolTipText=Don't change the starter Pokemon. +GUI.spUnchangedRadioButton.text=Unchanged +GUI.spCustomRadioButton.toolTipText=Lets you pick the 3 starter pokemon you want to use, or "Random" at the top of the selector. +GUI.spCustomRadioButton.text=Custom +GUI.spRandomCompletelyRadioButton.toolTipText=Picks 3 random starter Pokemon to be used. +GUI.spRandomCompletelyRadioButton.text=Random (completely) +GUI.spRandomTwoEvosRadioButton.toolTipText=Picks 3 random starter Pokemon to be used.
These Pokemon must have 2 evolution stages, e.g. be like the starters in the real games. +GUI.spRandomTwoEvosRadioButton.text=Random (basic Pokemon with 2 evolutions) +GUI.spRandomizeStarterHeldItemsCheckBox.toolTipText=Checking this will randomize the items held by the starters where possible.
In Generation 2 games, each starter will get an individual random item.
In Generation 3 games, all the starters will get the same random item. +GUI.spRandomizeStarterHeldItemsCheckBox.text=Randomize Starter Held Items +GUI.spBanBadItemsCheckBox.toolTipText=Checking this will remove "bad" items that don't do much from the set of possible random items for starter Pokemon, such as berries and mail. +GUI.spBanBadItemsCheckBox.text=Ban Bad Items +GUI.stpPanel.title=Static Pokemon +GUI.stpUnchangedRadioButton.toolTipText=Static Pokemon remain the same. +GUI.stpUnchangedRadioButton.text=Unchanged +GUI.stpSwapLegendariesSwapStandardsRadioButton.toolTipText=Selecting this will replace every static Pokemon encounter, gift or purchase with another random one.
In this particular mode, legendary pokemon will always be swapped for other legendaries.
Also, normal non-legendary Pokemon will only be swapped for other non-legendaries.
In games where Ultra Beasts appear, they will be swapped out for other Ultra Beasts. +GUI.stpSwapLegendariesSwapStandardsRadioButton.text=Swap Legendaries && Swap Standards +GUI.stpRandomCompletelyRadioButton.toolTipText=Selecting this will replace every static Pokemon encounter, gift or purchase with another random one.
In this particular mode, any Pokemon can replace any other Pokemon, so you could get a Mew in the Game Corner.
Or fight Magikarp instead of Mewtwo... +GUI.stpRandomCompletelyRadioButton.text=Random (completely) +GUI.stpRandomSimilarStrengthRadioButton.toolTipText=Selecting this will replace every static Pokemon encounter with a Pokemon of similar strength. +GUI.stpRandomSimilarStrengthRadioButton.text=Random (similar strength) +GUI.stpLimitMainGameLegendariesCheckBox.toolTipText=Selecting this will set an upper BST limit on what main-game Legendary Pokemon can be randomized into, and also expand the window for what counts
as "Similar Strength" for those Pokemon.

This only applies to main-game Legendary Pokemon that are catchable and that are directly in your way during the main game, so some Pokemon
like Kyurem-B/Kyurem-W in BW2 (not catchable) and Rayquaza in Emerald (have to go out of your way to catch) are not included. +GUI.stpLimitMainGameLegendariesCheckBox.text=Limit Main-Game Legendaries +GUI.stpRandomize600BSTCheckBox.toolTipText=Selecting this will enforce pure random on all static Pokemon with 600+ BST. +GUI.stpRandomize600BSTCheckBox.text=Randomize 600+ BST +GUI.igtPanel.title=In-Game Trades +GUI.igtUnchangedRadioButton.toolTipText=In-game trades remain the same. +GUI.igtUnchangedRadioButton.text=Unchanged +GUI.igtRandomizeGivenPokemonOnlyRadioButton.toolTipText=Selecting this will randomize the Pokemon you receive from each in-game trade,
but the Pokemon requested by the NPC in exchange will remain the same. +GUI.igtRandomizeGivenPokemonOnlyRadioButton.text=Randomize Given Pokemon Only +GUI.igtRandomizeBothRequestedGivenRadioButton.toolTipText=Selecting this will replace both the Pokemon you receive from an in-game trade and the Pokemon required to do the trade. +GUI.igtRandomizeBothRequestedGivenRadioButton.text=Randomize Both Requested && Given Pokemon +GUI.igtRandomizeNicknamesCheckBox.toolTipText=Check this to randomize the nicknames of the Pokemon you receive.
The nicknames will be chosen from a predefined list.
If there are no more usable nicknames the original nicknames will be kept for the rest of the trades. +GUI.igtRandomizeNicknamesCheckBox.text=Randomize Nicknames +GUI.igtRandomizeOTsCheckBox.toolTipText=Check this to randomize the Original Trainer (ID & name) of the Pokemon you receive from trades.
The names will be chosen from the same list as trainer names,
with names that are too long to be OT names excluded. +GUI.igtRandomizeOTsCheckBox.text=Randomize OTs +GUI.igtRandomizeIVsCheckBox.toolTipText=Check this to randomize the IVs of the Pokemon you receive from ingame trades.
In most games these Pokemon have set IVs, so clicking this randomizes those set IVs. +GUI.igtRandomizeIVsCheckBox.text=Randomize IVs +GUI.igtRandomizeItemsCheckBox.toolTipText=Check this to give each Pokemon you receive from an ingame trade a random held item.
This includes trades that do not normally have a held item on the Pokemon. +GUI.igtRandomizeItemsCheckBox.text=Randomize Items +GUI.movesMovesetsPanel=Moves & Movesets +GUI.mdPanel.title=Move Data +GUI.mdRandomizeMovePowerCheckBox.toolTipText=Randomize the power of normal damaging moves.
Moves will get a new random power generally between 20 and 150 inclusive, with a very small chance of higher values appearing.
Non-damaging moves and moves with variable power will be unaffected.
NOTE: There is no way to see the updated power of moves in-game in Gen 1! +GUI.mdRandomizeMovePowerCheckBox.text=Randomize Move Power +GUI.mdRandomizeMoveAccuracyCheckBox.toolTipText=Randomize the accuracy of most moves.
Some restrictions are enforced to reduce the chances of situations like 100% accurate OHKO moves happening.
Sure-hit moves are not changed.
NOTE: There is no way to see the updated accuracy of moves in-game in Gen 1! +GUI.mdRandomizeMoveAccuracyCheckBox.text=Randomize Move Accuracy +GUI.mdRandomizeMovePPCheckBox.toolTipText=Randomize the PP of each move.
The maximum PP of each move (before PP Ups) will be randomized to a multiple of 5 between 5 and 40 inclusive. +GUI.mdRandomizeMovePPCheckBox.text=Randomize Move PP +GUI.mdRandomizeMoveTypesCheckBox.toolTipText=Randomize the type of most moves.
The type of each move other than Struggle and ???-type moves will be re-rolled randomly.
This has no real effect on self-status moves, and may have odd side-effects in Gen 1. +GUI.mdRandomizeMoveTypesCheckBox.text=Randomize Move Types +GUI.mdRandomizeMoveCategoryCheckBox.toolTipText=Randomize the category of damaging moves between Physical and Special.
This does not affect status moves.
NOTE: This feature is not available for Gen 1-3 games. This randomizer does NOT add the Physical/Special split to games that don't have it! +GUI.mdRandomizeMoveCategoryCheckBox.text=Randomize Move Category +GUI.mdUpdateMovesCheckBox.toolTipText=If this is checked, moves will be updated to their stats (power, accuracy, etc) in the generation chosen to the right where possible.
This does NOT add the Fairy type! Moves that were changed to Fairy type will keep their Gen 5 types.
Note that this only affects move stats, not any secondary effects that may have been added. +GUI.mdUpdateMovesCheckBox.text=Update Moves to Generation: +GUI.mdLegacyCheckBox.toolTipText=Instead of updating moves to their Gen 6 stats, update them to their Gen 5 stats instead.
This is intended for people who used the "Update Moves" function in old randomizers but don't like the new changes.
This is available for every game except Gen 5 games where it would be pointless. +GUI.mdLegacyCheckBox.text=Legacy +GUI.pmsPanel.title=Pokemon Movesets +GUI.pmsUnchangedRadioButton.toolTipText=Don't change Pokemon movesets at all. +GUI.pmsUnchangedRadioButton.text=Unchanged +GUI.pmsRandomPreferringSameTypeRadioButton.toolTipText=Randomize Pokemon movesets, preferring moves that are of the type (or one of the types) of the Pokemon.
Each Pokemon will get at least one reasonably accurate damaging move to begin with. +GUI.pmsRandomPreferringSameTypeRadioButton.text=Random (preferring same type) +GUI.pmsRandomCompletelyRadioButton.toolTipText=Randomize Pokemon movesets, completely ignoring the type of the move and the Pokemon.
Each Pokemon will get at least one reasonably accurate damaging move to begin with. +GUI.pmsRandomCompletelyRadioButton.text=Random (completely) +GUI.pmsMetronomeOnlyModeRadioButton.toolTipText=Where possible, every Pok\u00e9mon in the entire game will have Metronome as its only move,
with the PP boosted to 40 to make it possible to complete fights without always using Struggle.
Does not currently apply to non-standard battles such as the Battle Tower/Frontier or the PWT. +GUI.pmsMetronomeOnlyModeRadioButton.text=Metronome Only Mode +GUI.pmsGuaranteedLevel1MovesCheckBox.toolTipText=Check this to make sure every Pokemon gets a guaranteed amount of moves at level 1, instead of them keeping their original move count.
Set amount of guaranteed moves with the slider to the right.
Setting this to 4 ensures that every Pokemon you catch will have a full moveset. +GUI.pmsGuaranteedLevel1MovesCheckBox.text=Guaranteed Level 1 Moves +GUI.pmsReorderDamagingMovesCheckBox.toolTipText=Reorders the randomized movesets so that less powerful damaging moves are learnt before those with high power.\n
The positions of non-damaging moves will not change. +GUI.pmsReorderDamagingMovesCheckBox.text=Reorder Damaging Moves +GUI.pmsNoGameBreakingMovesCheckBox.toolTipText=Checking this checkbox will stop moves that can break early/late games being available in randomized movesets.
In first generation games, Dragon Rage, SonicBoom, Spore, and every OHKO move are banned.
In second generation onwards, only SonicBoom and Dragon Rage are banned (because OHKO moves and sleep are significantly less broken). +GUI.pmsNoGameBreakingMovesCheckBox.text=No Game-Breaking Moves +GUI.pmsForceGoodDamagingCheckBox.toolTipText=Selecting this option will allow you to use the slider below to select a proportion of the randomized movesets to be forced to be good damaging moves.
Note that other moves can still be selected as good damaging moves randomly, this is simply an additional chance. +GUI.pmsForceGoodDamagingCheckBox.text=Force % of Good Damaging Moves: +GUI.pmsGuaranteedLevel1MovesSlider.toolTipText=Set amount of guaranteed moves at level 1. Applies to all Pokemon. +GUI.pmsForceGoodDamagingSlider.toolTipText=Use this slider to select the probability of good damaging moves for the option above.
Note that other moves can still be selected as good damaging moves randomly, this is simply an additional chance. +GUI.foePokemonPanel.title=Foe Pokemon +GUI.tpPanel.title=Trainer Pokemon +GUI.tpMain0Unchanged.toolTipText=Don't change trainer Pokemon at all. +GUI.tpMain0Unchanged.text=Unchanged +GUI.tpUnchangedRadioButton.toolTipText=Don't change trainer Pokemon at all. +GUI.tpUnchangedRadioButton.text=Unchanged +GUI.tpMain1Random.toolTipText=Randomize Trainers' Pokemon completely. +GUI.tpMain1Random.text=Random +GUI.tpRandomRadioButton.toolTipText=Randomize Trainers' Pokemon completely. +GUI.tpRandomRadioButton.text=Random +GUI.tpMain2RandomEvenDistribution.toolTipText=For all trainers in the game (main-game and post-game), attempt to distribute Pokemon evenly.
Any other modifiers like similar strength take precedence, with the goal of reducing Pokemon from appearing too often. +GUI.tpMain2RandomEvenDistribution.text=Random (even distribution) +GUI.tpRandomEvenDistributionRadioButton.toolTipText=For all trainers in the game (main-game and post-game), attempt to distribute Pokemon evenly.
Any other modifiers like similar strength take precedence, with the goal of reducing Pokemon from appearing too often. +GUI.tpRandomEvenDistributionRadioButton.text=Random (even distribution) +GUI.tpMain3RandomEvenDistributionMainGame.toolTipText=For all trainers in the game (main-game trainers only), attempt to distribute Pokemon evenly.
Any other modifiers like similar strength take precedence, with the goal of reducing Pokemon from appearing too often.
Balancing among main game trainers causes evenness with a standard playthrough, and ignores post-game trainers (they are simply random).
This setting is for Gen 5 only. +GUI.tpMain3RandomEvenDistributionMainGame.text=Random (even distribution, main-game) +GUI.tpRandomEvenDistributionMainRadioButton.toolTipText=For all trainers in the game (main-game trainers only), attempt to distribute Pokemon evenly.
Any other modifiers like similar strength take precedence, with the goal of reducing Pokemon from appearing too often.
Balancing among main game trainers causes evenness with a standard playthrough, and ignores post-game trainers (they are simply random).
This setting is for Gen 5 only. +GUI.tpRandomEvenDistributionMainRadioButton.text=Random (even distribution, main-game) +GUI.tpMain4TypeThemed.toolTipText=Pick a type for each trainer and give them random Pokemon in that type.
Certain groups of trainers, such as the trainers in each gym, will all be given the same (random) type. +GUI.tpMain4TypeThemed.text=Type Themed +GUI.tpTypeThemedRadioButton.toolTipText=Pick a type for each trainer and give them random Pokemon in that type.
Certain groups of trainers, such as the trainers in each gym, will all be given the same (random) type. +GUI.tpTypeThemedRadioButton.text=Type Themed +GUI.tpMain5TypeThemedEliteFourGyms.toolTipText=Pick a type for each trainer and give them random Pokemon in that type.
Certain groups of trainers, such as the trainers in each gym, will all be given the same (random) type.
Only applies to Elite Four and Gym Trainers/Leaders. +GUI.tpMain5TypeThemedEliteFourGyms.text=Type Themed (Elite Four/Gyms Only) +GUI.tpRivalCarriesStarterCheckBox.toolTipText=If this is selected, the rival will have their starter in every team they battle you with, evolved if it is far enough through.
The rest of their team will be chosen in the same way as every other trainer. +GUI.tpRivalCarriesStarterCheckBox.text=Rival Carries Starter Through Game +GUI.tpSimilarStrengthCheckBox.toolTipText=If this is checked, the random Pokemon that replaces each Pokemon will be of similar power to the original.
However, preserving other rules such as type theming has precedence over this, so weaker or stronger Pokemon will be chosen if there are no other Pokemon available. +GUI.tpSimilarStrengthCheckBox.text=Try to Use Pokemon with Similar Strength +GUI.tpWeightTypesCheckBox.toolTipText=If this is checked, the number of trainers with each type will roughly match up to the number of Pokemon with that type.
This should reduce repetition of Pokemon, but may lead to a lot of trainers with the same type in a row. +GUI.tpWeightTypesCheckBox.text=Weight Types by # of Pokemon +GUI.tpDontUseLegendariesCheckBox.text=Don't Use Legendaries +GUI.tpNoEarlyWonderGuardCheckBox.toolTipText=Pokemon such as Shedinja, with the ability "Wonder Guard", are a pain to run into early on when you can't damage them.
Selecting this option will make sure that no trainers are given these Pokemon under level 20.
By the time you are fighting level 20 trainers you can reasonably be expected to have a counter for each type. +GUI.tpNoEarlyWonderGuardCheckBox.text=No Early Wonder Guard +GUI.tpRandomizeTrainerNamesCheckBox.toolTipText=Check this to randomize trainers' names when you fight them.
For RBY, this only includes the Gym Leaders and Elite 4, as no-one else has names.
For every other game this will replace the names of each individual trainer. +GUI.tpRandomizeTrainerNamesCheckBox.text=Randomize Trainer Names +GUI.tpRandomizeTrainerClassNamesCheckBox.toolTipText=Check this to randomize the class names to new names (e.g. "Youngster" could become "Misfit"). +GUI.tpRandomizeTrainerClassNamesCheckBox.text=Randomize Trainer Class Names +GUI.tpForceFullyEvolvedAtCheckBox.toolTipText=Checking this will force all trainer Pokemon at or above the level you select below to be fully evolved regardless of other settings.
Pokemon below the selected level will be randomly picked like normal. +GUI.tpForceFullyEvolvedAtCheckBox.text=Force Fully Evolved at Level: +GUI.tpForceFullyEvolvedAtSlider.toolTipText=Use this slider to select the minimum level to force fully evolved Pokemon at if said option is checked above. +GUI.tpPercentageLevelModifierSlider.toolTipText=Use this slider to select the percentage to change trainer Pokemon levels by.
Negative percentages (-50 to 0) will decrease levels, and positive percentages will increase them. +GUI.tpPercentageLevelModifierCheckBox.toolTipText=Checking this will enable a percentage-based level modifier for every enemy trainer Pokemon in the game. +GUI.tpPercentageLevelModifierCheckBox.text=Percentage Level Modifier: +GUI.tpAllowAlternateFormesCheckBox.toolTipText=Allow trainers to have alternate formes of Pokemon, such as the various formes of Rotom or Deoxys.
Note that their traits are randomized/shuffled separately from their base formes. +GUI.tpAllowAlternateFormesCheckBox.text=Allow Alternate Formes +GUI.wildPokemonPanel.title=Wild Pokemon +GUI.wpPanel.title=Wild Pokemon +GUI.wpUnchangedRadioButton.toolTipText=Don't change Wild Pokemon at all. +GUI.wpUnchangedRadioButton.text=Unchanged +GUI.wpRandomRadioButton.toolTipText=Completely randomize Wild Pokemon in every area.
This should mean that there are many different Pokemon in each area. +GUI.wpRandomRadioButton.text=Random +GUI.wpArea1To1RadioButton.toolTipText=Each Pokemon in a given encounter area will be replaced by another Pokemon in every slot it appears in.
This will make each area have a handful of random Pokemon. +GUI.wpArea1To1RadioButton.text=Area 1-to-1 Mapping +GUI.wpGlobal1To1RadioButton.toolTipText=Every place a certain Pokemon appears in, it will be replaced by another set Pokemon.
This mode doesn't support any other rules except similar strength because it is too restrictive on its own. +GUI.wpGlobal1To1RadioButton.text=Global 1-to-1 Mapping +GUI.wpARPanel.title=Additional Rule +GUI.wpARNoneRadioButton.toolTipText=Don't apply any other rules. +GUI.wpARNoneRadioButton.text=None +GUI.wpARSimilarStrengthRadioButton.toolTipText=If this is checked, the random Pokemon that replaces each Pokemon will be of similar power to the original.
However, preserving other rules such as 1-1 maps has precedence over this, so weaker or stronger Pokemon will be chosen if there are no other Pokemon available.
This option is not available alongside Type Themes or Catch-em-All because the Pokemon pool would be too limited in some cases. +GUI.wpARSimilarStrengthRadioButton.text=Similar Strength +GUI.wpARCatchEmAllModeRadioButton.toolTipText=If this is turned on, every random Pokemon chosen to replace an encounter will be one that hasn't been chosen before.
This should make sure that every Pokemon is catchable.
Once every Pokemon has been chosen the Pokemon choice list will start again from full. +GUI.wpARCatchEmAllModeRadioButton.text=Catch Em All Mode +GUI.wpARTypeThemeAreasRadioButton.toolTipText=If this is chosen, every encounter area will have only Pokemon of a random type.
This may lead to a more realistic experience, or an odd one (e.g. if Fire pokemon are chosen to be in Surfing encounters). +GUI.wpARTypeThemeAreasRadioButton.text=Type Themed Areas +GUI.wpUseTimeBasedEncountersCheckBox.toolTipText=This affects games that have either seasons or morning/day/night encounter sets.
If this is checked, each of these sets will be treated as a separate "area".
So you will have to visit each place in morning/day/night, or in each season, to collect the Pokemon.
If this isn't checked, all of morning/day/night and all seasons will use the same encounter data. +GUI.wpUseTimeBasedEncountersCheckBox.text=Use Time Based Encounters +GUI.wpDontUseLegendariesCheckBox.text=Don't Use Legendaries +GUI.wpSetMinimumCatchRateCheckBox.toolTipText=If this is selected, every Pokemon in the game with a catch rate below a certain level will have its catch rate increased.\n
The exact minimum catch rate level can be set using the slider to the right.\n
The percentages below assume a normal Poke Ball is used while the wild Pokemon is at full health with no status condition.\n
Level 1: "Normal" minimum catch rate. ~10% (Gens 1-4)/~18% (Gens 5+) chance for normal Pokemon, ~5% (Gens 1-4)/~10% (Gens 5+) chance for legendary Pokemon.\n
Level 2: "Buffed" minimum catch rate. ~17% (Gens 1-4)/~26% (Gens 5+) chance for normal Pokemon, ~9% (Gens 1-4)/~16% (Gens 5+) chance for legendary Pokemon.\n
Level 3: "Super" minimum catch rate. ~27% (Gens 1-4)/~37% (Gens 5+) chance for normal Pokemon, ~14% (Gens 1-4)/~22% (Gens 5+) chance for legendary Pokemon.\n
Level 4: "Ultra" minimum catch rate. ~34% (Gens 1-4)/~44% (Gens 5+) chance for all Pokemon. \n
Level 5: Guaranteed catches (every Pokemon is guaranteed to be caught, so long as they are catchable in the first place). +GUI.wpSetMinimumCatchRateCheckBox.text=Set Minimum Catch Rate: +GUI.wpRandomizeHeldItemsCheckBox.toolTipText=Checking this will randomize the items held by Pokemon in the wild, including whether they can have one at all or not.
In some cases, these item definitions will also apply to static encounters with legendaries and the like. +GUI.wpRandomizeHeldItemsCheckBox.text=Randomize Held Items +GUI.wpBanBadItemsCheckBox.toolTipText=Checking this will remove "bad" items that don't do much from the set of possible random items for wild Pokemon, such as berries and mail. +GUI.wpBanBadItemsCheckBox.text=Ban Bad Items +GUI.wpBalanceShakingGrassPokemonCheckBox.toolTipText=Checking this will tune down shaking grass Pokemon at lower levels. +GUI.wpBalanceShakingGrassPokemonCheckBox.text=Balance Shaking Grass Pokemon +GUI.wpPercentageLevelModifierCheckBox.toolTipText=Checking this will enable a percentage-based level modifier for every wild Pokemon in the game. +GUI.wpPercentageLevelModifierCheckBox.text=Percentage Level Modifier: +GUI.wpPercentageLevelModifierSlider.toolTipText=Use this slider to select the percentage to change wild Pokemon levels by.
Negative percentages (-50 to 0) will decrease levels, and positive percentages will increase them. +GUI.wpSetMinimumCatchRateSlider.toolTipText=If Minimum Catch Rate is selected, allows you to set the level used. The percentages below assume a normal Poke Ball at full health.\n
Level 1: "Normal" minimum catch rate. ~10% (Gens 1-4)/~18% (Gens 5+) chance for normal Pokemon, ~5% (Gens 1-4)/~10% (Gens 5+) chance for legendary Pokemon.\n
Level 2: "Buffed" minimum catch rate. ~17% (Gens 1-4)/~26% (Gens 5+) chance for normal Pokemon, ~9% (Gens 1-4)/~16% (Gens 5+) chance for legendary Pokemon.\n
Level 3: "Super" minimum catch rate. ~27% (Gens 1-4)/~37% (Gens 5+) chance for normal Pokemon, ~14% (Gens 1-4)/~22% (Gens 5+) chance for legendary Pokemon.\n
Level 4: "Ultra" minimum catch rate. ~34% (Gens 1-4)/~44% (Gens 5+) chance for all Pokemon. \n
Level 5: Guaranteed catches (every Pokemon is guaranteed to be caught, so long as they are catchable in the first place). +GUI.tmsHMsTutorsPanel.title=TM/HMs & Tutors +GUI.tmPanel.title=TMs & HMs +GUI.tmMovesPanel.title=TM/HM Moves +GUI.tmUnchangedRadioButton.toolTipText=Leave the moves in TMs as they are.
If Metronome Only Mode is selected, all TMs are changed to Metronome and this setting has no effect. +GUI.tmUnchangedRadioButton.text=Unchanged +GUI.tmRandomRadioButton.toolTipText=Give each TM a new move.
HM moves are not affected, nor can they be selected to be put in TMs.
Each TM will still be unique.
If Metronome Only Mode is selected, all TMs are changed to Metronome and this setting has no effect. +GUI.tmRandomRadioButton.text=Random +GUI.tmFullHMCompatibilityCheckBox.toolTipText=If you select this option, then every Pokemon will learn every HM, regardless of any other options you check. +GUI.tmFullHMCompatibilityCheckBox.text=Full HM Compatibility +GUI.tmLevelupMoveSanityCheckBox.toolTipText=If you select this option, then Pokemon will be guaranteed to learn TMs of moves that they learn by levelup.
Otherwise, move compatibility will be left alone or decided randomly, depending on your other choices. +GUI.tmLevelupMoveSanityCheckBox.text=TM/Levelup Move Sanity +GUI.tmKeepFieldMoveTMsCheckBox.toolTipText=If you select this, TMs that contain field-use moves will be left alone.
This includes things like Dig & Teleport, but not healing moves (Softboiled). +GUI.tmKeepFieldMoveTMsCheckBox.text=Keep Field Move TMs +GUI.tmForceGoodDamagingCheckBox.toolTipText=Selecting this option will allow you to use the slider below to select a proportion of the randomized TMs to be forced to be good damaging moves.
Note that other TMs can still be selected as good damaging moves randomly, this is simply an additional chance. +GUI.tmForceGoodDamagingCheckBox.text=Force % of Good Damaging Moves: +GUI.tmForceGoodDamagingSlider.toolTipText=Use this slider to select the probability of good damaging moves for the option above.
Note that other TMs can still be selected as good damaging moves randomly, this is simply an additional chance. +GUI.tmNoGameBreakingMovesCheckBox.toolTipText=Checking this checkbox will stop moves that can break early/late games being available in randomized TMs.
In first generation games, Dragon Rage, SonicBoom, Spore, and every OHKO move are banned.
In second generation onwards, only SonicBoom and Dragon Rage are banned (because OHKO moves and sleep are significantly less broken). +GUI.tmNoGameBreakingMovesCheckBox.text=No Game-Breaking Moves +GUI.thcPanel.title=TM/HM Compatibility +GUI.thcUnchangedRadioButton.toolTipText=Every Pokemon will be able to learn the same TMs that it could before.
Note that this applies even if you change the TM moves, which could lead to some odd combinations. +GUI.thcUnchangedRadioButton.text=Unchanged +GUI.thcRandomPreferSameTypeRadioButton.toolTipText=Randomize the TMs and HMs that each Pokemon can learn.
Each TM or HM will have:
A 90% chance of being learnable if the Pokemon has it as (one of) its type(s).
A 50% chance of being learnable if the move is Normal and the Pokemon isn't.
A 25% chance otherwise. +GUI.thcRandomPreferSameTypeRadioButton.text=Random (prefer same type) +GUI.thcRandomCompletelyRadioButton.toolTipText=Randomize the TMs and HMs that each Pokemon can learn.
Each TM or HM will have a 50% chance of being learnable regardless of type. +GUI.thcRandomCompletelyRadioButton.text=Random (completely) +GUI.thcFullCompatibilityRadioButton.toolTipText=Select this option to allow every Pokemon to learn every TM/HM.
This can be fun to mess around with, but it might make the game too easy. +GUI.thcFullCompatibilityRadioButton.text=Full Compatibility +GUI.mtPanel.title=Move Tutors +GUI.mtMovesPanel.title=Move Tutor Moves +GUI.mtNoExistLabel.text=This game does not have any Move Tutors, or they are not randomizable yet. +GUI.mtUnchangedRadioButton.toolTipText=Leave the moves taught by tutors as they are.
If Metronome Only Mode is selected, all Move Tutors are changed to Metronome and this setting has no effect. +GUI.mtUnchangedRadioButton.text=Unchanged +GUI.mtRandomRadioButton.toolTipText=Give each move tutor slot a new move.
Each Move Tutor move will still be unique, and they will not overlap with TM/HM moves.
If Metronome Only Mode is selected, all Move Tutors are changed to Metronome and this setting has no effect. +GUI.mtRandomRadioButton.text=Random +GUI.mtLevelupMoveSanityCheckBox.toolTipText=If you select this option, then Pokemon will be guaranteed to learn move tutors of moves that they learn by levelup.
Otherwise, move compatibility will be left alone or decided randomly, depending on your other choices. +GUI.mtLevelupMoveSanityCheckBox.text=Tutor/Levelup Move Sanity +GUI.mtKeepFieldMoveTutorsCheckBox.toolTipText=If you select this, move tutors that contain field-use moves will be left alone.
This includes things like Headbutt, but not healing moves (Softboiled). +GUI.mtKeepFieldMoveTutorsCheckBox.text=Keep Field Move Tutors +GUI.mtForceGoodDamagingCheckBox.toolTipText=Selecting this option will allow you to use the slider below to select a proportion of the randomized tutors to be forced to be good damaging moves.
Note that other tutors can still be selected as good damaging moves randomly, this is simply an additional chance. +GUI.mtForceGoodDamagingCheckBox.text=Force % of Good Damaging Moves: +GUI.mtForceGoodDamagingSlider.toolTipText=Use this slider to select the probability of good damaging moves for the option above.
Note that other tutors can still be selected as good damaging moves randomly, this is simply an additional chance. +GUI.mtNoGameBreakingMovesCheckBox.toolTipText=Checking this checkbox will stop moves that can break early/late games being available in randomized move tutors.
In first generation games, Dragon Rage, SonicBoom, Spore, and every OHKO move are banned.
In second generation onwards, only SonicBoom and Dragon Rage are banned (because OHKO moves and sleep are significantly less broken). +GUI.mtNoGameBreakingMovesCheckBox.text=No Game-Breaking Moves +GUI.mtcPanel.title=Move Tutor Compatibility +GUI.mtcUnchangedRadioButton.toolTipText=Every Pokemon will be able to learn the same move tutor moves that it could before.
Note that this applies even if you randomize the moves, which could lead to some odd combinations. +GUI.mtcUnchangedRadioButton.text=Unchanged +GUI.mtcRandomPreferSameTypeRadioButton.toolTipText=Randomize the Move Tutor moves that each Pokemon can learn.
Each move will have:
A 90% chance of being learnable if the Pokemon has it as (one of) its type(s).
A 50% chance of being learnable if the move is Normal and the Pokemon isn't.
A 25% chance otherwise. +GUI.mtcRandomPreferSameTypeRadioButton.text=Random (prefer same type) +GUI.mtcRandomCompletelyRadioButton.toolTipText=Randomize the Move Tutor moves that each Pokemon can learn.
Each move will have a 50% chance of being learnable regardless of type. +GUI.mtcRandomCompletelyRadioButton.text=Random (completely) +GUI.mtcFullCompatibilityRadioButton.toolTipText=Select this option to allow every Pokemon to learn every Move Tutor move.
This can be fun to mess around with, but it might make the game too easy. +GUI.mtcFullCompatibilityRadioButton.text=Full Compatibility +GUI.itemsPanel.title=Items +GUI.fiPanel.title=Field Items +GUI.fiUnchangedRadioButton.toolTipText=Items on the ground and hidden items remain the same. +GUI.fiUnchangedRadioButton.text=Unchanged +GUI.fiShuffleRadioButton.toolTipText=Selecting this will take the full set of items that can be picked up from item balls, as well as hidden items,
and randomize their order so each item appears in a new place.
Key items are left in their original location and glitch items are excluded.
This stops item balls containing seriously overpowered items, but more powerful items may be available early on.
TMs will remain in the same item balls, but the numbers of the TMs in them will be shuffled among the set. +GUI.fiShuffleRadioButton.text=Shuffle +GUI.fiRandomRadioButton.toolTipText=Selecting this will place a new random item in each item ball & hidden item slot.
Key items & glitch items are automatically excluded.
This means item balls can contain seriously overpowered items such as Master Balls.
TMs will remain in the same item balls, but the numbers of the TMs in them will be randomized, including TMs not usually available from item balls.
All TMs will still be available at least once in the game. +GUI.fiRandomRadioButton.text=Random +GUI.fiRandomEvenDistributionRadioButton.toolTipText=This is the same as Random, except that the randomizer will control the number of times each item is placed.
This applies to the entire game, main- and post-game. +GUI.fiRandomEvenDistributionRadioButton.text=Random (even distribution) +GUI.fiBanBadItemsCheckBox.toolTipText=Checking this will remove "bad" items that don't do much from the set of possible random items for field items, such as berries and mail. +GUI.fiBanBadItemsCheckBox.text=Ban Bad Items +GUI.shPanel.title=Special Shops +GUI.shUnchangedRadioButton.toolTipText=Items in shops remain the same. +GUI.shUnchangedRadioButton.text=Unchanged +GUI.shShuffleRadioButton.toolTipText=Items in non-main shops are shuffled. +GUI.shShuffleRadioButton.text=Shuffle +GUI.shRandomRadioButton.toolTipText=Items in non-main shops are randomized. +GUI.shRandomRadioButton.text=Random +GUI.shBanOverpoweredShopItemsCheckBox.toolTipText=Checking this will remove overpowered shop items from the pool, such as the Lucky Egg and
items that sell for high prices to maniacs. +GUI.shBanOverpoweredShopItemsCheckBox.text=Ban Overpowered Shop Items +GUI.shBanBadItemsCheckBox.toolTipText=Checking this will remove "bad" items that don't do much from the set of possible random items for shop items, such as berries and mail. +GUI.shBanBadItemsCheckBox.text=Ban Bad Items +GUI.shBanRegularShopItemsCheckBox.toolTipText=Checking this will remove regular shop items from the possible random items for shop items. +GUI.shBanRegularShopItemsCheckBox.text=Ban Regular Shop Items +GUI.shBalanceShopItemPricesCheckBox.toolTipText=Checking this will change prices for many items in order to make them more balanced.
Be aware: prices for some items are seriously imbalanced without this setting. +GUI.shBalanceShopItemPricesCheckBox.text=Balance Shop Item Prices +GUI.shGuaranteeEvolutionItemsCheckBox.toolTipText=Checking this will ensure all evolution items appear in shops. +GUI.shGuaranteeEvolutionItemsCheckBox.text=Guarantee Evolution Items +GUI.shGuaranteeXItemsCheckbox.tooltipText=Checking this will ensure all X items (including Guard Spec. and Dire Hit) appear in shops. +GUI.shGuaranteeXItemsCheckbox.text=Guarantee X Items +GUI.puPanel.title=Pickup Items +GUI.puUnchangedRadioButton.toolTipText=Items obtained via the Pickup ability remain the same. +GUI.puUnchangedRadioButton.text=Unchanged +GUI.puRandomRadioButton.toolTipText=Items obtained via the Pickup ability are randomized. +GUI.puRandomRadioButton.text=Random +GUI.puBanBadItemsCheckBox.toolTipText=Checking this will remove "bad" items that don't do much from the set of possible random items for the Pickup item table, such as berries and mail. +GUI.puBanBadItemsCheckBox.text=Ban Bad Items +GUI.miscTweaksPanel.title=Misc. Tweaks +GUI.miscPanel.title=Misc. Tweaks +GUI.miscNoneAvailableLabel.text=There are no tweaks available for the currently loaded game. +GUI.miscBWExpPatchCheckBox.toolTipText=Select this to patch the game you are randomizing to use the Black/White new XP gain system.
This system gives EXP in a different way, rewarding players for beating higher level Pokemon with lower levels.
This is currently available only for English R/B/Y and G/S/C. +GUI.miscBWExpPatchCheckBox.text=B/W Exp Patch +GUI.miscNerfXAccuracyCheckBox.toolTipText=X Accuracy in Generation 1 games is a pretty broken item, given that it gives 100% accuracy to any and all moves used.
Apply this code tweak to stop X Accuracy from working on sleep moves, trapping moves & one-hit KO moves.
Doing this means that the player cannot use X Accuracy and lock the opponents into infinite sleep/Wrap, nor can they just use one-hit KO moves on everything.
Applying this patch will also remove the one-hit KO moves from the "broken moves" list if you use that option, since they aren't really broken without X Accuracy.
Credit to Mountebank for this patch. +GUI.miscNerfXAccuracyCheckBox.text=Nerf X Accuracy +GUI.miscFixCritRateCheckBox.toolTipText=Selecting this option will "fix" Generation 1's critical hit rate to be the same as the other games (1/16), instead of being based on Speed.
Focus Energy and Dire Hit will also be fixed to increase crit rate instead of decreasing it.
"High crit rate" moves such as Slash will have double the normal crit chance, like in later games. +GUI.miscFixCritRateCheckBox.text="Fix" Crit Rate +GUI.miscFastestTextCheckBox.toolTipText=Selecting this option will make all text boxes in the game show up with minimum delay, regardless of the text speed the player sets in Options.
+GUI.miscFastestTextCheckBox.text=Fastest Text +GUI.miscRunningShoesIndoorsCheckBox.toolTipText=Selecting this option will allow you to use the Running Shoes in any location, just like in later games. +GUI.miscRunningShoesIndoorsCheckBox.text=Running Shoes Indoors +GUI.miscRandomizePCPotionCheckBox.toolTipText=Selecting this option will randomize the Potion that is in your item PC at the beginning of the game to any other "useful" item. +GUI.miscRandomizePCPotionCheckBox.text=Randomize PC Potion +GUI.miscAllowPikachuEvolutionCheckBox.toolTipText=Selecting this option will let you evolve Pikachu into Raichu with a Thunder Stone like any other game. +GUI.miscAllowPikachuEvolutionCheckBox.text=Allow Pikachu Evolution +GUI.miscGiveNationalDexAtCheckBox.toolTipText=If this is checked then the National Dex will be given to the player at the start of the game with the regular Pokedex.
It is useful for tracking Pokemon outside of the regional dex when using the Pokedex.
Important caveats:
For FRLG only: this setting is necessary for Kanto Pokemon to evolve into Non-Kanto Pokemon, i.e. Golbat evolving into Crobat.
For DPPt only: the National Dex will instead be obtained after speaking to Professor Rowan once after obtaining the regular Pokedex. +GUI.miscGiveNationalDexAtCheckBox.text=Give National Dex at Start +GUI.miscUpdateTypeEffectivenessCheckBox.toolTipText=If this is checked, the type weaknesses/strengths/immunities will be updated to what they are in Gen 6 and up. This does NOT add the Fairy type.
For Gen 1, this changes the following:
  • Ice is now not very effective against Fire.
  • Poison is now neutral against Bug.
  • Bug is now not very effective against Poison.
  • Ghost is now super effective against Psychic.
For Gens 2-5, this changes the following:
  • Ghost is now neutral against Steel.
  • Dark is now neutral against Steel.
+GUI.miscUpdateTypeEffectivenessCheckBox.text=Update Type Effectiveness +GUI.forceChallengeMode.toolTipText=If this is checked, then Challenge Mode will be forcibly enabled, bypassing the Key System entirely.
You will not be able to access Easy or Normal Mode if you enable this setting. +GUI.forceChallengeMode.text=Force Challenge Mode +GUI.miscLowerCasePokemonNamesCheckBox.toolTipText=If this is selected, all Pokemon names will be made into Camel Case.
e.g. VENUSAUR becomes Venusaur.
This looks better in Gen3/Gen4 games, and OK in Gen1/Gen2 games. +GUI.miscLowerCasePokemonNamesCheckBox.text=Lower Case Pokemon Names +GUI.miscRandomizeCatchingTutorialCheckBox.toolTipText=Selecting this option will randomize the Pokemon participating in the game's catching tutorial. +GUI.miscRandomizeCatchingTutorialCheckBox.text=Randomize Catching Tutorial +GUI.miscBanLuckyEggCheckBox.toolTipText=Bans Lucky Egg from showing up as any kind of randomized item.
If the original game contains a Lucky Egg in any non-randomized place, that will still be the case. +GUI.miscBanLuckyEggCheckBox.text=Ban Lucky Egg +GUI.miscNoFreeLuckyEggCheckBox.toolTipText=Causes Professor Juniper to not give you a Lucky Egg for free in the Chargestone Cave (BW)/Celestial Tower (BW2). +GUI.miscNoFreeLuckyEggCheckBox.text=No Free Lucky Egg +GUI.miscBanBigMoneyManiacCheckBox.toolTipText=Bans maniac items worth more than $10,000 from showing up as any kind of randomized item. +GUI.miscBanBigMoneyManiacCheckBox.text=Ban Big Money Maniac Items +GUI.cantWriteConfigFile=WARNING: The randomizer is unable to write its config file to the directory it is in.\nThis means that it will probably be unable to save the randomized ROMs it creates.\nPlease run the randomizer from a directory where you can write files.\nYou can try to use the randomizer as-is, but it will probably not work. +GUI.copyNameFilesDialog.text=You appear to have customized name files in the config directory left over from an old version of the randomizer.\nWould you like these files to be copied to the main program directory so they are used in this version? +GUI.copyNameFilesDialog.title=Copy custom names? +GUI.convertNameFilesDialog.text=You appear to have customized name files in the randomizer directory left over from an old version of the randomizer.\nWould you like these files to be converted to the new custom names format so they are used in this version? +GUI.convertNameFilesDialog.title=Convert custom names? +GUI.copyNameFilesFailed=At least one file was not able to be copied. +GUI.convertNameFilesFailed=Could not convert custom names to the new format. +GUI.configFileMissing=The file %s is missing from the configuration and so this program cannot start.\nPlease make sure you extract the program from the ZIP file before running it. +GUI.loadingText=Loading... +GUI.savingText=Saving... +GUI.loadFailed=There was an unhandled exception trying to load your ROM.\nA log file containing some details has been saved to %s.\nPlease include this file in any bug reports you do. +GUI.loadFailedNoLog=There was an unhandled exception trying to load your ROM. +GUI.unreadableRom=Could not read %s from disk.\nPlease ensure you have read access to the ROM you're trying to open. +GUI.tooShortToBeARom=%s appears to be a blank or nearly blank file.\nCheck to make sure you're opening the right file. +GUI.openedZIPfile=%s is a ZIP archive, not a ROM.\nYou should extract it and try to randomize the actual ROM file inside. +GUI.openedRARfile=%s is a RAR archive, not a ROM.\nYou should extract it and try to randomize the actual ROM file inside. +GUI.openedIPSfile=%s is an IPS patch, not a ROM.\nYou should apply it to a ROM first before trying to randomize the result. +GUI.unsupportedRom=Could not load %s - it's not a supported ROM. +GUI.encryptedRom=Could not load %s as it appears to be an encrypted ROM.
Universal Pokemon Randomizer ZX does not currently support encrypted 3DS ROMs.
If you believe your ROM is actually decrypted, please try decrypting your ROM with a different tool or try using a different ROM. +GUI.romSupportPrefix=Support: +GUI.processFailed=There was an unhandled exception trying to process your ROM.\nA log file containing some details has been saved to %s.\nPlease include this file in any bug reports you do. +GUI.processFailedNoLog=There was an unhandled exception trying to process your ROM. +GUI.raceModeRequirements=You can't use Race Mode without randomizing either the wild Pokemon or the trainer Pokemon.\nReview this and try again. +GUI.pokeLimitNotChosen=You enabled the option to limit the Pokemon that appear, but didn't choose any to allow.\nSelect some by clicking on the "Limit Pokemon" button and try again. +GUI.presetFailTrainerClasses=Can't use this preset because you have a different set of random trainer class names to the creator.\nHave them make you a rndp file instead. +GUI.presetFailTrainerNames=Can't use this preset because you have a different set of random trainer names to the creator.\nHave them make you a rndp file instead. +GUI.presetFailNicknames=Can't use this preset because you have a different set of random nicknames to the creator.\nHave them make you a rndp file instead. +GUI.presetDifferentCustomNames=You don't have the same set of custom names as the creator. If you continue, you will have different custom names (if that setting is in use).\nIf you want to play with the same custom names, have them send you a preset file (.rndp) instead. +GUI.starterUnavailable=Could not set one of the custom starters from the settings file because it does not exist in this generation. +GUI.saveFailedMessage=Error during randomization: %s.\nIf this is the first time you've seen this message, try again.\nIf it keeps happening, try loosening any restrictive settings you've selected.\nA log file containing some details has been saved to %s.\nPlease include this file in any bug reports you do. +GUI.saveFailedMessageNoLog=Error during randomization: %s.\nIf this is the first time you've seen this message, try again.\nIf it keeps happening, try loosening any restrictive settings you've selected. +GUI.saveFailedIO=There was an unhandled exception trying to save your ROM to disk.\nA log file containing some details has been saved to %s.\nPlease include this file in any bug reports you do. +GUI.saveFailedIONoLog=There was an unhandled exception trying to save your ROM to disk. +GUI.cannotWriteToLocation=The randomizer is not allowed to write this file: %s.\nPlease try saving your ROM to a different location. +GUI.raceModeCheckValuePopup=Your check value for the race is:\n%08X\nDistribute this along with the preset file, if you're the race maker!\nIf you received this in a race, compare it to the value the race maker gave. +GUI.saveLogDialog.text=Do you want to save a log file of the randomization performed?\nThis may allow you to gain an unfair advantage, do not do so if you are doing something like a race. +GUI.saveLogDialog.title=Save Log? +GUI.logSaveFailed=Could not save log file! +GUI.logSaved=Log file saved to\n%s.log +GUI.randomizationDone=Randomization Complete. You can now play! +GUI.saveFailed=There was an unhandled exception trying to save your ROM.\nA log file containing some details has been saved to %s.\nPlease include this file in any bug reports you do. +GUI.saveFailedNoLog=There was an unhandled exception trying to save your ROM. +GUI.cantOverwriteDS=You cannot overwrite the original ROM when you save a DS randomization.\nPlease choose a different filename. +GUI.noUpdates=No new updates found. +GUI.settingsFileOlder=This settings file was created by an older randomizer version.\nThe randomizer will attempt to open it anyway, but you should look out for new options added since you made it.\nTo prevent this message from appearing every time you load this settings file, re-save your settings file. +GUI.settingsFileNewer=This settings file is for a newer randomizer version.\nYou should upgrade your randomizer. +GUI.invalidSettingsFile=Settings file is not valid. +GUI.settingsLoaded=Settings loaded from %s. +GUI.settingsLoadFailed=Settings file load failed. Please try again. +GUI.settingsSaveFailed=Settings file save failed. Please try again. +GUI.cantLoadCustomNames=Could not initialise custom names data.\nPlease redownload the randomizer and try again. +GUI.customNamesEditorMenuItem.text=Custom Names Editor +GUI.mtMovesPanel.toolTipText= +GUI.mtMovesPanel.text= +GUI.mtCompatPanel.toolTipText= +GUI.mtCompatPanel.text= +GUI.shopItemsPanel.toolTipText= +GUI.shopItemsPanel.text= +GUI.miscTweaksPanel.toolTipText= +GUI.miscTweaksPanel.text= +GUI.gameMascotLabel.toolTipText= +GUI.gameMascotLabel.text= +Log.InvalidRomLoaded=The ROM you loaded is not a clean, official ROM.\nRandomizing ROM hacks or bad ROM dumps is not supported and may cause issues.\n +GenerationLimitDialog.includePokemonHeader.text=Include Pokemon from: +GenerationLimitDialog.relatedPokemonHeader.text=... and related Pokemon from: +GenerationLimitDialog.gen1CB.text=Generation 1 +GenerationLimitDialog.gen2CB.text=Generation 2 +GenerationLimitDialog.gen3CB.text=Generation 3 +GenerationLimitDialog.gen4CB.text=Generation 4 +GenerationLimitDialog.gen5CB.text=Generation 5 +GenerationLimitDialog.okButton.text=OK +GenerationLimitDialog.gen2Short=Gen 2 +GenerationLimitDialog.gen4Short=Gen 4 +GenerationLimitDialog.gen1Short=Gen 1 +GenerationLimitDialog.gen3Short=Gen 3 +GenerationLimitDialog.title=Choose Pokemon to allow +GenerationLimitDialog.cancelButton.text=Cancel +GenerationLimitDialog.warningRomHackLabel.text=
WARNING: This functionality will NOT work correctly with ROM hacks
that change the available Pokemon or add new ones!
+GenerationLimitDialog.warningXYLabel.text=
To prevent X/Y from softlocking, you must
select at least one of Generations 1, 2, 3, or 4
+CodeTweaksDialog.headerLabel.text=Choose tweaks to enable... +CodeTweaksDialog.title=Code Tweaks +CodeTweaksDialog.okButton.text=OK +CodeTweaksDialog.cancelButton.text=Cancel +PresetMakeDialog.doneButton.text=Done +PresetMakeDialog.title=Randomization Completed - Seed Details +PresetMakeDialog.produceFileButton.text=Produce File +PresetMakeDialog.gameRandomizedLabel.text=Your game has been successfully randomized! +PresetMakeDialog.settingsToGiveLabel.text=Below are the settings you can give to other people to produce the same randomization as you just did. +PresetMakeDialog.seedFieldLabel.text=Random Seed: +PresetMakeDialog.configStringFieldLabel.text=Config String: +PresetMakeDialog.canProduceFileLabel.text=Alternatively you can produce a file which contains this data which you can then send to people. +PresetLoadDialog.romFileButton.text=... +PresetLoadDialog.presetFileButton.text=... +PresetLoadDialog.acceptButton.text=Apply Randomization Settings +PresetLoadDialog.presetFileLabel.text=Preset File: +PresetLoadDialog.cancelButton.text=Cancel +PresetLoadDialog.romRequiredLabel.text=ROM Required: Enter settings above first. +PresetLoadDialog.title=Use Preset +PresetLoadDialog.seedBoxLabel.text=Random Seed: +PresetLoadDialog.configStringBoxLabel.text=Config String: +PresetLoadDialog.orLabel.text=-OR- +PresetLoadDialog.romFileBoxLabel.text=Rom File: +PresetLoadDialog.romRequiredLabel.textWithROM=ROM Required: %s +PresetLoadDialog.invalidSeedFile=The seed file did not contain valid settings. +PresetLoadDialog.loadingSeedFileFailed=Could not load seed file. +PresetLoadDialog.notRequiredROM=This isn't the required ROM.\nRequired: %s\nThis ROM: %s +PresetLoadDialog.newerVersionRequired=The preset file was generated with a newer randomizer version. Try downloading the latest version. +PresetLoadDialog.olderVersionRequired=The preset file was generated with an older randomizer version. It can only be used with version %s. +CodeTweaks.bwPatch.name=B/W Exp Patch +CodeTweaks.bwPatch.toolTipText=Select this to patch the game you are randomizing to use the Black/White new XP gain system.
This system gives EXP in a different way, rewarding players for beating higher level Pokemon with lower levels.
This is currently available only for English R/B/Y and G/S/C. +CodeTweaks.nerfXAcc.name=Nerf X Accuracy +CodeTweaks.nerfXAcc.toolTipText=X Accuracy in Generation 1 games is a pretty broken item, given that it gives 100% accuracy to any and all moves used.
Apply this code tweak to stop X Accuracy from working on sleep moves, trapping moves & one-hit KO moves.
Doing this means that the player cannot use X Accuracy and lock the opponents into infinite sleep/Wrap, nor can they just use one-hit KO moves on everything.
Applying this patch will also remove the one-hit KO moves from the "broken moves" list if you use that option, since they aren't really broken without X Accuracy.
Credit to Mountebank for this patch. +CodeTweaks.critRateFix.name="Fix" Crit Rate +CodeTweaks.critRateFix.toolTipText=Selecting this option will "fix" Generation 1's critical hit rate to be the same as the other games (1/16), instead of being based on Speed.
Focus Energy and Dire Hit will also be fixed to increase crit rate instead of decreasing it.
"High crit rate" moves such as Slash will have double the normal crit chance, like in later games. +CodeTweaks.fastestText.toolTipText=Selecting this option will make all text boxes in the game show up with minimum delay, regardless of the text speed the player sets in Options.
+CodeTweaks.fastestText.name=Fastest Text +CodeTweaks.runningShoes.toolTipText=Selecting this option will allow you to use the Running Shoes in any location, just like in later games. +CodeTweaks.runningShoes.name=Running Shoes Indoors +CodeTweaks.pcPotion.toolTipText=Selecting this option will randomize the Potion that is in your item PC at the beginning of the game to any other "useful" item. +CodeTweaks.pcPotion.name=Randomize PC Potion +CodeTweaks.pikachuEvo.toolTipText=Selecting this option will let you evolve Pikachu into Raichu with a Thunder Stone like any other game. +CodeTweaks.pikachuEvo.name=Allow Pikachu Evolution +CodeTweaks.nationalDex.toolTipText=If this is checked then the National Dex will be given to the player at the start of the game with the regular Pokedex.
This is only necessary for FRLG, where certain Pokemon can't evolve if you don't have it.
It is useful in other games for tracking Pokemon outside of the regional dex when using the Pokedex. +CodeTweaks.nationalDex.name=Give National Dex at Start +CodeTweaks.typeEffectiveness.toolTipText=If this is checked, the type weakness/strengths/immunities will be updated to the current set.
For RBY, this means Ghost will be Super Effective against Psychic, among other changes. +CodeTweaks.typeEffectiveness.name=Update Type Effectiveness +CodeTweaks.forceChallengeMode.toolTipText=If this is checked, then Challenge Mode will be forcibly enabled, bypassing the Key System entirely.
You will not be able to access Easy or Normal Mode if you enable this setting. +CodeTweaks.forceChallengeMode.name=Force Challenge Mode +CodeTweaks.lowerCaseNames.toolTipText=If this is selected, all Pokemon names will be made into Camel Case.
e.g. VENUSAUR becomes Venusaur.
This looks better in Gen3/Gen4 games, and OK in Gen1/Gen2 games. +CodeTweaks.lowerCaseNames.name=Lower Case Pokemon Names +CodeTweaks.catchingTutorial.toolTipText=Selecting this option will randomize the Pokemon participating in the game's catching tutorial. +CodeTweaks.catchingTutorial.name=Randomize Catching Tutorial +CodeTweaks.luckyEgg.toolTipText=Bans Lucky Egg from showing up as any kind of randomized item.
If the original game contains a Lucky Egg in any non-randomized place, that will still be the case. +CodeTweaks.luckyEgg.name=Ban Lucky Egg +CodeTweaks.freeLuckyEgg.toolTipText=Causes Professor Juniper to not give you a Lucky Egg for free in the Chargestone Cave (BW)/Celestial Tower (BW2). +CodeTweaks.freeLuckyEgg.name=No Free Lucky Egg +CodeTweaks.maniacItems.toolTipText=Bans maniac items worth more than $10,000 from showing up as any kind of randomized item. +CodeTweaks.maniacItems.name=Ban Big Money Maniac Items +CodeTweaks.sosBattles.toolTipText=Makes it possible for every Pokemon to call allies during wild Pokemon battles.
Without this setting, only Pokemon that could call allies in the base game will be able to call allies. +CodeTweaks.sosBattles.name=All Wild Pokemon Can Call Allies +CodeTweaks.balanceStaticLevels.toolTipText=Changes levels of Static Pokemon to be more balanced. Currently applies only to fossil Pokemon in FRLG and BW1. +CodeTweaks.balanceStaticLevels.name=Balance Static Pokemon Levels +CodeTweaks.retainAltFormes.toolTipText=Lets all party Pokemon that are in an alternate forme retain their alternate forme when the game is reset.
Without this setting, Mega Evolutions, Primal Reversions, Ash-Greninja, Zygarde Complete Forme, and Ultra Necrozma will revert to their base forme upon reset.
In ORAS and Gen 7, this setting also prevents certain alternate formes from being reverted after a battle if these formes are not the result of an in-battle transformation.
This includes Primal Groudon, Primal Kyogre, Wishiwashi School Form, and Minior. +CodeTweaks.retainAltFormes.name=Don't Revert Temporary Alt Formes +CodeTweaks.runWithoutRunningShoes.toolTipText=Allows you to run before acquiring the Running Shoes. +CodeTweaks.runWithoutRunningShoes.name=Run Without Running Shoes +CodeTweaks.fasterHpAndExpBars.toolTipText=Doubles the scrolling speed of the HP and EXP bars that appear in battle.
In practice, this makes them scroll at the same speed as in the Gen 3 games. +CodeTweaks.fasterHpAndExpBars.name=Faster HP and EXP Bars +CodeTweaks.fastDistortionWorld.name=Fast Distortion World +CodeTweaks.fastDistortionWorld.toolTipText=Cuts out most of the Distortion World by instantly warping you to the Cyrus fight when you enter it. +CodeTweaks.updateRotomFormeTyping.name=Update Rotom Appliance Typings +CodeTweaks.updateRotomFormeTyping.toolTipText=Updates the typings of Rotom's alternate formes (i.e., the appliances) to match the typings they have in Gen 5 and onwards.
For example, Wash Rotom will change from Electric/Ghost to Electric/Water. +CodeTweaks.disableLowHpMusic.name=Disable Low HP Music +CodeTweaks.disableLowHpMusic.toolTipText=Disables the music that plays when one of the player's Pokemon is at low HP in battle, ensuring that the current song will continue playing no matter what. +CustomNamesEditorDialog.trainerNamesSP.TabConstraints.tabTitle=Trainer Names +CustomNamesEditorDialog.title=Custom Names Editor +CustomNamesEditorDialog.closeBtn.text=Close +CustomNamesEditorDialog.saveBtn.text=Save +CustomNamesEditorDialog.nicknamesSP.TabConstraints.tabTitle=Pokemon Nicknames +CustomNamesEditorDialog.doublesTrainerClassesSP.TabConstraints.tabTitle=Doubles Trainer Classes +CustomNamesEditorDialog.doublesTrainerNamesSP.TabConstraints.tabTitle=Doubles Trainer Names +CustomNamesEditorDialog.trainerClassesSP.TabConstraints.tabTitle=Trainer Classes +GUI.pbsFollowMegaEvosCheckBox.text=Follow Mega Evolutions +GUI.pbsFollowMegaEvosCheckBox.toolTipText=When this is selected and base stats are shuffled or randomized, Mega Evolutions will inherit from their base form's rolls.
Shuffle: The same new ordering of stats will be used.
Randomized: The Mega Evolution will have the same proportion of stats as the base form.
Not recommended for use in Gen 7 unless you know what you're doing; see the Important Information page on the wiki for more info. +GUI.paFollowMegaEvosCheckBox.text=Follow Mega Evolutions +GUI.paFollowMegaEvosCheckBox.toolTipText=When this is selected and abilities are randomized, non-split Mega Evolutions of a Pokemon will inherit that Pokemon's random abilities.
Not recommended for use in Gen 7 unless you know what you're doing; see the Important Information page on the wiki for more info. +GUI.ptFollowMegaEvosCheckBox.text=Follow Mega Evolutions +GUI.ptFollowMegaEvosCheckBox.toolTipText=When this is selected and types are randomized, non-split Mega Evolutions of a Pokemon will inherit that Pokemon's random types (perhaps adding another type).
Not recommended for use in Gen 7 unless you know what you're doing; see the Important Information page on the wiki for more info. +GUI.spAllowAltFormesCheckBox.text=Allow Alternate Formes +GUI.spAllowAltFormesCheckBox.toolTipText=Allow alternate formes of Pokemon, such as the various formes of Rotom or Deoxys, to appear as Starter Pokemon.
Note that their traits are randomized/shuffled separately from their base formes. +GUI.stpAllowAltFormesCheckBox.text=Allow Alternate Formes +GUI.stpAllowAltFormesCheckBox.toolTipText=Allow alternate formes of Pokemon, such as the various formes of Rotom or Deoxys, to appear as Static Pokemon.
Note that their traits are randomized/shuffled separately from their base formes. +GUI.stpSwapMegaEvosCheckBox.text=Swap Mega Evolvables +GUI.stpSwapMegaEvosCheckBox.toolTipText=Swap Statics capable of Mega Evolution with another Pokemon capable of Mega Evolution.
This affects Lucario in X/Y and Latios/Latias in OR/AS. +GUI.stpPercentageLevelModifierCheckBox.text=Percentage Level Modifier: +GUI.stpPercentageLevelModifierCheckBox.tooltipText=Checking this will enable a percentage-based level modifier for every Static Pokemon. +GUI.tpSwapMegaEvosCheckBox.text=Swap Mega Evolvables +GUI.tpSwapMegaEvosCheckBox.toolTipText=Swap Trainer Pokemon capable of Mega Evolution with another Pokemon capable of Mega Evolution.
This only affects Trainer Pokemon that actually hold a Mega Stone (so, for example, it will affect Diantha's Gardevoir but not every Gardevoir)
If the "Limit Pokemon" general option is used to remove all Mega Evolutions from the game, this setting will be disabled. +GUI.ptForceDualTypeCheckBox.text=Force Dual Types +GUI.ptForceDualTypeCheckBox.toolTipText=Checking this will force all Pokemon to have two types when types are randomized +GUI.wpAllowAltFormesCheckBox.text=Allow Alternate Formes +GUI.wpAllowAltFormesCheckBox.toolTipText=Allow alternate formes of Pokemon, such as the various formes of Rotom or Deoxys, to appear as Wild Pokemon.
Note that their traits are randomized/shuffled separately from their base formes. +GUI.tpDoubleBattleModeCheckBox.text=Double Battle Mode +GUI.tpDoubleBattleModeCheckBox.toolTipText=Sets all Trainer battles to be double battles instead of single battles. The first rival battle is excluded.

Be aware that there are a few issues with this mode. In particular:
  • Entering a double battle with a single Pokemon causes some generation-dependent issues:
    • Gen 3: The empty spot will be filled by a duplicate of the single Pokemon, and entering the "Pokémon" menu will softlock the game.
    • Gen 4: The empty spot will be filled by a garbled sprite that only can use Struggle.
    • Gen 6/7: The empty spot will be filled by a shiny Bulbasaur that cannot act.
  • In Gen 5 games, Trainer pairs that are double battles in the regular game will have one incorrect text box if you let them spot you.
  • In Gen 6 games, regular Trainers will have some text issues if you talk to them directly.
+GUI.tpAddMorePokemonForLabel.text=Additional Pokemon for... +GUI.tpBossTrainersCheckBox.text=Boss Trainers +GUI.tpImportantTrainersCheckBox.text=Important Trainers +GUI.tpRegularTrainersCheckBox.text=Regular Trainers +GUI.tpBossTrainersCheckBox.toolTipText=Check this to add additional Pokemon to "Boss" Trainers (select amount of new Pokemon below). Trainers will not get more than 6 total Pokemon.
Boss Trainers include Gym Leaders, Kahunas, Team Leaders, Elite Four, and Champions. +GUI.tpImportantTrainersCheckBox.toolTipText=Check this to add additional Pokemon to "Important" Trainers (select amount of new Pokemon below). Trainers will not get more than 6 total Pokemon.
Important Trainers include Rivals/Friends, Team Admins, and other important story battles (such as Colress in BW2 and Sycamore in XY). +GUI.tpRegularTrainersCheckBox.toolTipText=Check this to add additional Pokemon to regular Trainers (select amount of new Pokemon below). Trainers will not get more than 6 total Pokemon.
Regular Trainers include all trainers that don't qualify as "Boss" or "Important" Trainers. +GUI.peAllowAltFormesCheckBox.text=Allow Alternate Formes +GUI.peAllowAltFormesCheckBox.toolTipText=Allow Pokemon to evolve into alternate formes of Pokemon, including into regional variants. +GUI.tpRandomShinyTrainerPokemonCheckBox.text=Random Shiny Trainer Pokemon +GUI.tpRandomShinyTrainerPokemonCheckBox.toolTipText=Gives a 1/256 chance of Trainer Pokemon being shiny. +GUI.totpPanel.title=Totem Pokemon +GUI.totpAllyPanel.title=Ally Pokemon +GUI.totpAuraPanel.title=Auras +GUI.totpRandomizeHeldItemsCheckBox.text=Randomize Held Items +GUI.totpAllowAltFormesCheckBox.text=Allow Alternate Formes +GUI.totpPercentageLevelModifierCheckBox.text=Percentage Level Modifier: +GUI.totpUnchangedRadioButton.text=Unchanged +GUI.totpRandomRadioButton.text=Random +GUI.totpRandomSimilarStrengthRadioButton.text=Random (similar strength) +GUI.totpAllyUnchangedRadioButton.text=Unchanged +GUI.totpAllyRandomRadioButton.text=Random +GUI.totpAllyRandomSimilarStrengthRadioButton.text=Random (similar strength) +GUI.totpAuraUnchangedRadioButton.text=Unchanged +GUI.totpAuraRandomRadioButton.text=Random +GUI.totpAuraRandomRadioButton.toolTipText.=Auras will be completely randomized.
The possible auras are +1/+2/+3 to a single stat or to every stat. +GUI.totpAuraRandomSameStrengthRadioButton.text=Random (same strength) +GUI.totpAuraRandomSameStrengthRadioButton.toolTipText=Auras will be randomized to auras with the same net gain of stages.
For example, a Totem Pokemon that previously had +2 Speed could get +2 Defense instead.
Totem Pokemon with +1/+2 to every stat will not have their auras randomized, since those auras are the only ones with 5 and 10 stages respectively. +GUI.totpUnchangedRadioButton.toolTipText=Don't change Totem Pokemon species. +GUI.totpRandomRadioButton.toolTipText=Randomize Totem Pokemon species. +GUI.totpRandomSimilarStrengthRadioButton.toolTipText=Replace every Totem Pokemon with a Pokemon of similar strength. +GUI.totpAllyRandomRadioButton.toolTipText=Randomize Ally Pokemon completely. +GUI.totpAllyRandomSimilarStrengthRadioButton.toolTipText=Replace Ally Pokemon with Pokemon of similar strength. +GUI.totpAuraUnchangedRadioButton.toolTipText=Don't change auras. +GUI.totpRandomizeHeldItemsCheckBox.toolTipText=Replace Totem Pokemon's held items with different consumable items. +GUI.totpAllowAltFormesCheckBox.toolTipText=Allows alternate formes of Pokemon to appear as Totem/Ally Pokemon. +GUI.totpPercentageLevelModifierCheckBox.toolTipText=Checking this will enable a percentage-based level modifier for every Totem/Ally Pokemon. +GUI.totpAllyUnchangedRadioButton.toolTipText=Don't change Ally Pokemon. +GUI.pmsEvolutionMovesCheckBox.text=Evolution Moves for All Pokemon +GUI.pmsEvolutionMovesCheckBox.toolTipText=Selecting this option will cause every Pokemon to learn a move upon evolution.

Use carefully with Sun/Moon 1.0 - due to a glitch, if a Pokemon has an evolution move and also learns a move
on the level that it evolved at, it will only learn the evolution move upon evolving. +GUI.windowTitle=Pokemon Randomizer +GUI.paWeighDuplicatesTogetherCheckBox.text=Combine Duplicate Abilities +GUI.paWeighDuplicatesTogetherCheckBox.toolTipText=This setting causes abilities that have the exact same effect to be considered as the same ability for randomization probability purposes. Every variation of the respective abilities can still appear.
This applies to:
Gen 3+: Insomnia/Vital Spirit, Clear Body/White Smoke, Huge Power/Pure Power, Battle Armor/Shell Armor, Cloud Nine/Air Lock
Gen 4+: Filter/Solid Rock
Gen 5+: Rough Skin/Iron Barbs, Mold Breaker/Turboblaze/Teravolt
Gen 7+: Wimp Out/Emergency Exit, Queenly Majesty/Dazzling, Gooey/Tangling Hair, Receiver/Power of Alchemy, Clear Body/White Smoke/Full Metal Body, Multiscale/Shadow Shield, Filter/Solid Rock/Prism Armor +GUI.gameUpdateApplied=Update saved for game: %s.
Note that if the update file is moved or renamed, you will have to reapply it. +GUI.nonMatchingGameUpdate=Supplied file %s is not a valid game update for %s.
Please check your game updates and try again. +GUI.invalidGameUpdate=Supplied file %s does not appear to be a valid game update.
Please check if the file is a valid game update. +GUI.savingWithGameUpdate=You've supplied a game update, so your game can only be output as a LayeredFS directory.
Make sure you have this same game update installed in Citra or on your 3DS! +GUI.keepGameLoadedAfterRandomizingMenuItem.text=Keep Game Loaded After Randomizing +GUI.unloadGameAfterRandomizingMenuItem.text=Unload Game After Randomizing +GUI.keepGameLoadedAfterRandomizing=Your loaded game and settings will now remain after a successful randomization. +GUI.unloadGameAfterRandomizing=Your loaded game and settings will now be unloaded after a successful randomization (this is the default setting). +GUI.firstStart=Welcome to the Universal Pokemon Randomizer ZX! You are using version %s. +GUI.loadGetSettingsMenuItem.text=Get/Load Settings String +GUI.invalidSettingsString=The settings string you attempted to load is invalid. +GUI.settingsStringTooNew=The settings string was generated with a newer randomizer version. Try downloading the latest version. +GUI.settingsStringTooOld=The settings string was generated with an older randomizer version. It can only be used with version %s. +GUI.settingsStringLoaded=Settings loaded from string. +GUI.settingsStringOlder=This settings string was created by an older randomizer version.\nThe randomizer will attempt to open it anyway, but you should look out for new options added since you made it. +GUI.pleaseUseTheLauncher=WARNING!!!\nTo randomize 3DS games make sure you have more than 4G of ram and start the randomizer by toggling the -Xmx4096M flag in the VM options. +GUI.invalidRomMessage=The selected ROM does not appear to be a clean, official ROM.\nThe randomizer will still attempt to load it, but you may experience issues. +GUI.tmFollowEvolutionsCheckBox.text=Follow Evolutions +GUI.tmFollowEvolutionsCheckBox.toolTipText=When this is selected and TM compatibility is randomized, evolutions of a Pokemon will inherit that Pokemon's TM compatibilities.
Additionally, when a Pokemon evolves, each TM will have:
Random (prefer same type): A 90% chance of becoming learnable if the Pokemon just gained its type through evolution, a 10% chance otherwise.
Random (completely): A 25% chance of becoming learnable regardless of type. +GUI.mtFollowEvolutionsCheckBox.text=Follow Evolutions +GUI.mtFollowEvolutionsCheckBox.toolTipText=When this is selected and move tutor compatibility is randomized, evolutions of a Pokemon will inherit that Pokemon's move tutor compatibilities.
Additionally, when a Pokemon evolves, each move will have:
Random (prefer same type): A 90% chance of becoming learnable if the Pokemon just gained its type through evolution, a 10% chance otherwise.
Random (completely): A 25% chance of becoming learnable regardless of type. +GUI.stpFixMusicAllCheckBox.text=Fix Music +GUI.stpFixMusicAllCheckBox.toolTipText=Fixes the music for all Static Pokemon encounters so that even when randomized, encounters that should have special music will play the correct song.
Note that in Gen 4/5, if special music is assigned to a Pokemon then it will always play when battling that Pokemon, even if you just find it normally in the wild. +GUI.tpHeldItemsLabel.text=Add Held Items to... +GUI.tpBossTrainersItemsCheckBox.text=Boss Trainers +GUI.tpBossTrainersItemsCheckBox.toolTipText=Check this to add or replace held items for the Pokemon of "Boss" Trainers.
Boss Trainers include Gym Leaders, Kahunas, Team Leaders, Elite Four, and Champions. +GUI.tpImportantTrainersItemsCheckBox.text=Important Trainers +GUI.tpImportantTrainersItemsCheckBox.toolTipText=Check this to add or replace held items for the Pokemon of "Important" Trainers.
Important Trainers include Rivals/Friends, Team Admins, and other important story battles (such as Colress in BW2 and Sycamore in XY). +GUI.tpRegularTrainersItemsCheckBox.text=Regular Trainers +GUI.tpRegularTrainersItemsCheckBox.toolTipText=Check this to add or replace held items for the Pokemon of regular Trainers.
Regular Trainers include all trainers that don't qualify as "Boss" or "Important" Trainers. +GUI.tpConsumableItemsOnlyCheckBox.text=Consumable Only +GUI.tpConsumableItemsOnlyCheckBox.tooltip=Items will be chosen randomly from all single-use held items that a Pokemon can eat/use on its own.
This is mostly berries, with a few other items like Focus Sash and White Herb. +GUI.tpSensibleItemsCheckBox.text=Sensible Items +GUI.tpSensibleItemsCheckBox.tooltip=Items will be chosen randomly from a subset of the held items that "make sense" for a given Pokemon.
For example, Silk Scarf won't be an option for a given Pokemon unless it knows at least one damaging normal move.
Another example is that a Yache berry will only be an option on Pokemon that are weak or double weak to ice. +GUI.tpHighestLevelGetsItemCheckBox.text=Highest Level Only +GUI.tpHighestLevelGetsItemCheckBox.tooltip=If this option is selected, a held item will be given only to a trainer's highest level Pokemon (or one of them, if there are multiple at that level).
If this is unchecked, all of a Trainer's Pokemon will be given items. +GUI.pbsAssignEvoStatsRandomlyCheckBox.text=Randomize Added Stats on Evolution +GUI.pbsAssignEvoStatsRandomlyCheckBox.tooltipText=When a Pokemon evolves, the additional BST it gains will be assigned randomly on top of the pre-evo's stats
instead of the evolved Pokemon having the exact same ratio between its stats as the pre-evo.

Applies to both regular Evolutions and Mega Evolutions (only if "Follow Evolutions"/"Follow Mega Evolutions" is selected). +GUI.tpEliteFourUniquePokemonCheckBox.text=Pokemon League Has Unique Pokemon: +GUI.tpEliteFourUniquePokemonCheckBox.toolTipText=Enabling this setting will ensure that Pokemon League Trainers (Elite Four, Champions and BW1 N/Ghetsis)
are given the specified number of unique Pokemon that will not appear on any other Trainer.
The Elite Four Trainers' highest level Pokemon are the ones that will be unique;
in the case of a level tie, it will be the Pokemon further back in the party.
Does not apply to postgame Pokemon League battles. +GUI.tpBetterMovesetsCheckBox.text=Better Movesets +GUI.tpBetterMovesetsCheckBox.toolTipText=Attempts to give Trainer Pokemon better movesets by including TM moves/tutor moves/egg moves/pre-evolution moves,
and picking moves that synergize with the Pokemon's ability/stats/other moves. diff --git a/src/com/pkrandom/newgui/CustomNamesEditorDialog.java b/src/com/pkrandom/newgui/CustomNamesEditorDialog.java new file mode 100644 index 0000000..07954ed --- /dev/null +++ b/src/com/pkrandom/newgui/CustomNamesEditorDialog.java @@ -0,0 +1,309 @@ +package com.pkrandom.newgui; + +/*----------------------------------------------------------------------------*/ +/*-- CustomNamesEditorDialog.java - a GUI interface to allow users to edit --*/ +/*-- their custom names for trainers etc. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.CustomNamesSet; +import com.pkrandom.FileFunctions; +import com.pkrandom.SysConstants; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class CustomNamesEditorDialog extends javax.swing.JDialog { + + private static final long serialVersionUID = -1421503126547242929L; + private boolean pendingChanges; + + /** + * Creates new form CustomNamesEditorDialog + */ + public CustomNamesEditorDialog(java.awt.Frame parent) { + super(parent, true); + initComponents(); + setLocationRelativeTo(parent); + + java.awt.EventQueue.invokeLater(() -> setVisible(true)); + + // load trainer names etc + try { + CustomNamesSet cns = FileFunctions.getCustomNames(); + populateNames(trainerNamesText, cns.getTrainerNames()); + populateNames(trainerClassesText, cns.getTrainerClasses()); + populateNames(doublesTrainerNamesText, cns.getDoublesTrainerNames()); + populateNames(doublesTrainerClassesText, cns.getDoublesTrainerClasses()); + populateNames(nicknamesText, cns.getPokemonNicknames()); + } catch (IOException ex) { + java.awt.EventQueue.invokeLater(() -> JOptionPane.showMessageDialog(CustomNamesEditorDialog.this, + "Your custom names file is for a different randomizer version or otherwise corrupt.")); + } + + // dialog if there's no custom names file yet + if (!new File(SysConstants.ROOT_PATH + SysConstants.customNamesFile).exists()) { + java.awt.EventQueue.invokeLater(() -> JOptionPane.showMessageDialog( + CustomNamesEditorDialog.this, + String.format( + "Welcome to the custom names editor!\nThis is where you can edit the names used for options like \"Randomize Trainer Names\".\nThe names are initially populated with a few default names included with the randomizer.\nYou can share your customized name sets with others, too!\nJust send them the %s file created in the randomizer directory.", + SysConstants.customNamesFile))); + } + + pendingChanges = false; + + addDocListener(trainerNamesText); + addDocListener(trainerClassesText); + addDocListener(doublesTrainerNamesText); + addDocListener(doublesTrainerClassesText); + addDocListener(nicknamesText); + } + + private void addDocListener(JTextArea textArea) { + textArea.getDocument().addDocumentListener(new DocumentListener() { + + @Override + public void insertUpdate(DocumentEvent e) { + pendingChanges = true; + } + + @Override + public void removeUpdate(DocumentEvent e) { + pendingChanges = true; + } + + @Override + public void changedUpdate(DocumentEvent e) { + pendingChanges = true; + } + }); + + } + + private void formWindowClosing() {// GEN-FIRST:event_formWindowClosing + attemptClose(); + }// GEN-LAST:event_formWindowClosing + + private void saveBtnActionPerformed() {// GEN-FIRST:event_saveBtnActionPerformed + save(); + }// GEN-LAST:event_saveBtnActionPerformed + + private void closeBtnActionPerformed() {// GEN-FIRST:event_closeBtnActionPerformed + attemptClose(); + }// GEN-LAST:event_closeBtnActionPerformed + + private boolean save() { + CustomNamesSet cns = new CustomNamesSet(); + cns.setTrainerNames(getNameList(trainerNamesText)); + cns.setTrainerClasses(getNameList(trainerClassesText)); + cns.setDoublesTrainerNames(getNameList(doublesTrainerNamesText)); + cns.setDoublesTrainerClasses(getNameList(doublesTrainerClassesText)); + cns.setPokemonNicknames(getNameList(nicknamesText)); + try { + byte[] data = cns.getBytes(); + FileFunctions.writeBytesToFile(SysConstants.ROOT_PATH + SysConstants.customNamesFile, data); + pendingChanges = false; + JOptionPane.showMessageDialog(this, "Custom names saved."); + return true; + } catch (IOException ex) { + JOptionPane.showMessageDialog(this, "Could not save changes."); + return false; + } + } + + private void attemptClose() { + if (pendingChanges) { + int result = JOptionPane + .showConfirmDialog(this, + "You've made some unsaved changes to your custom names.\nDo you want to save them before closing the editor?"); + if (result == JOptionPane.YES_OPTION) { + if (save()) { + dispose(); + } + } else if (result == JOptionPane.NO_OPTION) { + dispose(); + } + } else { + dispose(); + } + } + + private List getNameList(JTextArea textArea) { + String contents = textArea.getText(); + // standardize newlines + contents = contents.replace("\r\n", "\n"); + contents = contents.replace("\r", "\n"); + // split by them + String[] names = contents.split("\n"); + List results = new ArrayList<>(); + for (String name : names) { + String ln = name.trim(); + if (!ln.isEmpty()) { + results.add(ln); + } + } + return results; + } + + private void populateNames(JTextArea textArea, List names) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (String name : names) { + if (!first) { + sb.append(SysConstants.LINE_SEP); + } + first = false; + sb.append(name); + } + textArea.setText(sb.toString()); + } + + /* @formatter:off */ + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + editorTabsPane = new javax.swing.JTabbedPane(); + trainerNamesSP = new javax.swing.JScrollPane(); + trainerNamesText = new JTextArea(); + trainerClassesSP = new javax.swing.JScrollPane(); + trainerClassesText = new JTextArea(); + doublesTrainerNamesSP = new javax.swing.JScrollPane(); + doublesTrainerNamesText = new JTextArea(); + doublesTrainerClassesSP = new javax.swing.JScrollPane(); + doublesTrainerClassesText = new JTextArea(); + nicknamesSP = new javax.swing.JScrollPane(); + nicknamesText = new JTextArea(); + saveBtn = new javax.swing.JButton(); + closeBtn = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE); + java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("com/pkrandom/newgui/Bundle"); + setTitle(bundle.getString("CustomNamesEditorDialog.title")); + setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + addWindowListener(new java.awt.event.WindowAdapter() { + public void windowClosing(java.awt.event.WindowEvent evt) { + formWindowClosing(); + } + }); + + trainerNamesSP.setHorizontalScrollBar(null); + + trainerNamesText.setColumns(20); + trainerNamesText.setRows(5); + trainerNamesSP.setViewportView(trainerNamesText); + + editorTabsPane.addTab(bundle.getString("CustomNamesEditorDialog.trainerNamesSP.TabConstraints.tabTitle"), trainerNamesSP); + + trainerClassesSP.setHorizontalScrollBar(null); + + trainerClassesText.setColumns(20); + trainerClassesText.setRows(5); + trainerClassesSP.setViewportView(trainerClassesText); + + editorTabsPane.addTab(bundle.getString("CustomNamesEditorDialog.trainerClassesSP.TabConstraints.tabTitle"), trainerClassesSP); + + doublesTrainerNamesSP.setHorizontalScrollBar(null); + + doublesTrainerNamesText.setColumns(20); + doublesTrainerNamesText.setRows(5); + doublesTrainerNamesSP.setViewportView(doublesTrainerNamesText); + + editorTabsPane.addTab(bundle.getString("CustomNamesEditorDialog.doublesTrainerNamesSP.TabConstraints.tabTitle"), doublesTrainerNamesSP); + + doublesTrainerClassesSP.setHorizontalScrollBar(null); + + doublesTrainerClassesText.setColumns(20); + doublesTrainerClassesText.setRows(5); + doublesTrainerClassesSP.setViewportView(doublesTrainerClassesText); + + editorTabsPane.addTab(bundle.getString("CustomNamesEditorDialog.doublesTrainerClassesSP.TabConstraints.tabTitle"), doublesTrainerClassesSP); + + nicknamesSP.setHorizontalScrollBar(null); + + nicknamesText.setColumns(20); + nicknamesText.setRows(5); + nicknamesSP.setViewportView(nicknamesText); + + editorTabsPane.addTab(bundle.getString("CustomNamesEditorDialog.nicknamesSP.TabConstraints.tabTitle"), nicknamesSP); + + saveBtn.setText(bundle.getString("CustomNamesEditorDialog.saveBtn.text")); + saveBtn.addActionListener(evt -> saveBtnActionPerformed()); + + closeBtn.setText(bundle.getString("CustomNamesEditorDialog.closeBtn.text")); + closeBtn.addActionListener(evt -> closeBtnActionPerformed()); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(editorTabsPane, javax.swing.GroupLayout.DEFAULT_SIZE, 616, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(saveBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(closeBtn))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(editorTabsPane, javax.swing.GroupLayout.DEFAULT_SIZE, 357, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(saveBtn) + .addComponent(closeBtn)) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton closeBtn; + private javax.swing.JScrollPane doublesTrainerClassesSP; + private JTextArea doublesTrainerClassesText; + private javax.swing.JScrollPane doublesTrainerNamesSP; + private JTextArea doublesTrainerNamesText; + private javax.swing.JTabbedPane editorTabsPane; + private javax.swing.JScrollPane nicknamesSP; + private JTextArea nicknamesText; + private javax.swing.JButton saveBtn; + private javax.swing.JScrollPane trainerClassesSP; + private JTextArea trainerClassesText; + private javax.swing.JScrollPane trainerNamesSP; + private JTextArea trainerNamesText; + // End of variables declaration//GEN-END:variables + /* @formatter:on */ +} diff --git a/src/com/pkrandom/newgui/GameUpdateFilter.java b/src/com/pkrandom/newgui/GameUpdateFilter.java new file mode 100644 index 0000000..50866bb --- /dev/null +++ b/src/com/pkrandom/newgui/GameUpdateFilter.java @@ -0,0 +1,49 @@ +package com.pkrandom.newgui; + +/*----------------------------------------------------------------------------*/ +/*-- GameUpdateFilter.java - a file filter for 3DS game updates. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import javax.swing.filechooser.FileFilter; +import java.io.File; + +public class GameUpdateFilter extends FileFilter { + + @Override + public boolean accept(File arg0) { + if (arg0.isDirectory()) { + return true; // needed to allow directory navigation + } + String filename = arg0.getName(); + if (!filename.contains(".")) { + return false; + } + String extension = arg0.getName().substring(arg0.getName().lastIndexOf('.') + 1).toLowerCase(); + return extension.equals("cia"); + } + + @Override + public String getDescription() { + return "Nintendo 3DS game update (*.cia)"; + } + +} diff --git a/src/com/pkrandom/newgui/NewGenerationLimitDialog.form b/src/com/pkrandom/newgui/NewGenerationLimitDialog.form new file mode 100644 index 0000000..1f87054 --- /dev/null +++ b/src/com/pkrandom/newgui/NewGenerationLimitDialog.form @@ -0,0 +1,156 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/pkrandom/newgui/NewGenerationLimitDialog.java b/src/com/pkrandom/newgui/NewGenerationLimitDialog.java new file mode 100644 index 0000000..9281749 --- /dev/null +++ b/src/com/pkrandom/newgui/NewGenerationLimitDialog.java @@ -0,0 +1,168 @@ +package com.pkrandom.newgui; + +/*----------------------------------------------------------------------------*/ +/*-- NewGenerationLimitDialog.java - a GUI interface to allow users to --*/ +/*-- limit which Pokemon appear based on --*/ +/*-- their generation of origin. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.pokemon.GenRestrictions; + +import javax.swing.*; + +public class NewGenerationLimitDialog extends javax.swing.JDialog { + private JCheckBox gen1CheckBox; + private JCheckBox gen2CheckBox; + private JCheckBox gen3CheckBox; + private JCheckBox gen4CheckBox; + private JCheckBox gen5CheckBox; + private JCheckBox gen6CheckBox; + private JCheckBox gen7CheckBox; + private JButton okButton; + private JButton cancelButton; + private JPanel mainPanel; + private JLabel xyWarningLabel; + private JCheckBox allowEvolutionaryRelativesCheckBox; + + private boolean pressedOk; + private boolean isXY; + + public NewGenerationLimitDialog(JFrame parent, GenRestrictions current, int generation, boolean isXY) { + super(parent, true); + add(mainPanel); + this.isXY = isXY; + initComponents(); + initialState(generation); + if (current != null) { + current.limitToGen(generation); + restoreFrom(current); + } + enableAndDisableBoxes(); + pressedOk = false; + setLocationRelativeTo(parent); + setVisible(true); + } + + public boolean pressedOK() { + return pressedOk; + } + + public GenRestrictions getChoice() { + GenRestrictions gr = new GenRestrictions(); + gr.allow_gen1 = gen1CheckBox.isSelected(); + gr.allow_gen2 = gen2CheckBox.isSelected(); + gr.allow_gen3 = gen3CheckBox.isSelected(); + gr.allow_gen4 = gen4CheckBox.isSelected(); + gr.allow_gen5 = gen5CheckBox.isSelected(); + gr.allow_gen6 = gen6CheckBox.isSelected(); + gr.allow_gen7 = gen7CheckBox.isSelected(); + gr.allow_evolutionary_relatives = allowEvolutionaryRelativesCheckBox.isSelected(); + return gr; + } + + private void initialState(int generation) { + if (generation < 2) { + gen2CheckBox.setVisible(false); + } + if (generation < 3) { + gen3CheckBox.setVisible(false); + } + if (generation < 4) { + gen4CheckBox.setVisible(false); + } + if (generation < 5) { + gen5CheckBox.setVisible(false); + } + if (generation < 6) { + gen6CheckBox.setVisible(false); + } + if (generation < 7) { + gen7CheckBox.setVisible(false); + } + + allowEvolutionaryRelativesCheckBox.setEnabled(false); + allowEvolutionaryRelativesCheckBox.setSelected(false); + } + + private void restoreFrom(GenRestrictions restrict) { + gen1CheckBox.setSelected(restrict.allow_gen1); + gen2CheckBox.setSelected(restrict.allow_gen2); + gen3CheckBox.setSelected(restrict.allow_gen3); + gen4CheckBox.setSelected(restrict.allow_gen4); + gen5CheckBox.setSelected(restrict.allow_gen5); + gen6CheckBox.setSelected(restrict.allow_gen6); + gen7CheckBox.setSelected(restrict.allow_gen7); + allowEvolutionaryRelativesCheckBox.setSelected(restrict.allow_evolutionary_relatives); + } + + private void initComponents() { + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("com/pkrandom/newgui/Bundle"); + setTitle(bundle.getString("GenerationLimitDialog.title")); + gen1CheckBox.addActionListener(ev -> enableAndDisableBoxes()); + gen2CheckBox.addActionListener(ev -> enableAndDisableBoxes()); + gen3CheckBox.addActionListener(ev -> enableAndDisableBoxes()); + gen4CheckBox.addActionListener(ev -> enableAndDisableBoxes()); + gen5CheckBox.addActionListener(ev -> enableAndDisableBoxes()); + gen6CheckBox.addActionListener(ev -> enableAndDisableBoxes()); + gen7CheckBox.addActionListener(ev -> enableAndDisableBoxes()); + allowEvolutionaryRelativesCheckBox.addActionListener(ev -> enableAndDisableBoxes()); + okButton.addActionListener(evt -> okButtonActionPerformed()); + cancelButton.addActionListener(evt -> cancelButtonActionPerformed()); + xyWarningLabel.setVisible(isXY); + if (isXY) { + okButton.setEnabled(false); + } + pack(); + } + + private void enableAndDisableBoxes() { + // To prevent softlocks on the Successor Korrina fight, only turn + // on the OK button for XY if at least one of Gens 1-4 is selected. + if (isXY) { + if (gen1CheckBox.isSelected() || gen2CheckBox.isSelected() || gen3CheckBox.isSelected() || gen4CheckBox.isSelected()) { + okButton.setEnabled(true); + } else { + okButton.setEnabled(false); + } + } + + if (gen1CheckBox.isSelected() || gen2CheckBox.isSelected() || gen3CheckBox.isSelected() || + gen4CheckBox.isSelected() || gen5CheckBox.isSelected() || gen6CheckBox.isSelected() || + gen7CheckBox.isSelected()) { + allowEvolutionaryRelativesCheckBox.setEnabled(true); + } else { + allowEvolutionaryRelativesCheckBox.setEnabled(false); + allowEvolutionaryRelativesCheckBox.setSelected(false); + } + } + + private void okButtonActionPerformed() { + pressedOk = true; + setVisible(false); + } + + private void cancelButtonActionPerformed() { + pressedOk = false; + setVisible(false); + } +} diff --git a/src/com/pkrandom/newgui/NewRandomizerGUI.form b/src/com/pkrandom/newgui/NewRandomizerGUI.form new file mode 100644 index 0000000..c7c7a4e --- /dev/null +++ b/src/com/pkrandom/newgui/NewRandomizerGUI.form @@ -0,0 +1,3901 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/pkrandom/newgui/NewRandomizerGUI.java b/src/com/pkrandom/newgui/NewRandomizerGUI.java new file mode 100644 index 0000000..e3e762e --- /dev/null +++ b/src/com/pkrandom/newgui/NewRandomizerGUI.java @@ -0,0 +1,3700 @@ +package com.pkrandom.newgui; + +/*----------------------------------------------------------------------------*/ +/*-- NewRandomizerGUI.java - the main GUI for the randomizer, containing --*/ +/*-- the various options available and such --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.*; +import com.pkrandom.cli.CliRandomizer; +import com.pkrandom.constants.GlobalConstants; +import com.pkrandom.exceptions.CannotWriteToLocationException; +import com.pkrandom.exceptions.EncryptedROMException; +import com.pkrandom.exceptions.InvalidSupplementFilesException; +import com.pkrandom.exceptions.RandomizationException; +import com.pkrandom.pokemon.ExpCurve; +import com.pkrandom.pokemon.GenRestrictions; +import com.pkrandom.pokemon.Pokemon; +import com.pkrandom.romhandlers.*; + +import javax.swing.*; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public class NewRandomizerGUI { + private JTabbedPane tabbedPane1; + private JCheckBox raceModeCheckBox; + private JButton openROMButton; + private JButton randomizeSaveButton; + private JButton premadeSeedButton; + private JButton settingsButton; + private JButton loadSettingsButton; + private JButton saveSettingsButton; + private JPanel mainPanel; + private JRadioButton pbsUnchangedRadioButton; + private JRadioButton pbsShuffleRadioButton; + private JRadioButton pbsRandomRadioButton; + private JRadioButton pbsLegendariesSlowRadioButton; + private JRadioButton pbsStrongLegendariesSlowRadioButton; + private JRadioButton pbsAllMediumFastRadioButton; + private JCheckBox pbsStandardizeEXPCurvesCheckBox; + private JCheckBox pbsFollowEvolutionsCheckBox; + private JCheckBox pbsUpdateBaseStatsCheckBox; + private JCheckBox ptIsDualTypeCheckBox; + private JRadioButton ptUnchangedRadioButton; + private JRadioButton ptRandomFollowEvolutionsRadioButton; + private JRadioButton ptRandomCompletelyRadioButton; + private JRadioButton paUnchangedRadioButton; + private JRadioButton paRandomRadioButton; + private JCheckBox paAllowWonderGuardCheckBox; + private JCheckBox paFollowEvolutionsCheckBox; + private JCheckBox paTrappingAbilitiesCheckBox; + private JCheckBox paNegativeAbilitiesCheckBox; + private JCheckBox paBadAbilitiesCheckBox; + private JRadioButton peUnchangedRadioButton; + private JRadioButton peRandomRadioButton; + private JCheckBox peSimilarStrengthCheckBox; + private JCheckBox peSameTypingCheckBox; + private JCheckBox peLimitEvolutionsToThreeCheckBox; + private JCheckBox peForceChangeCheckBox; + private JCheckBox peChangeImpossibleEvosCheckBox; + private JCheckBox peMakeEvolutionsEasierCheckBox; + private JRadioButton spUnchangedRadioButton; + private JRadioButton spCustomRadioButton; + private JRadioButton spRandomCompletelyRadioButton; + private JRadioButton spRandomTwoEvosRadioButton; + private JComboBox spComboBox1; + private JComboBox spComboBox2; + private JComboBox spComboBox3; + private JCheckBox spRandomizeStarterHeldItemsCheckBox; + private JCheckBox spBanBadItemsCheckBox; + private JRadioButton stpUnchangedRadioButton; + private JRadioButton stpSwapLegendariesSwapStandardsRadioButton; + private JRadioButton stpRandomCompletelyRadioButton; + private JRadioButton stpRandomSimilarStrengthRadioButton; + private JCheckBox stpLimitMainGameLegendariesCheckBox; + private JCheckBox stpRandomize600BSTCheckBox; + private JRadioButton igtUnchangedRadioButton; + private JRadioButton igtRandomizeGivenPokemonOnlyRadioButton; + private JRadioButton igtRandomizeBothRequestedGivenRadioButton; + private JCheckBox igtRandomizeNicknamesCheckBox; + private JCheckBox igtRandomizeOTsCheckBox; + private JCheckBox igtRandomizeIVsCheckBox; + private JCheckBox igtRandomizeItemsCheckBox; + private JCheckBox mdRandomizeMovePowerCheckBox; + private JCheckBox mdRandomizeMoveAccuracyCheckBox; + private JCheckBox mdRandomizeMovePPCheckBox; + private JCheckBox mdRandomizeMoveTypesCheckBox; + private JCheckBox mdRandomizeMoveCategoryCheckBox; + private JCheckBox mdUpdateMovesCheckBox; + private JCheckBox mdLegacyCheckBox; + private JRadioButton pmsUnchangedRadioButton; + private JRadioButton pmsRandomPreferringSameTypeRadioButton; + private JRadioButton pmsRandomCompletelyRadioButton; + private JRadioButton pmsMetronomeOnlyModeRadioButton; + private JCheckBox pmsGuaranteedLevel1MovesCheckBox; + private JCheckBox pmsReorderDamagingMovesCheckBox; + private JCheckBox pmsNoGameBreakingMovesCheckBox; + private JCheckBox pmsForceGoodDamagingCheckBox; + private JSlider pmsGuaranteedLevel1MovesSlider; + private JSlider pmsForceGoodDamagingSlider; + private JCheckBox tpRivalCarriesStarterCheckBox; + private JCheckBox tpSimilarStrengthCheckBox; + private JCheckBox tpWeightTypesCheckBox; + private JCheckBox tpDontUseLegendariesCheckBox; + private JCheckBox tpNoEarlyWonderGuardCheckBox; + private JCheckBox tpRandomizeTrainerNamesCheckBox; + private JCheckBox tpRandomizeTrainerClassNamesCheckBox; + private JCheckBox tpForceFullyEvolvedAtCheckBox; + private JSlider tpForceFullyEvolvedAtSlider; + private JSlider tpPercentageLevelModifierSlider; + private JCheckBox tpEliteFourUniquePokemonCheckBox; + private JSpinner tpEliteFourUniquePokemonSpinner; + private JCheckBox tpPercentageLevelModifierCheckBox; + private JRadioButton wpUnchangedRadioButton; + private JRadioButton wpRandomRadioButton; + private JRadioButton wpArea1To1RadioButton; + private JRadioButton wpGlobal1To1RadioButton; + private JRadioButton wpARNoneRadioButton; + private JRadioButton wpARSimilarStrengthRadioButton; + private JRadioButton wpARCatchEmAllModeRadioButton; + private JRadioButton wpARTypeThemeAreasRadioButton; + private JCheckBox wpUseTimeBasedEncountersCheckBox; + private JCheckBox wpDontUseLegendariesCheckBox; + private JCheckBox wpSetMinimumCatchRateCheckBox; + private JCheckBox wpRandomizeHeldItemsCheckBox; + private JCheckBox wpBanBadItemsCheckBox; + private JCheckBox wpBalanceShakingGrassPokemonCheckBox; + private JCheckBox wpPercentageLevelModifierCheckBox; + private JSlider wpPercentageLevelModifierSlider; + private JSlider wpSetMinimumCatchRateSlider; + private JRadioButton tmUnchangedRadioButton; + private JRadioButton tmRandomRadioButton; + private JCheckBox tmFullHMCompatibilityCheckBox; + private JCheckBox tmLevelupMoveSanityCheckBox; + private JCheckBox tmKeepFieldMoveTMsCheckBox; + private JCheckBox tmForceGoodDamagingCheckBox; + private JSlider tmForceGoodDamagingSlider; + private JRadioButton thcUnchangedRadioButton; + private JRadioButton thcRandomPreferSameTypeRadioButton; + private JRadioButton thcRandomCompletelyRadioButton; + private JRadioButton thcFullCompatibilityRadioButton; + private JRadioButton mtUnchangedRadioButton; + private JRadioButton mtRandomRadioButton; + private JCheckBox mtLevelupMoveSanityCheckBox; + private JCheckBox mtKeepFieldMoveTutorsCheckBox; + private JCheckBox mtForceGoodDamagingCheckBox; + private JSlider mtForceGoodDamagingSlider; + private JRadioButton mtcUnchangedRadioButton; + private JRadioButton mtcRandomPreferSameTypeRadioButton; + private JRadioButton mtcRandomCompletelyRadioButton; + private JRadioButton mtcFullCompatibilityRadioButton; + private JRadioButton fiUnchangedRadioButton; + private JRadioButton fiShuffleRadioButton; + private JRadioButton fiRandomRadioButton; + private JRadioButton fiRandomEvenDistributionRadioButton; + private JCheckBox fiBanBadItemsCheckBox; + private JRadioButton shUnchangedRadioButton; + private JRadioButton shShuffleRadioButton; + private JRadioButton shRandomRadioButton; + private JCheckBox shBanOverpoweredShopItemsCheckBox; + private JCheckBox shBanBadItemsCheckBox; + private JCheckBox shBanRegularShopItemsCheckBox; + private JCheckBox shBalanceShopItemPricesCheckBox; + private JCheckBox shGuaranteeEvolutionItemsCheckBox; + private JCheckBox shGuaranteeXItemsCheckBox; + private JCheckBox miscBWExpPatchCheckBox; + private JCheckBox miscNerfXAccuracyCheckBox; + private JCheckBox miscFixCritRateCheckBox; + private JCheckBox miscFastestTextCheckBox; + private JCheckBox miscRunningShoesIndoorsCheckBox; + private JCheckBox miscRandomizePCPotionCheckBox; + private JCheckBox miscAllowPikachuEvolutionCheckBox; + private JCheckBox miscGiveNationalDexAtCheckBox; + private JCheckBox miscUpdateTypeEffectivenessCheckBox; + private JCheckBox miscLowerCasePokemonNamesCheckBox; + private JCheckBox miscRandomizeCatchingTutorialCheckBox; + private JCheckBox miscBanLuckyEggCheckBox; + private JCheckBox miscNoFreeLuckyEggCheckBox; + private JCheckBox miscBanBigMoneyManiacCheckBox; + private JPanel pokemonAbilitiesPanel; + private JPanel moveTutorPanel; + private JPanel mtMovesPanel; + private JPanel mtCompatPanel; + private JLabel mtNoExistLabel; + private JPanel shopItemsPanel; + private JLabel mtNoneAvailableLabel; + private JPanel miscTweaksPanel; + private JLabel gameMascotLabel; + private JPanel baseTweaksPanel; + private JLabel romNameLabel; + private JLabel romCodeLabel; + private JLabel romSupportLabel; + private JCheckBox tmNoGameBreakingMovesCheckBox; + private JCheckBox mtNoGameBreakingMovesCheckBox; + private JCheckBox limitPokemonCheckBox; + private JButton limitPokemonButton; + private JCheckBox tpAllowAlternateFormesCheckBox; + private JLabel versionLabel; + private JCheckBox pbsFollowMegaEvosCheckBox; + private JCheckBox paFollowMegaEvosCheckBox; + private JCheckBox ptFollowMegaEvosCheckBox; + private JCheckBox spAllowAltFormesCheckBox; + private JCheckBox stpAllowAltFormesCheckBox; + private JCheckBox stpSwapMegaEvosCheckBox; + private JCheckBox tpSwapMegaEvosCheckBox; + private JCheckBox wpAllowAltFormesCheckBox; + private JCheckBox tpDoubleBattleModeCheckBox; + private JCheckBox tpBossTrainersCheckBox; + private JCheckBox tpImportantTrainersCheckBox; + private JCheckBox tpRegularTrainersCheckBox; + private JSpinner tpBossTrainersSpinner; + private JSpinner tpImportantTrainersSpinner; + private JSpinner tpRegularTrainersSpinner; + private JLabel tpAdditionalPokemonForLabel; + private JCheckBox peAllowAltFormesCheckBox; + private JCheckBox miscSOSBattlesCheckBox; + private JCheckBox tpRandomShinyTrainerPokemonCheckBox; + private JRadioButton totpUnchangedRadioButton; + private JRadioButton totpRandomRadioButton; + private JRadioButton totpRandomSimilarStrengthRadioButton; + private JRadioButton totpAllyUnchangedRadioButton; + private JRadioButton totpAllyRandomRadioButton; + private JRadioButton totpAllyRandomSimilarStrengthRadioButton; + private JPanel totpAllyPanel; + private JPanel totpAuraPanel; + private JRadioButton totpAuraUnchangedRadioButton; + private JRadioButton totpAuraRandomRadioButton; + private JRadioButton totpAuraRandomSameStrengthRadioButton; + private JCheckBox totpPercentageLevelModifierCheckBox; + private JSlider totpPercentageLevelModifierSlider; + private JCheckBox totpRandomizeHeldItemsCheckBox; + private JCheckBox totpAllowAltFormesCheckBox; + private JPanel totpPanel; + private JCheckBox pmsEvolutionMovesCheckBox; + private JComboBox pbsUpdateComboBox; + private JComboBox mdUpdateComboBox; + private JCheckBox paWeighDuplicatesTogetherCheckBox; + private JCheckBox miscBalanceStaticLevelsCheckBox; + private JCheckBox miscRetainAltFormesCheckBox; + private JComboBox pbsEXPCurveComboBox; + private JCheckBox miscRunWithoutRunningShoesCheckBox; + private JCheckBox peRemoveTimeBasedEvolutionsCheckBox; + private JCheckBox tmFollowEvolutionsCheckBox; + private JCheckBox mtFollowEvolutionsCheckBox; + private JCheckBox stpPercentageLevelModifierCheckBox; + private JSlider stpPercentageLevelModifierSlider; + private JCheckBox stpFixMusicCheckBox; + private JCheckBox miscFasterHPAndEXPBarsCheckBox; + private JCheckBox tpBossTrainersItemsCheckBox; + private JCheckBox tpImportantTrainersItemsCheckBox; + private JCheckBox tpRegularTrainersItemsCheckBox; + private JLabel tpHeldItemsLabel; + private JCheckBox tpConsumableItemsOnlyCheckBox; + private JCheckBox tpSensibleItemsCheckBox; + private JCheckBox tpHighestLevelGetsItemCheckBox; + private JPanel pickupItemsPanel; + private JRadioButton puUnchangedRadioButton; + private JRadioButton puRandomRadioButton; + private JCheckBox puBanBadItemsCheckBox; + private JCheckBox miscForceChallengeModeCheckBox; + private JCheckBox pbsAssignEvoStatsRandomlyCheckBox; + private JCheckBox noIrregularAltFormesCheckBox; + private JRadioButton peRandomEveryLevelRadioButton; + private JCheckBox miscFastDistortionWorldCheckBox; + private JComboBox tpComboBox; + private JCheckBox tpBetterMovesetsCheckBox; + private JCheckBox paEnsureTwoAbilitiesCheckbox; + private JCheckBox miscUpdateRotomFormeTypingCheckBox; + private JCheckBox miscDisableLowHPMusicCheckBox; + + private static JFrame frame; + + private static String launcherInput = ""; + public static boolean usedLauncher = false; + + private GenRestrictions currentRestrictions; + private OperationDialog opDialog; + + private ResourceBundle bundle; + protected RomHandler.Factory[] checkHandlers; + private RomHandler romHandler; + + private boolean presetMode = false; + private boolean initialPopup = true; + private boolean showInvalidRomPopup = true; + + private List tweakCheckBoxes; + private JPanel liveTweaksPanel = new JPanel(); + + private JFileChooser romOpenChooser = new JFileChooser(); + private JFileChooser romSaveChooser = new JFileChooser(); + private JFileChooser qsOpenChooser = new JFileChooser(); + private JFileChooser qsSaveChooser = new JFileChooser(); + private JFileChooser qsUpdateChooser = new JFileChooser(); + private JFileChooser gameUpdateChooser = new JFileChooser(); + + private JPopupMenu settingsMenu; + private JMenuItem customNamesEditorMenuItem; + private JMenuItem loadGetSettingsMenuItem; + private JMenuItem keepOrUnloadGameAfterRandomizingMenuItem; + + private ImageIcon emptyIcon = new ImageIcon(getClass().getResource("/com/pkrandom/newgui/emptyIcon.png")); + private boolean haveCheckedCustomNames, unloadGameOnSuccess; + private Map gameUpdates = new TreeMap<>(); + + private List trainerSettings = new ArrayList<>(); + private List trainerSettingToolTips = new ArrayList<>(); + private final int TRAINER_UNCHANGED = 0, TRAINER_RANDOM = 1, TRAINER_RANDOM_EVEN = 2, TRAINER_RANDOM_EVEN_MAIN = 3, + TRAINER_TYPE_THEMED = 4, TRAINER_TYPE_THEMED_ELITE4_GYMS = 5; + + public NewRandomizerGUI() { + ToolTipManager.sharedInstance().setInitialDelay(400); + ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE); + bundle = ResourceBundle.getBundle("com/pkrandom/newgui/Bundle"); + testForRequiredConfigs(); + checkHandlers = new RomHandler.Factory[] { new Gen1RomHandler.Factory(), new Gen2RomHandler.Factory(), + new Gen3RomHandler.Factory(), new Gen4RomHandler.Factory(), new Gen5RomHandler.Factory(), + new Gen6RomHandler.Factory(), new Gen7RomHandler.Factory() }; + + haveCheckedCustomNames = false; + initExplicit(); + initTweaksPanel(); + initFileChooserDirectories(); + + if (!haveCheckedCustomNames) { + checkCustomNames(); + } + + new Thread(() -> { + String latestVersionString = "???"; + + try { + + URL url = new URL(SysConstants.API_URL_ZX); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept", "application/json"); + conn.setConnectTimeout(2000); + conn.setReadTimeout(2000); + + BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream()))); + + String output; + while ((output = br.readLine()) != null) { + String[] a = output.split("tag_name\":\""); + if (a.length > 1) { + latestVersionString = a[1].split("\",")[0]; + } + } + + conn.disconnect(); + + } catch (Exception e) { + e.printStackTrace(); + } + }).run(); + + frame.setTitle(String.format(bundle.getString("GUI.windowTitle"),Version.VERSION_STRING)); + + openROMButton.addActionListener(e -> loadROM()); + pbsUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + pbsShuffleRadioButton.addActionListener(e -> enableOrDisableSubControls()); + pbsRandomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + pbsFollowMegaEvosCheckBox.addActionListener(e -> enableOrDisableSubControls()); + pbsFollowEvolutionsCheckBox.addActionListener(e -> enableOrDisableSubControls()); + pbsStandardizeEXPCurvesCheckBox.addActionListener(e -> enableOrDisableSubControls()); + paUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + paRandomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + peUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + peRandomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + peRandomEveryLevelRadioButton.addActionListener(e -> enableOrDisableSubControls()); + peAllowAltFormesCheckBox.addActionListener(e -> enableOrDisableSubControls()); + spUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + spCustomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + spRandomCompletelyRadioButton.addActionListener(e -> enableOrDisableSubControls()); + spRandomTwoEvosRadioButton.addActionListener(e -> enableOrDisableSubControls()); + stpUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + stpSwapLegendariesSwapStandardsRadioButton.addActionListener(e -> enableOrDisableSubControls()); + stpRandomCompletelyRadioButton.addActionListener(e -> enableOrDisableSubControls()); + stpRandomSimilarStrengthRadioButton.addActionListener(e -> enableOrDisableSubControls()); + stpPercentageLevelModifierCheckBox.addActionListener(e -> enableOrDisableSubControls()); + igtUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + igtRandomizeGivenPokemonOnlyRadioButton.addActionListener(e -> enableOrDisableSubControls()); + igtRandomizeBothRequestedGivenRadioButton.addActionListener(e -> enableOrDisableSubControls()); + pmsUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + pmsRandomPreferringSameTypeRadioButton.addActionListener(e -> enableOrDisableSubControls()); + pmsRandomCompletelyRadioButton.addActionListener(e -> enableOrDisableSubControls()); + pmsMetronomeOnlyModeRadioButton.addActionListener(e -> enableOrDisableSubControls()); + pmsGuaranteedLevel1MovesCheckBox.addActionListener(e -> enableOrDisableSubControls()); + pmsForceGoodDamagingCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tpForceFullyEvolvedAtCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tpPercentageLevelModifierCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tpEliteFourUniquePokemonCheckBox.addActionListener(e -> enableOrDisableSubControls()); + wpUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + wpRandomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + wpArea1To1RadioButton.addActionListener(e -> enableOrDisableSubControls()); + wpGlobal1To1RadioButton.addActionListener(e -> enableOrDisableSubControls()); + wpSetMinimumCatchRateCheckBox.addActionListener(e -> enableOrDisableSubControls()); + wpRandomizeHeldItemsCheckBox.addActionListener(e -> enableOrDisableSubControls()); + wpPercentageLevelModifierCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tmUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + tmRandomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + tmForceGoodDamagingCheckBox.addActionListener(e -> enableOrDisableSubControls()); + thcUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + thcRandomPreferSameTypeRadioButton.addActionListener(e -> enableOrDisableSubControls()); + thcRandomCompletelyRadioButton.addActionListener(e -> enableOrDisableSubControls()); + thcFullCompatibilityRadioButton.addActionListener(e -> enableOrDisableSubControls()); + mtUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + mtRandomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + mtForceGoodDamagingCheckBox.addActionListener(e -> enableOrDisableSubControls()); + mtcUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + mtcRandomPreferSameTypeRadioButton.addActionListener(e -> enableOrDisableSubControls()); + mtcRandomCompletelyRadioButton.addActionListener(e -> enableOrDisableSubControls()); + mtcFullCompatibilityRadioButton.addActionListener(e -> enableOrDisableSubControls()); + fiUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + fiShuffleRadioButton.addActionListener(e -> enableOrDisableSubControls()); + fiRandomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + fiRandomEvenDistributionRadioButton.addActionListener(e -> enableOrDisableSubControls()); + shUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + shShuffleRadioButton.addActionListener(e -> enableOrDisableSubControls()); + shRandomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + puUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + puRandomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + randomizeSaveButton.addActionListener(e -> saveROM()); + premadeSeedButton.addActionListener(e -> presetLoader()); + loadSettingsButton.addActionListener(e -> loadQS()); + saveSettingsButton.addActionListener(e -> saveQS()); + settingsButton.addActionListener(e -> settingsMenu.show(settingsButton,0,settingsButton.getHeight())); + customNamesEditorMenuItem.addActionListener(e -> new CustomNamesEditorDialog(frame)); + loadGetSettingsMenuItem.addActionListener(e -> loadGetSettingsMenuItemActionPerformed()); + keepOrUnloadGameAfterRandomizingMenuItem.addActionListener(e -> keepOrUnloadGameAfterRandomizingMenuItemActionPerformed()); + limitPokemonButton.addActionListener(e -> { + NewGenerationLimitDialog gld = new NewGenerationLimitDialog(frame, currentRestrictions, + romHandler.generationOfPokemon(), romHandler.forceSwapStaticMegaEvos()); + if (gld.pressedOK()) { + currentRestrictions = gld.getChoice(); + if (currentRestrictions != null && !currentRestrictions.allowTrainerSwapMegaEvolvables( + romHandler.forceSwapStaticMegaEvos(), isTrainerSetting(TRAINER_TYPE_THEMED))) { + tpSwapMegaEvosCheckBox.setEnabled(false); + tpSwapMegaEvosCheckBox.setSelected(false); + } + } + }); + limitPokemonCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tpAllowAlternateFormesCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tpBossTrainersCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tpImportantTrainersCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tpRegularTrainersCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tpBossTrainersItemsCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tpImportantTrainersItemsCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tpRegularTrainersItemsCheckBox.addActionListener(e -> enableOrDisableSubControls()); + totpUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + totpRandomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + totpRandomSimilarStrengthRadioButton.addActionListener(e -> enableOrDisableSubControls()); + totpAllyUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + totpAllyRandomRadioButton.addActionListener(e -> enableOrDisableSubControls()); + totpAllyRandomSimilarStrengthRadioButton.addActionListener(e -> enableOrDisableSubControls()); + totpPercentageLevelModifierCheckBox.addActionListener(e -> enableOrDisableSubControls()); + pbsUpdateBaseStatsCheckBox.addActionListener(e -> enableOrDisableSubControls()); + mdUpdateMovesCheckBox.addActionListener(e -> enableOrDisableSubControls()); + frame.addComponentListener(new ComponentListener() { + @Override + public void componentResized(ComponentEvent e) { + + } + + @Override + public void componentMoved(ComponentEvent e) { + + } + + @Override + public void componentShown(ComponentEvent e) { + showInitialPopup(); + } + + @Override + public void componentHidden(ComponentEvent e) { + + } + }); + ptUnchangedRadioButton.addActionListener(e -> enableOrDisableSubControls()); + ptRandomFollowEvolutionsRadioButton.addActionListener(e -> enableOrDisableSubControls()); + ptRandomCompletelyRadioButton.addActionListener(e -> enableOrDisableSubControls()); + spRandomizeStarterHeldItemsCheckBox.addActionListener(e -> enableOrDisableSubControls()); + tmLevelupMoveSanityCheckBox.addActionListener(e -> enableOrDisableSubControls()); + mtLevelupMoveSanityCheckBox.addActionListener(e -> enableOrDisableSubControls()); + noIrregularAltFormesCheckBox.addActionListener(e -> enableOrDisableSubControls()); + ptIsDualTypeCheckBox.addActionListener(e->enableOrDisableSubControls()); + tpComboBox.addItemListener(e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + enableOrDisableSubControls(); + } + }); + } + + private void showInitialPopup() { + if (!usedLauncher) { + String message = bundle.getString("GUI.pleaseUseTheLauncher"); + Object[] messages = {message}; + JOptionPane.showMessageDialog(frame, messages); + } + } + + private void showInvalidRomPopup() { + if (showInvalidRomPopup) { + String message = String.format(bundle.getString("GUI.invalidRomMessage")); + JLabel label = new JLabel("Randomizing ROM hacks or bad ROM dumps is not supported and may cause issues."); + JCheckBox checkbox = new JCheckBox("Don't show this again"); + Object[] messages = {message, label, checkbox}; + Object[] options = {"OK"}; + JOptionPane.showOptionDialog(frame, + messages, + "Invalid ROM detected", + JOptionPane.OK_OPTION, + JOptionPane.WARNING_MESSAGE, + null, + options, + null); + showInvalidRomPopup = !checkbox.isSelected(); + } + } + + private void initFileChooserDirectories() { + romOpenChooser.setCurrentDirectory(new File(SysConstants.ROOT_PATH)); + romSaveChooser.setCurrentDirectory(new File(SysConstants.ROOT_PATH)); + if (new File(SysConstants.ROOT_PATH + "settings/").exists()) { + qsOpenChooser.setCurrentDirectory(new File(SysConstants.ROOT_PATH + "settings/")); + qsSaveChooser.setCurrentDirectory(new File(SysConstants.ROOT_PATH + "settings/")); + qsUpdateChooser.setCurrentDirectory(new File(SysConstants.ROOT_PATH + "settings/")); + } else { + qsOpenChooser.setCurrentDirectory(new File(SysConstants.ROOT_PATH)); + qsSaveChooser.setCurrentDirectory(new File(SysConstants.ROOT_PATH)); + qsUpdateChooser.setCurrentDirectory(new File(SysConstants.ROOT_PATH)); + } + } + + private void initExplicit() { + + versionLabel.setText(String.format(bundle.getString("GUI.versionLabel.text"), Version.VERSION_STRING)); + mtNoExistLabel.setVisible(false); + mtNoneAvailableLabel.setVisible(false); + baseTweaksPanel.add(liveTweaksPanel); + liveTweaksPanel.setVisible(false); + + romOpenChooser.setFileFilter(new ROMFilter()); + + romSaveChooser.setDialogType(javax.swing.JFileChooser.SAVE_DIALOG); + romSaveChooser.setFileFilter(new ROMFilter()); + + qsOpenChooser.setFileFilter(new QSFileFilter()); + + qsSaveChooser.setDialogType(javax.swing.JFileChooser.SAVE_DIALOG); + qsSaveChooser.setFileFilter(new QSFileFilter()); + + qsUpdateChooser.setFileFilter(new QSFileFilter()); + + settingsMenu = new JPopupMenu(); + + SpinnerModel bossTrainerModel = new SpinnerNumberModel( + 1, + 1, + 5, + 1 + ); + SpinnerModel importantTrainerModel = new SpinnerNumberModel( + 1, + 1, + 5, + 1 + ); + SpinnerModel regularTrainerModel = new SpinnerNumberModel( + 1, + 1, + 5, + 1 + ); + + SpinnerModel eliteFourUniquePokemonModel = new SpinnerNumberModel( + 1, + 1, + 2, + 1 + ); + + List keys = new ArrayList<>(bundle.keySet()); + Collections.sort(keys); + for (String k: keys) { + if (k.matches("^GUI\\.tpMain.*\\.text$")) { + trainerSettings.add(bundle.getString(k)); + trainerSettingToolTips.add(k.replace("text","toolTipText")); + } + } + + tpBossTrainersSpinner.setModel(bossTrainerModel); + tpImportantTrainersSpinner.setModel(importantTrainerModel); + tpRegularTrainersSpinner.setModel(regularTrainerModel); + tpEliteFourUniquePokemonSpinner.setModel(eliteFourUniquePokemonModel); + + customNamesEditorMenuItem = new JMenuItem(); + customNamesEditorMenuItem.setText(bundle.getString("GUI.customNamesEditorMenuItem.text")); + settingsMenu.add(customNamesEditorMenuItem); + + loadGetSettingsMenuItem = new JMenuItem(); + loadGetSettingsMenuItem.setText(bundle.getString("GUI.loadGetSettingsMenuItem.text")); + settingsMenu.add(loadGetSettingsMenuItem); + + keepOrUnloadGameAfterRandomizingMenuItem = new JMenuItem(); + if (this.unloadGameOnSuccess) { + keepOrUnloadGameAfterRandomizingMenuItem.setText(bundle.getString("GUI.keepGameLoadedAfterRandomizingMenuItem.text")); + } else { + keepOrUnloadGameAfterRandomizingMenuItem.setText(bundle.getString("GUI.unloadGameAfterRandomizingMenuItem.text")); + } + settingsMenu.add(keepOrUnloadGameAfterRandomizingMenuItem); + } + + private void loadROM() { + romOpenChooser.setSelectedFile(null); + int returnVal = romOpenChooser.showOpenDialog(mainPanel); + if (returnVal == JFileChooser.APPROVE_OPTION) { + final File fh = romOpenChooser.getSelectedFile(); + try { + Utils.validateRomFile(fh); + } catch (Utils.InvalidROMException e) { + switch (e.getType()) { + case LENGTH: + JOptionPane.showMessageDialog(mainPanel, + String.format(bundle.getString("GUI.tooShortToBeARom"), fh.getName())); + return; + case ZIP_FILE: + JOptionPane.showMessageDialog(mainPanel, + String.format(bundle.getString("GUI.openedZIPfile"), fh.getName())); + return; + case RAR_FILE: + JOptionPane.showMessageDialog(mainPanel, + String.format(bundle.getString("GUI.openedRARfile"), fh.getName())); + return; + case IPS_FILE: + JOptionPane.showMessageDialog(mainPanel, + String.format(bundle.getString("GUI.openedIPSfile"), fh.getName())); + return; + case UNREADABLE: + JOptionPane.showMessageDialog(mainPanel, + String.format(bundle.getString("GUI.unreadableRom"), fh.getName())); + return; + } + } + + for (RomHandler.Factory rhf : checkHandlers) { + if (rhf.isLoadable(fh.getAbsolutePath())) { + this.romHandler = rhf.create(RandomSource.instance()); + if (!usedLauncher && this.romHandler instanceof Abstract3DSRomHandler) { + String message = bundle.getString("GUI.pleaseUseTheLauncher"); + Object[] messages = {message}; + JOptionPane.showMessageDialog(frame, messages); + this.romHandler = null; + return; + } + opDialog = new OperationDialog(bundle.getString("GUI.loadingText"), frame, true); + Thread t = new Thread(() -> { + boolean romLoaded = false; + SwingUtilities.invokeLater(() -> opDialog.setVisible(true)); + try { + this.romHandler.loadRom(fh.getAbsolutePath()); + if (gameUpdates.containsKey(this.romHandler.getROMCode())) { + this.romHandler.loadGameUpdate(gameUpdates.get(this.romHandler.getROMCode())); + } + romLoaded = true; + } catch (EncryptedROMException ex) { + JOptionPane.showMessageDialog(mainPanel, + String.format(bundle.getString("GUI.encryptedRom"), fh.getAbsolutePath())); + } catch (Exception ex) { + attemptToLogException(ex, "GUI.loadFailed", "GUI.loadFailedNoLog", null, null); + } + final boolean loadSuccess = romLoaded; + SwingUtilities.invokeLater(() -> { + this.opDialog.setVisible(false); + this.initialState(); + if (loadSuccess) { + this.romLoaded(); + } + }); + }); + t.start(); + + return; + } + } + JOptionPane.showMessageDialog(mainPanel, + String.format(bundle.getString("GUI.unsupportedRom"), fh.getName())); + } + } + + private void saveROM() { + if (romHandler == null) { + return; // none loaded + } + if (raceModeCheckBox.isSelected() && isTrainerSetting(TRAINER_UNCHANGED) && wpUnchangedRadioButton.isSelected()) { + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.raceModeRequirements")); + return; + } + if (limitPokemonCheckBox.isSelected() + && (this.currentRestrictions == null || this.currentRestrictions.nothingSelected())) { + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.pokeLimitNotChosen")); + return; + } + SaveType outputType = askForSaveType(); + romSaveChooser.setSelectedFile(null); + boolean allowed = false; + File fh = null; + if (outputType == SaveType.FILE) { + romSaveChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + int returnVal = romSaveChooser.showSaveDialog(frame); + if (returnVal == JFileChooser.APPROVE_OPTION) { + fh = romSaveChooser.getSelectedFile(); + // Fix or add extension + List extensions = new ArrayList<>(Arrays.asList("sgb", "gbc", "gba", "nds", "cxi")); + extensions.remove(this.romHandler.getDefaultExtension()); + fh = FileFunctions.fixFilename(fh, this.romHandler.getDefaultExtension(), extensions); + allowed = true; + if (this.romHandler instanceof AbstractDSRomHandler || this.romHandler instanceof Abstract3DSRomHandler) { + String currentFN = this.romHandler.loadedFilename(); + if (currentFN.equals(fh.getAbsolutePath())) { + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.cantOverwriteDS")); + allowed = false; + } + } + } + } else if (outputType == SaveType.DIRECTORY) { + romSaveChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int returnVal = romSaveChooser.showSaveDialog(frame); + if (returnVal == JFileChooser.APPROVE_OPTION) { + fh = romSaveChooser.getSelectedFile(); + allowed = true; + } + } + + if (allowed && fh != null) { + // Get a seed + long seed = RandomSource.pickSeed(); + // Apply it + RandomSource.seed(seed); + presetMode = false; + + try { + + CustomNamesSet cns = FileFunctions.getCustomNames(); + performRandomization(fh.getAbsolutePath(), seed, cns, outputType == SaveType.DIRECTORY); + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.cantLoadCustomNames")); + } + } + } + + private void loadQS() { + if (this.romHandler == null) { + return; + } + qsOpenChooser.setSelectedFile(null); + int returnVal = qsOpenChooser.showOpenDialog(frame); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File fh = qsOpenChooser.getSelectedFile(); + try { + FileInputStream fis = new FileInputStream(fh); + Settings settings = Settings.read(fis); + fis.close(); + + SwingUtilities.invokeLater(() -> { + // load settings + initialState(); + romLoaded(); + Settings.TweakForROMFeedback feedback = settings.tweakForRom(this.romHandler); + if (feedback.isChangedStarter() && settings.getStartersMod() == Settings.StartersMod.CUSTOM) { + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.starterUnavailable")); + } + this.restoreStateFromSettings(settings); + + if (settings.isUpdatedFromOldVersion()) { + // show a warning dialog, but load it + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.settingsFileOlder")); + } + + JOptionPane.showMessageDialog(frame, + String.format(bundle.getString("GUI.settingsLoaded"), fh.getName())); + }); + } catch (UnsupportedOperationException ex) { + ex.printStackTrace(); + JOptionPane.showMessageDialog(frame, ex.getMessage()); + } catch (IllegalArgumentException ex) { + ex.printStackTrace(); + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.invalidSettingsFile")); + } catch (IOException ex) { + ex.printStackTrace(); + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.settingsLoadFailed")); + } + } + } + + private void saveQS() { + if (this.romHandler == null) { + return; + } + qsSaveChooser.setSelectedFile(null); + int returnVal = qsSaveChooser.showSaveDialog(frame); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File fh = qsSaveChooser.getSelectedFile(); + // Fix or add extension + fh = FileFunctions.fixFilename(fh, "rnqs"); + // Save now? + try { + FileOutputStream fos = new FileOutputStream(fh); + getCurrentSettings().write(fos); + fos.close(); + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.settingsSaveFailed")); + } + } + } + + private void performRandomization(final String filename, final long seed, CustomNamesSet customNames, boolean saveAsDirectory) { + final Settings settings = createSettingsFromState(customNames); + final boolean raceMode = settings.isRaceMode(); + // Setup verbose log + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream log; + try { + log = new PrintStream(baos, false, "UTF-8"); + } catch (UnsupportedEncodingException e) { + log = new PrintStream(baos); + } + + final PrintStream verboseLog = log; + + try { + final AtomicInteger finishedCV = new AtomicInteger(0); + opDialog = new OperationDialog(bundle.getString("GUI.savingText"), frame, true); + Thread t = new Thread(() -> { + SwingUtilities.invokeLater(() -> opDialog.setVisible(true)); + boolean succeededSave = false; + try { + romHandler.setLog(verboseLog); + finishedCV.set(new Randomizer(settings, romHandler, bundle, saveAsDirectory).randomize(filename, + verboseLog, seed)); + succeededSave = true; + } catch (RandomizationException ex) { + attemptToLogException(ex, "GUI.saveFailedMessage", + "GUI.saveFailedMessageNoLog", true, settings.toString(), Long.toString(seed)); + if (verboseLog != null) { + verboseLog.close(); + } + } catch (CannotWriteToLocationException ex) { + JOptionPane.showMessageDialog(mainPanel, String.format(bundle.getString("GUI.cannotWriteToLocation"), filename)); + if (verboseLog != null) { + verboseLog.close(); + } + } catch (Exception ex) { + attemptToLogException(ex, "GUI.saveFailedIO", "GUI.saveFailedIONoLog", settings.toString(), Long.toString(seed)); + if (verboseLog != null) { + verboseLog.close(); + } + } + if (succeededSave) { + SwingUtilities.invokeLater(() -> { + opDialog.setVisible(false); + // Log? + verboseLog.close(); + byte[] out = baos.toByteArray(); + + if (raceMode) { + JOptionPane.showMessageDialog(frame, + String.format(bundle.getString("GUI.raceModeCheckValuePopup"), + finishedCV.get())); + } else { + int response = JOptionPane.showConfirmDialog(frame, + bundle.getString("GUI.saveLogDialog.text"), + bundle.getString("GUI.saveLogDialog.title"), + JOptionPane.YES_NO_OPTION); + if (response == JOptionPane.YES_OPTION) { + try { + FileOutputStream fos = new FileOutputStream(filename + ".log"); + fos.write(0xEF); + fos.write(0xBB); + fos.write(0xBF); + fos.write(out); + fos.close(); + } catch (IOException e) { + JOptionPane.showMessageDialog(frame, + bundle.getString("GUI.logSaveFailed")); + return; + } + JOptionPane.showMessageDialog(frame, + String.format(bundle.getString("GUI.logSaved"), filename)); + } + } + if (presetMode) { + JOptionPane.showMessageDialog(frame, + bundle.getString("GUI.randomizationDone")); + // Done + if (this.unloadGameOnSuccess) { + romHandler = null; + initialState(); + } else { + reinitializeRomHandler(); + } + } else { + // Compile a config string + try { + String configString = getCurrentSettings().toString(); + // Show the preset maker + new PresetMakeDialog(frame, seed, configString); + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, + bundle.getString("GUI.cantLoadCustomNames")); + } + + // Done + if (this.unloadGameOnSuccess) { + romHandler = null; + initialState(); + } else { + reinitializeRomHandler(); + } + } + }); + } else { + SwingUtilities.invokeLater(() -> { + opDialog.setVisible(false); + romHandler = null; + initialState(); + }); + } + }); + t.start(); + } catch (Exception ex) { + attemptToLogException(ex, "GUI.saveFailed", "GUI.saveFailedNoLog", settings.toString(), Long.toString(seed)); + if (verboseLog != null) { + verboseLog.close(); + } + } + } + + private void presetLoader() { + PresetLoadDialog pld = new PresetLoadDialog(this,frame); + if (pld.isCompleted()) { + // Apply it + long seed = pld.getSeed(); + String config = pld.getConfigString(); + this.romHandler = pld.getROM(); + if (gameUpdates.containsKey(this.romHandler.getROMCode())) { + this.romHandler.loadGameUpdate(gameUpdates.get(this.romHandler.getROMCode())); + } + this.romLoaded(); + Settings settings; + try { + settings = Settings.fromString(config); + settings.tweakForRom(this.romHandler); + this.restoreStateFromSettings(settings); + } catch (UnsupportedEncodingException | IllegalArgumentException e) { + // settings load failed + e.printStackTrace(); + this.romHandler = null; + initialState(); + } + SaveType outputType = askForSaveType(); + romSaveChooser.setSelectedFile(null); + boolean allowed = false; + File fh = null; + if (outputType == SaveType.FILE) { + romSaveChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + int returnVal = romSaveChooser.showSaveDialog(frame); + if (returnVal == JFileChooser.APPROVE_OPTION) { + fh = romSaveChooser.getSelectedFile(); + // Fix or add extension + List extensions = new ArrayList<>(Arrays.asList("sgb", "gbc", "gba", "nds", "cxi")); + extensions.remove(this.romHandler.getDefaultExtension()); + fh = FileFunctions.fixFilename(fh, this.romHandler.getDefaultExtension(), extensions); + allowed = true; + if (this.romHandler instanceof AbstractDSRomHandler || this.romHandler instanceof Abstract3DSRomHandler) { + String currentFN = this.romHandler.loadedFilename(); + if (currentFN.equals(fh.getAbsolutePath())) { + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.cantOverwriteDS")); + allowed = false; + } + } + } else { + this.romHandler = null; + initialState(); + } + } else if (outputType == SaveType.DIRECTORY) { + romSaveChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int returnVal = romSaveChooser.showSaveDialog(frame); + if (returnVal == JFileChooser.APPROVE_OPTION) { + fh = romSaveChooser.getSelectedFile(); + allowed = true; + } else { + this.romHandler = null; + initialState(); + } + } + + if (allowed && fh != null) { + // Apply the seed we were given + RandomSource.seed(seed); + presetMode = true; + performRandomization(fh.getAbsolutePath(), seed, pld.getCustomNames(), outputType == SaveType.DIRECTORY); + } + } + + } + + + private enum SaveType { + FILE, DIRECTORY, INVALID + } + + private SaveType askForSaveType() { + SaveType saveType = SaveType.FILE; + if (romHandler.hasGameUpdateLoaded()) { + String text = bundle.getString("GUI.savingWithGameUpdate"); + String url = "https://github.com/Ajarmar/universal-pokemon-randomizer-zx/wiki/Randomizing-the-3DS-games#managing-game-updates"; + showMessageDialogWithLink(text, url); + saveType = SaveType.DIRECTORY; + } else if (romHandler.generationOfPokemon() == 6 || romHandler.generationOfPokemon() == 7) { + Object[] options3DS = {"CXI", "LayeredFS"}; + String question = "Would you like to output your 3DS game as a CXI file or as a LayeredFS directory?"; + JLabel label = new JLabel("
For more information, click here."); + label.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + Desktop desktop = java.awt.Desktop.getDesktop(); + try { + desktop.browse(new URI("https://github.com/Ajarmar/universal-pokemon-randomizer-zx/wiki/Randomizing-the-3DS-games#changes-to-saving-a-rom-when-working-with-3ds-games")); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + }); + label.setCursor(new java.awt.Cursor(Cursor.HAND_CURSOR)); + Object[] messages = {question,label}; + int returnVal3DS = JOptionPane.showOptionDialog(frame, + messages, + "3DS Output Choice", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options3DS, + null); + if (returnVal3DS < 0) { + saveType = SaveType.INVALID; + } else { + saveType = SaveType.values()[returnVal3DS]; + } + } + return saveType; + } + + private void applyGameUpdateMenuItemActionPerformed() { + + if (romHandler == null) return; + + gameUpdateChooser.setSelectedFile(null); + gameUpdateChooser.setFileFilter(new GameUpdateFilter()); + int returnVal = gameUpdateChooser.showOpenDialog(frame); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File fh = gameUpdateChooser.getSelectedFile(); + + // On the 3DS, the update has the same title ID as the base game, save for the 8th character, + // which is 'E' instead of '0'. We can use this to detect if the update matches the game. + String actualUpdateTitleId = Abstract3DSRomHandler.getTitleIdFromFile(fh.getAbsolutePath()); + if (actualUpdateTitleId == null) { + // Error: couldn't find a title ID in the update + JOptionPane.showMessageDialog(frame, String.format(bundle.getString("GUI.invalidGameUpdate"), fh.getName())); + return; + } + Abstract3DSRomHandler ctrRomHandler = (Abstract3DSRomHandler) romHandler; + String baseGameTitleId = ctrRomHandler.getTitleIdFromLoadedROM(); + char[] baseGameTitleIdChars = baseGameTitleId.toCharArray(); + baseGameTitleIdChars[7] = 'E'; + String expectedUpdateTitleId = String.valueOf(baseGameTitleIdChars); + if (actualUpdateTitleId.equals(expectedUpdateTitleId)) { + try { + romHandler.loadGameUpdate(fh.getAbsolutePath()); + } catch (EncryptedROMException ex) { + JOptionPane.showMessageDialog(mainPanel, + String.format(bundle.getString("GUI.encryptedRom"), fh.getAbsolutePath())); + return; + } + gameUpdates.put(romHandler.getROMCode(), fh.getAbsolutePath()); + setRomNameLabel(); + String text = String.format(bundle.getString("GUI.gameUpdateApplied"), romHandler.getROMName()); + String url = "https://github.com/Ajarmar/universal-pokemon-randomizer-zx/wiki/Randomizing-the-3DS-games#3ds-game-updates"; + showMessageDialogWithLink(text, url); + } else { + // Error: update is not for the correct game + JOptionPane.showMessageDialog(frame, String.format(bundle.getString("GUI.nonMatchingGameUpdate"), fh.getName(), romHandler.getROMName())); + } + } + } + + private void loadGetSettingsMenuItemActionPerformed() { + + if (romHandler == null) return; + + String currentSettingsString = "Current Settings String:"; + JTextField currentSettingsStringField = new JTextField(); + currentSettingsStringField.setEditable(false); + try { + String theSettingsString = Version.VERSION + getCurrentSettings().toString(); + currentSettingsStringField.setColumns(Settings.LENGTH_OF_SETTINGS_DATA * 2); + currentSettingsStringField.setText(theSettingsString); + } catch (IOException e) { + e.printStackTrace(); + } + String loadSettingsString = "Load Settings String:"; + JTextField loadSettingsStringField = new JTextField(); + Object[] messages = {currentSettingsString,currentSettingsStringField,loadSettingsString,loadSettingsStringField}; + Object[] options = {"Load","Cancel"}; + int choice = JOptionPane.showOptionDialog( + frame, + messages, + "Get/Load Settings String", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + null + ); + if (choice == 0) { + String configString = loadSettingsStringField.getText().trim(); + if (configString.length() > 0) { + if (configString.length() < 3) { + JOptionPane.showMessageDialog(frame,bundle.getString("GUI.invalidSettingsString")); + } else { + try { + int settingsStringVersionNumber = Integer.parseInt(configString.substring(0, 3)); + if (settingsStringVersionNumber < Version.VERSION) { + JOptionPane.showMessageDialog(frame,bundle.getString("GUI.settingsStringOlder")); + String updatedSettingsString = new SettingsUpdater().update(settingsStringVersionNumber, configString.substring(3)); + Settings settings = Settings.fromString(updatedSettingsString); + settings.tweakForRom(this.romHandler); + restoreStateFromSettings(settings); + JOptionPane.showMessageDialog(frame,bundle.getString("GUI.settingsStringLoaded")); + } else if (settingsStringVersionNumber > Version.VERSION) { + JOptionPane.showMessageDialog(frame,bundle.getString("GUI.settingsStringTooNew")); + } else { + Settings settings = Settings.fromString(configString.substring(3)); + settings.tweakForRom(this.romHandler); + restoreStateFromSettings(settings); + JOptionPane.showMessageDialog(frame,bundle.getString("GUI.settingsStringLoaded")); + } + } catch (UnsupportedEncodingException | IllegalArgumentException ex) { + JOptionPane.showMessageDialog(frame,bundle.getString("GUI.invalidSettingsString")); + } + } + + } + } + } + + private void keepOrUnloadGameAfterRandomizingMenuItemActionPerformed() { + this.unloadGameOnSuccess = !this.unloadGameOnSuccess; + if (this.unloadGameOnSuccess) { + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.unloadGameAfterRandomizing")); + keepOrUnloadGameAfterRandomizingMenuItem.setText(bundle.getString("GUI.keepGameLoadedAfterRandomizingMenuItem.text")); + } else { + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.keepGameLoadedAfterRandomizing")); + keepOrUnloadGameAfterRandomizingMenuItem.setText(bundle.getString("GUI.unloadGameAfterRandomizingMenuItem.text")); + } + } + + private void showMessageDialogWithLink(String text, String url) { + JLabel label = new JLabel("For more information, click here."); + label.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + Desktop desktop = java.awt.Desktop.getDesktop(); + try { + desktop.browse(new URI(url)); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + }); + label.setCursor(new java.awt.Cursor(Cursor.HAND_CURSOR)); + Object[] messages = {text,label}; + JOptionPane.showMessageDialog(frame, messages); + } + + + // This is only intended to be used with the "Keep Game Loaded After Randomizing" setting; it assumes that + // the game has already been loaded once, and we just need to reload the same game to reinitialize the + // RomHandler. Don't use this for other purposes unless you know what you're doing. + private void reinitializeRomHandler() { + String currentFN = this.romHandler.loadedFilename(); + for (RomHandler.Factory rhf : checkHandlers) { + if (rhf.isLoadable(currentFN)) { + this.romHandler = rhf.create(RandomSource.instance()); + opDialog = new OperationDialog(bundle.getString("GUI.loadingText"), frame, true); + Thread t = new Thread(() -> { + SwingUtilities.invokeLater(() -> opDialog.setVisible(true)); + try { + this.romHandler.loadRom(currentFN); + if (gameUpdates.containsKey(this.romHandler.getROMCode())) { + this.romHandler.loadGameUpdate(gameUpdates.get(this.romHandler.getROMCode())); + } + } catch (Exception ex) { + attemptToLogException(ex, "GUI.loadFailed", "GUI.loadFailedNoLog", null, null); + } + SwingUtilities.invokeLater(() -> { + this.opDialog.setVisible(false); + }); + }); + t.start(); + + return; + } + } + } + + private void restoreStateFromSettings(Settings settings) { + + limitPokemonCheckBox.setSelected(settings.isLimitPokemon()); + currentRestrictions = settings.getCurrentRestrictions(); + if (currentRestrictions != null) { + currentRestrictions.limitToGen(romHandler.generationOfPokemon()); + } + noIrregularAltFormesCheckBox.setSelected(settings.isBanIrregularAltFormes()); + raceModeCheckBox.setSelected(settings.isRaceMode()); + + peChangeImpossibleEvosCheckBox.setSelected(settings.isChangeImpossibleEvolutions()); + mdUpdateMovesCheckBox.setSelected(settings.isUpdateMoves()); + mdUpdateComboBox.setSelectedIndex(Math.max(0,settings.getUpdateMovesToGeneration() - (romHandler.generationOfPokemon()+1))); + tpRandomizeTrainerNamesCheckBox.setSelected(settings.isRandomizeTrainerNames()); + tpRandomizeTrainerClassNamesCheckBox.setSelected(settings.isRandomizeTrainerClassNames()); + ptIsDualTypeCheckBox.setSelected(settings.isDualTypeOnly()); + + pbsRandomRadioButton.setSelected(settings.getBaseStatisticsMod() == Settings.BaseStatisticsMod.RANDOM); + pbsShuffleRadioButton.setSelected(settings.getBaseStatisticsMod() == Settings.BaseStatisticsMod.SHUFFLE); + pbsUnchangedRadioButton.setSelected(settings.getBaseStatisticsMod() == Settings.BaseStatisticsMod.UNCHANGED); + pbsFollowEvolutionsCheckBox.setSelected(settings.isBaseStatsFollowEvolutions()); + pbsUpdateBaseStatsCheckBox.setSelected(settings.isUpdateBaseStats()); + pbsUpdateComboBox.setSelectedIndex(Math.max(0,settings.getUpdateBaseStatsToGeneration() - (Math.max(6,romHandler.generationOfPokemon()+1)))); + pbsStandardizeEXPCurvesCheckBox.setSelected(settings.isStandardizeEXPCurves()); + pbsLegendariesSlowRadioButton.setSelected(settings.getExpCurveMod() == Settings.ExpCurveMod.LEGENDARIES); + pbsStrongLegendariesSlowRadioButton.setSelected(settings.getExpCurveMod() == Settings.ExpCurveMod.STRONG_LEGENDARIES); + pbsAllMediumFastRadioButton.setSelected(settings.getExpCurveMod() == Settings.ExpCurveMod.ALL); + ExpCurve[] expCurves = getEXPCurvesForGeneration(romHandler.generationOfPokemon()); + int index = 0; + for (int i = 0; i < expCurves.length; i++) { + if (expCurves[i] == settings.getSelectedEXPCurve()) { + index = i; + } + } + pbsEXPCurveComboBox.setSelectedIndex(index); + pbsFollowMegaEvosCheckBox.setSelected(settings.isBaseStatsFollowMegaEvolutions()); + pbsAssignEvoStatsRandomlyCheckBox.setSelected(settings.isAssignEvoStatsRandomly()); + + paUnchangedRadioButton.setSelected(settings.getAbilitiesMod() == Settings.AbilitiesMod.UNCHANGED); + paRandomRadioButton.setSelected(settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE); + paAllowWonderGuardCheckBox.setSelected(settings.isAllowWonderGuard()); + paFollowEvolutionsCheckBox.setSelected(settings.isAbilitiesFollowEvolutions()); + paTrappingAbilitiesCheckBox.setSelected(settings.isBanTrappingAbilities()); + paNegativeAbilitiesCheckBox.setSelected(settings.isBanNegativeAbilities()); + paBadAbilitiesCheckBox.setSelected(settings.isBanBadAbilities()); + paFollowMegaEvosCheckBox.setSelected(settings.isAbilitiesFollowMegaEvolutions()); + paWeighDuplicatesTogetherCheckBox.setSelected(settings.isWeighDuplicateAbilitiesTogether()); + paEnsureTwoAbilitiesCheckbox.setSelected(settings.isEnsureTwoAbilities()); + + ptRandomFollowEvolutionsRadioButton.setSelected(settings.getTypesMod() == Settings.TypesMod.RANDOM_FOLLOW_EVOLUTIONS); + ptRandomCompletelyRadioButton.setSelected(settings.getTypesMod() == Settings.TypesMod.COMPLETELY_RANDOM); + ptUnchangedRadioButton.setSelected(settings.getTypesMod() == Settings.TypesMod.UNCHANGED); + ptFollowMegaEvosCheckBox.setSelected(settings.isTypesFollowMegaEvolutions()); + pmsNoGameBreakingMovesCheckBox.setSelected(settings.doBlockBrokenMoves()); + + peMakeEvolutionsEasierCheckBox.setSelected(settings.isMakeEvolutionsEasier()); + peRemoveTimeBasedEvolutionsCheckBox.setSelected(settings.isRemoveTimeBasedEvolutions()); + + spCustomRadioButton.setSelected(settings.getStartersMod() == Settings.StartersMod.CUSTOM); + spRandomCompletelyRadioButton.setSelected(settings.getStartersMod() == Settings.StartersMod.COMPLETELY_RANDOM); + spUnchangedRadioButton.setSelected(settings.getStartersMod() == Settings.StartersMod.UNCHANGED); + spRandomTwoEvosRadioButton.setSelected(settings.getStartersMod() == Settings.StartersMod.RANDOM_WITH_TWO_EVOLUTIONS); + spRandomizeStarterHeldItemsCheckBox.setSelected(settings.isRandomizeStartersHeldItems()); + spBanBadItemsCheckBox.setSelected(settings.isBanBadRandomStarterHeldItems()); + spAllowAltFormesCheckBox.setSelected(settings.isAllowStarterAltFormes()); + + int[] customStarters = settings.getCustomStarters(); + spComboBox1.setSelectedIndex(customStarters[0] - 1); + spComboBox2.setSelectedIndex(customStarters[1] - 1); + spComboBox3.setSelectedIndex(customStarters[2] - 1); + + peUnchangedRadioButton.setSelected(settings.getEvolutionsMod() == Settings.EvolutionsMod.UNCHANGED); + peRandomRadioButton.setSelected(settings.getEvolutionsMod() == Settings.EvolutionsMod.RANDOM); + peRandomEveryLevelRadioButton.setSelected(settings.getEvolutionsMod() == Settings.EvolutionsMod.RANDOM_EVERY_LEVEL); + peSimilarStrengthCheckBox.setSelected(settings.isEvosSimilarStrength()); + peSameTypingCheckBox.setSelected(settings.isEvosSameTyping()); + peLimitEvolutionsToThreeCheckBox.setSelected(settings.isEvosMaxThreeStages()); + peForceChangeCheckBox.setSelected(settings.isEvosForceChange()); + peAllowAltFormesCheckBox.setSelected(settings.isEvosAllowAltFormes()); + + mdRandomizeMoveAccuracyCheckBox.setSelected(settings.isRandomizeMoveAccuracies()); + mdRandomizeMoveCategoryCheckBox.setSelected(settings.isRandomizeMoveCategory()); + mdRandomizeMovePowerCheckBox.setSelected(settings.isRandomizeMovePowers()); + mdRandomizeMovePPCheckBox.setSelected(settings.isRandomizeMovePPs()); + mdRandomizeMoveTypesCheckBox.setSelected(settings.isRandomizeMoveTypes()); + + pmsRandomCompletelyRadioButton.setSelected(settings.getMovesetsMod() == Settings.MovesetsMod.COMPLETELY_RANDOM); + pmsRandomPreferringSameTypeRadioButton.setSelected(settings.getMovesetsMod() == Settings.MovesetsMod.RANDOM_PREFER_SAME_TYPE); + pmsUnchangedRadioButton.setSelected(settings.getMovesetsMod() == Settings.MovesetsMod.UNCHANGED); + pmsMetronomeOnlyModeRadioButton.setSelected(settings.getMovesetsMod() == Settings.MovesetsMod.METRONOME_ONLY); + pmsGuaranteedLevel1MovesCheckBox.setSelected(settings.isStartWithGuaranteedMoves()); + pmsGuaranteedLevel1MovesSlider.setValue(settings.getGuaranteedMoveCount()); + pmsReorderDamagingMovesCheckBox.setSelected(settings.isReorderDamagingMoves()); + pmsForceGoodDamagingCheckBox.setSelected(settings.isMovesetsForceGoodDamaging()); + pmsForceGoodDamagingSlider.setValue(settings.getMovesetsGoodDamagingPercent()); + pmsNoGameBreakingMovesCheckBox.setSelected(settings.isBlockBrokenMovesetMoves()); + pmsEvolutionMovesCheckBox.setSelected(settings.isEvolutionMovesForAll()); + + tpSimilarStrengthCheckBox.setSelected(settings.isTrainersUsePokemonOfSimilarStrength()); + tpComboBox.setSelectedItem(trainerSettings.get(settings.getTrainersMod().ordinal())); + tpRivalCarriesStarterCheckBox.setSelected(settings.isRivalCarriesStarterThroughout()); + tpWeightTypesCheckBox.setSelected(settings.isTrainersMatchTypingDistribution()); + tpDontUseLegendariesCheckBox.setSelected(settings.isTrainersBlockLegendaries()); + tpNoEarlyWonderGuardCheckBox.setSelected(settings.isTrainersBlockEarlyWonderGuard()); + tpForceFullyEvolvedAtCheckBox.setSelected(settings.isTrainersForceFullyEvolved()); + tpForceFullyEvolvedAtSlider.setValue(settings.getTrainersForceFullyEvolvedLevel()); + tpPercentageLevelModifierCheckBox.setSelected(settings.isTrainersLevelModified()); + tpPercentageLevelModifierSlider.setValue(settings.getTrainersLevelModifier()); + tpEliteFourUniquePokemonCheckBox.setSelected(settings.getEliteFourUniquePokemonNumber() > 0); + tpEliteFourUniquePokemonSpinner.setValue(settings.getEliteFourUniquePokemonNumber() > 0 ? settings.getEliteFourUniquePokemonNumber() : 1); + tpAllowAlternateFormesCheckBox.setSelected(settings.isAllowTrainerAlternateFormes()); + tpSwapMegaEvosCheckBox.setSelected(settings.isSwapTrainerMegaEvos()); + tpDoubleBattleModeCheckBox.setSelected(settings.isDoubleBattleMode()); + tpBossTrainersCheckBox.setSelected(settings.getAdditionalBossTrainerPokemon() > 0); + tpBossTrainersSpinner.setValue(settings.getAdditionalBossTrainerPokemon() > 0 ? settings.getAdditionalBossTrainerPokemon() : 1); + tpImportantTrainersCheckBox.setSelected(settings.getAdditionalImportantTrainerPokemon() > 0); + tpImportantTrainersSpinner.setValue(settings.getAdditionalImportantTrainerPokemon() > 0 ? settings.getAdditionalImportantTrainerPokemon() : 1); + tpRegularTrainersCheckBox.setSelected(settings.getAdditionalRegularTrainerPokemon() > 0); + tpRegularTrainersSpinner.setValue(settings.getAdditionalRegularTrainerPokemon() > 0 ? settings.getAdditionalRegularTrainerPokemon() : 1); + tpBossTrainersItemsCheckBox.setSelected(settings.isRandomizeHeldItemsForBossTrainerPokemon()); + tpImportantTrainersItemsCheckBox.setSelected(settings.isRandomizeHeldItemsForImportantTrainerPokemon()); + tpRegularTrainersItemsCheckBox.setSelected(settings.isRandomizeHeldItemsForRegularTrainerPokemon()); + tpConsumableItemsOnlyCheckBox.setSelected(settings.isConsumableItemsOnlyForTrainers()); + tpSensibleItemsCheckBox.setSelected(settings.isSensibleItemsOnlyForTrainers()); + tpHighestLevelGetsItemCheckBox.setSelected(settings.isHighestLevelGetsItemsForTrainers()); + + tpRandomShinyTrainerPokemonCheckBox.setSelected(settings.isShinyChance()); + tpBetterMovesetsCheckBox.setSelected(settings.isBetterTrainerMovesets()); + + totpUnchangedRadioButton.setSelected(settings.getTotemPokemonMod() == Settings.TotemPokemonMod.UNCHANGED); + totpRandomRadioButton.setSelected(settings.getTotemPokemonMod() == Settings.TotemPokemonMod.RANDOM); + totpRandomSimilarStrengthRadioButton.setSelected(settings.getTotemPokemonMod() == Settings.TotemPokemonMod.SIMILAR_STRENGTH); + totpAllyUnchangedRadioButton.setSelected(settings.getAllyPokemonMod() == Settings.AllyPokemonMod.UNCHANGED); + totpAllyRandomRadioButton.setSelected(settings.getAllyPokemonMod() == Settings.AllyPokemonMod.RANDOM); + totpAllyRandomSimilarStrengthRadioButton.setSelected(settings.getAllyPokemonMod() == Settings.AllyPokemonMod.SIMILAR_STRENGTH); + totpAuraUnchangedRadioButton.setSelected(settings.getAuraMod() == Settings.AuraMod.UNCHANGED); + totpAuraRandomRadioButton.setSelected(settings.getAuraMod() == Settings.AuraMod.RANDOM); + totpAuraRandomSameStrengthRadioButton.setSelected(settings.getAuraMod() == Settings.AuraMod.SAME_STRENGTH); + totpRandomizeHeldItemsCheckBox.setSelected(settings.isRandomizeTotemHeldItems()); + totpAllowAltFormesCheckBox.setSelected(settings.isAllowTotemAltFormes()); + totpPercentageLevelModifierCheckBox.setSelected(settings.isTotemLevelsModified()); + totpPercentageLevelModifierSlider.setValue(settings.getTotemLevelModifier()); + + wpARCatchEmAllModeRadioButton + .setSelected(settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.CATCH_EM_ALL); + wpArea1To1RadioButton.setSelected(settings.getWildPokemonMod() == Settings.WildPokemonMod.AREA_MAPPING); + wpARNoneRadioButton.setSelected(settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.NONE); + wpARTypeThemeAreasRadioButton + .setSelected(settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.TYPE_THEME_AREAS); + wpGlobal1To1RadioButton.setSelected(settings.getWildPokemonMod() == Settings.WildPokemonMod.GLOBAL_MAPPING); + wpRandomRadioButton.setSelected(settings.getWildPokemonMod() == Settings.WildPokemonMod.RANDOM); + wpUnchangedRadioButton.setSelected(settings.getWildPokemonMod() == Settings.WildPokemonMod.UNCHANGED); + wpUseTimeBasedEncountersCheckBox.setSelected(settings.isUseTimeBasedEncounters()); + + wpSetMinimumCatchRateCheckBox.setSelected(settings.isUseMinimumCatchRate()); + wpSetMinimumCatchRateSlider.setValue(settings.getMinimumCatchRateLevel()); + wpDontUseLegendariesCheckBox.setSelected(settings.isBlockWildLegendaries()); + wpARSimilarStrengthRadioButton + .setSelected(settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.SIMILAR_STRENGTH); + wpRandomizeHeldItemsCheckBox.setSelected(settings.isRandomizeWildPokemonHeldItems()); + wpBanBadItemsCheckBox.setSelected(settings.isBanBadRandomWildPokemonHeldItems()); + wpBalanceShakingGrassPokemonCheckBox.setSelected(settings.isBalanceShakingGrass()); + wpPercentageLevelModifierCheckBox.setSelected(settings.isWildLevelsModified()); + wpPercentageLevelModifierSlider.setValue(settings.getWildLevelModifier()); + wpAllowAltFormesCheckBox.setSelected(settings.isAllowWildAltFormes()); + + stpUnchangedRadioButton.setSelected(settings.getStaticPokemonMod() == Settings.StaticPokemonMod.UNCHANGED); + stpSwapLegendariesSwapStandardsRadioButton.setSelected(settings.getStaticPokemonMod() == Settings.StaticPokemonMod.RANDOM_MATCHING); + stpRandomCompletelyRadioButton + .setSelected(settings.getStaticPokemonMod() == Settings.StaticPokemonMod.COMPLETELY_RANDOM); + stpRandomSimilarStrengthRadioButton + .setSelected(settings.getStaticPokemonMod() == Settings.StaticPokemonMod.SIMILAR_STRENGTH); + stpLimitMainGameLegendariesCheckBox.setSelected(settings.isLimitMainGameLegendaries()); + stpRandomize600BSTCheckBox.setSelected(settings.isLimit600()); + stpAllowAltFormesCheckBox.setSelected(settings.isAllowStaticAltFormes()); + stpSwapMegaEvosCheckBox.setSelected(settings.isSwapStaticMegaEvos()); + stpPercentageLevelModifierCheckBox.setSelected(settings.isStaticLevelModified()); + stpPercentageLevelModifierSlider.setValue(settings.getStaticLevelModifier()); + stpFixMusicCheckBox.setSelected(settings.isCorrectStaticMusic()); + + thcRandomCompletelyRadioButton + .setSelected(settings.getTmsHmsCompatibilityMod() == Settings.TMsHMsCompatibilityMod.COMPLETELY_RANDOM); + thcRandomPreferSameTypeRadioButton + .setSelected(settings.getTmsHmsCompatibilityMod() == Settings.TMsHMsCompatibilityMod.RANDOM_PREFER_TYPE); + thcUnchangedRadioButton + .setSelected(settings.getTmsHmsCompatibilityMod() == Settings.TMsHMsCompatibilityMod.UNCHANGED); + tmRandomRadioButton.setSelected(settings.getTmsMod() == Settings.TMsMod.RANDOM); + tmUnchangedRadioButton.setSelected(settings.getTmsMod() == Settings.TMsMod.UNCHANGED); + tmLevelupMoveSanityCheckBox.setSelected(settings.isTmLevelUpMoveSanity()); + tmKeepFieldMoveTMsCheckBox.setSelected(settings.isKeepFieldMoveTMs()); + thcFullCompatibilityRadioButton.setSelected(settings.getTmsHmsCompatibilityMod() == Settings.TMsHMsCompatibilityMod.FULL); + tmFullHMCompatibilityCheckBox.setSelected(settings.isFullHMCompat()); + tmForceGoodDamagingCheckBox.setSelected(settings.isTmsForceGoodDamaging()); + tmForceGoodDamagingSlider.setValue(settings.getTmsGoodDamagingPercent()); + tmNoGameBreakingMovesCheckBox.setSelected(settings.isBlockBrokenTMMoves()); + tmFollowEvolutionsCheckBox.setSelected(settings.isTmsFollowEvolutions()); + + mtcRandomCompletelyRadioButton + .setSelected(settings.getMoveTutorsCompatibilityMod() == Settings.MoveTutorsCompatibilityMod.COMPLETELY_RANDOM); + mtcRandomPreferSameTypeRadioButton + .setSelected(settings.getMoveTutorsCompatibilityMod() == Settings.MoveTutorsCompatibilityMod.RANDOM_PREFER_TYPE); + mtcUnchangedRadioButton + .setSelected(settings.getMoveTutorsCompatibilityMod() == Settings.MoveTutorsCompatibilityMod.UNCHANGED); + mtRandomRadioButton.setSelected(settings.getMoveTutorMovesMod() == Settings.MoveTutorMovesMod.RANDOM); + mtUnchangedRadioButton.setSelected(settings.getMoveTutorMovesMod() == Settings.MoveTutorMovesMod.UNCHANGED); + mtLevelupMoveSanityCheckBox.setSelected(settings.isTutorLevelUpMoveSanity()); + mtKeepFieldMoveTutorsCheckBox.setSelected(settings.isKeepFieldMoveTutors()); + mtcFullCompatibilityRadioButton + .setSelected(settings.getMoveTutorsCompatibilityMod() == Settings.MoveTutorsCompatibilityMod.FULL); + mtForceGoodDamagingCheckBox.setSelected(settings.isTutorsForceGoodDamaging()); + mtForceGoodDamagingSlider.setValue(settings.getTutorsGoodDamagingPercent()); + mtNoGameBreakingMovesCheckBox.setSelected(settings.isBlockBrokenTutorMoves()); + mtFollowEvolutionsCheckBox.setSelected(settings.isTutorFollowEvolutions()); + + igtRandomizeBothRequestedGivenRadioButton + .setSelected(settings.getInGameTradesMod() == Settings.InGameTradesMod.RANDOMIZE_GIVEN_AND_REQUESTED); + igtRandomizeGivenPokemonOnlyRadioButton.setSelected(settings.getInGameTradesMod() == Settings.InGameTradesMod.RANDOMIZE_GIVEN); + igtRandomizeItemsCheckBox.setSelected(settings.isRandomizeInGameTradesItems()); + igtRandomizeIVsCheckBox.setSelected(settings.isRandomizeInGameTradesIVs()); + igtRandomizeNicknamesCheckBox.setSelected(settings.isRandomizeInGameTradesNicknames()); + igtRandomizeOTsCheckBox.setSelected(settings.isRandomizeInGameTradesOTs()); + igtUnchangedRadioButton.setSelected(settings.getInGameTradesMod() == Settings.InGameTradesMod.UNCHANGED); + + fiRandomRadioButton.setSelected(settings.getFieldItemsMod() == Settings.FieldItemsMod.RANDOM); + fiRandomEvenDistributionRadioButton.setSelected(settings.getFieldItemsMod() == Settings.FieldItemsMod.RANDOM_EVEN); + fiShuffleRadioButton.setSelected(settings.getFieldItemsMod() == Settings.FieldItemsMod.SHUFFLE); + fiUnchangedRadioButton.setSelected(settings.getFieldItemsMod() == Settings.FieldItemsMod.UNCHANGED); + fiBanBadItemsCheckBox.setSelected(settings.isBanBadRandomFieldItems()); + + shRandomRadioButton.setSelected(settings.getShopItemsMod() == Settings.ShopItemsMod.RANDOM); + shShuffleRadioButton.setSelected(settings.getShopItemsMod() == Settings.ShopItemsMod.SHUFFLE); + shUnchangedRadioButton.setSelected(settings.getShopItemsMod() == Settings.ShopItemsMod.UNCHANGED); + shBanBadItemsCheckBox.setSelected(settings.isBanBadRandomShopItems()); + shBanRegularShopItemsCheckBox.setSelected(settings.isBanRegularShopItems()); + shBanOverpoweredShopItemsCheckBox.setSelected(settings.isBanOPShopItems()); + shBalanceShopItemPricesCheckBox.setSelected(settings.isBalanceShopPrices()); + shGuaranteeEvolutionItemsCheckBox.setSelected(settings.isGuaranteeEvolutionItems()); + shGuaranteeXItemsCheckBox.setSelected(settings.isGuaranteeXItems()); + + puUnchangedRadioButton.setSelected(settings.getPickupItemsMod() == Settings.PickupItemsMod.UNCHANGED); + puRandomRadioButton.setSelected(settings.getPickupItemsMod() == Settings.PickupItemsMod.RANDOM); + puBanBadItemsCheckBox.setSelected(settings.isBanBadRandomPickupItems()); + + int mtsSelected = settings.getCurrentMiscTweaks(); + int mtCount = MiscTweak.allTweaks.size(); + + for (int mti = 0; mti < mtCount; mti++) { + MiscTweak mt = MiscTweak.allTweaks.get(mti); + JCheckBox mtCB = tweakCheckBoxes.get(mti); + mtCB.setSelected((mtsSelected & mt.getValue()) != 0); + } + + this.enableOrDisableSubControls(); + } + + private Settings createSettingsFromState(CustomNamesSet customNames) { + Settings settings = new Settings(); + settings.setRomName(this.romHandler.getROMName()); + + settings.setLimitPokemon(limitPokemonCheckBox.isSelected() && limitPokemonCheckBox.isVisible()); + settings.setCurrentRestrictions(currentRestrictions); + settings.setBanIrregularAltFormes(noIrregularAltFormesCheckBox.isSelected() && noIrregularAltFormesCheckBox.isVisible()); + settings.setRaceMode(raceModeCheckBox.isSelected()); + + settings.setChangeImpossibleEvolutions(peChangeImpossibleEvosCheckBox.isSelected() && peChangeImpossibleEvosCheckBox.isVisible()); + settings.setUpdateMoves(mdUpdateMovesCheckBox.isSelected() && mdUpdateMovesCheckBox.isVisible()); + settings.setUpdateMovesToGeneration(mdUpdateComboBox.getSelectedIndex() + (romHandler.generationOfPokemon()+1)); + settings.setRandomizeTrainerNames(tpRandomizeTrainerNamesCheckBox.isSelected()); + settings.setRandomizeTrainerClassNames(tpRandomizeTrainerClassNamesCheckBox.isSelected()); + + settings.setBaseStatisticsMod(pbsUnchangedRadioButton.isSelected(), pbsShuffleRadioButton.isSelected(), + pbsRandomRadioButton.isSelected()); + settings.setBaseStatsFollowEvolutions(pbsFollowEvolutionsCheckBox.isSelected()); + settings.setUpdateBaseStats(pbsUpdateBaseStatsCheckBox.isSelected() && pbsUpdateBaseStatsCheckBox.isVisible()); + settings.setUpdateBaseStatsToGeneration(pbsUpdateComboBox.getSelectedIndex() + (Math.max(6,romHandler.generationOfPokemon()+1))); + settings.setStandardizeEXPCurves(pbsStandardizeEXPCurvesCheckBox.isSelected()); + settings.setExpCurveMod(pbsLegendariesSlowRadioButton.isSelected(), pbsStrongLegendariesSlowRadioButton.isSelected(), + pbsAllMediumFastRadioButton.isSelected()); + ExpCurve[] expCurves = getEXPCurvesForGeneration(romHandler.generationOfPokemon()); + settings.setSelectedEXPCurve(expCurves[pbsEXPCurveComboBox.getSelectedIndex()]); + settings.setBaseStatsFollowMegaEvolutions(pbsFollowMegaEvosCheckBox.isSelected() && pbsFollowMegaEvosCheckBox.isVisible()); + settings.setAssignEvoStatsRandomly(pbsAssignEvoStatsRandomlyCheckBox.isSelected() && pbsAssignEvoStatsRandomlyCheckBox.isVisible()); + + settings.setAbilitiesMod(paUnchangedRadioButton.isSelected(), paRandomRadioButton.isSelected()); + settings.setAllowWonderGuard(paAllowWonderGuardCheckBox.isSelected()); + settings.setAbilitiesFollowEvolutions(paFollowEvolutionsCheckBox.isSelected()); + settings.setBanTrappingAbilities(paTrappingAbilitiesCheckBox.isSelected()); + settings.setBanNegativeAbilities(paNegativeAbilitiesCheckBox.isSelected()); + settings.setBanBadAbilities(paBadAbilitiesCheckBox.isSelected()); + settings.setAbilitiesFollowMegaEvolutions(paFollowMegaEvosCheckBox.isSelected()); + settings.setWeighDuplicateAbilitiesTogether(paWeighDuplicatesTogetherCheckBox.isSelected()); + settings.setEnsureTwoAbilities(paEnsureTwoAbilitiesCheckbox.isSelected()); + + settings.setTypesMod(ptUnchangedRadioButton.isSelected(), ptRandomFollowEvolutionsRadioButton.isSelected(), + ptRandomCompletelyRadioButton.isSelected()); + settings.setTypesFollowMegaEvolutions(ptFollowMegaEvosCheckBox.isSelected() && ptFollowMegaEvosCheckBox.isVisible()); + settings.setBlockBrokenMovesetMoves(pmsNoGameBreakingMovesCheckBox.isSelected()); + settings.setDualTypeOnly(ptIsDualTypeCheckBox.isSelected()); + + settings.setMakeEvolutionsEasier(peMakeEvolutionsEasierCheckBox.isSelected()); + settings.setRemoveTimeBasedEvolutions(peRemoveTimeBasedEvolutionsCheckBox.isSelected()); + + settings.setStartersMod(spUnchangedRadioButton.isSelected(), spCustomRadioButton.isSelected(), spRandomCompletelyRadioButton.isSelected(), + spRandomTwoEvosRadioButton.isSelected()); + settings.setRandomizeStartersHeldItems(spRandomizeStarterHeldItemsCheckBox.isSelected() && spRandomizeStarterHeldItemsCheckBox.isVisible()); + settings.setBanBadRandomStarterHeldItems(spBanBadItemsCheckBox.isSelected() && spBanBadItemsCheckBox.isVisible()); + settings.setAllowStarterAltFormes(spAllowAltFormesCheckBox.isSelected() && spAllowAltFormesCheckBox.isVisible()); + + int[] customStarters = new int[] { spComboBox1.getSelectedIndex() + 1, + spComboBox2.getSelectedIndex() + 1, spComboBox3.getSelectedIndex() + 1 }; + settings.setCustomStarters(customStarters); + + settings.setEvolutionsMod(peUnchangedRadioButton.isSelected(), peRandomRadioButton.isSelected(), peRandomEveryLevelRadioButton.isSelected()); + settings.setEvosSimilarStrength(peSimilarStrengthCheckBox.isSelected()); + settings.setEvosSameTyping(peSameTypingCheckBox.isSelected()); + settings.setEvosMaxThreeStages(peLimitEvolutionsToThreeCheckBox.isSelected()); + settings.setEvosForceChange(peForceChangeCheckBox.isSelected()); + settings.setEvosAllowAltFormes(peAllowAltFormesCheckBox.isSelected() && peAllowAltFormesCheckBox.isVisible()); + + settings.setRandomizeMoveAccuracies(mdRandomizeMoveAccuracyCheckBox.isSelected()); + settings.setRandomizeMoveCategory(mdRandomizeMoveCategoryCheckBox.isSelected()); + settings.setRandomizeMovePowers(mdRandomizeMovePowerCheckBox.isSelected()); + settings.setRandomizeMovePPs(mdRandomizeMovePPCheckBox.isSelected()); + settings.setRandomizeMoveTypes(mdRandomizeMoveTypesCheckBox.isSelected()); + + settings.setMovesetsMod(pmsUnchangedRadioButton.isSelected(), pmsRandomPreferringSameTypeRadioButton.isSelected(), + pmsRandomCompletelyRadioButton.isSelected(), pmsMetronomeOnlyModeRadioButton.isSelected()); + settings.setStartWithGuaranteedMoves(pmsGuaranteedLevel1MovesCheckBox.isSelected() && pmsGuaranteedLevel1MovesCheckBox.isVisible()); + settings.setGuaranteedMoveCount(pmsGuaranteedLevel1MovesSlider.getValue()); + settings.setReorderDamagingMoves(pmsReorderDamagingMovesCheckBox.isSelected()); + + settings.setMovesetsForceGoodDamaging(pmsForceGoodDamagingCheckBox.isSelected()); + settings.setMovesetsGoodDamagingPercent(pmsForceGoodDamagingSlider.getValue()); + settings.setBlockBrokenMovesetMoves(pmsNoGameBreakingMovesCheckBox.isSelected()); + settings.setEvolutionMovesForAll(pmsEvolutionMovesCheckBox.isVisible() && + pmsEvolutionMovesCheckBox.isSelected()); + + settings.setTrainersMod(isTrainerSetting(TRAINER_UNCHANGED), isTrainerSetting(TRAINER_RANDOM), + isTrainerSetting(TRAINER_RANDOM_EVEN), isTrainerSetting(TRAINER_RANDOM_EVEN_MAIN), + isTrainerSetting(TRAINER_TYPE_THEMED), isTrainerSetting(TRAINER_TYPE_THEMED_ELITE4_GYMS)); + settings.setTrainersUsePokemonOfSimilarStrength(tpSimilarStrengthCheckBox.isSelected()); + settings.setRivalCarriesStarterThroughout(tpRivalCarriesStarterCheckBox.isSelected()); + settings.setTrainersMatchTypingDistribution(tpWeightTypesCheckBox.isSelected()); + settings.setTrainersBlockLegendaries(tpDontUseLegendariesCheckBox.isSelected()); + settings.setTrainersBlockEarlyWonderGuard(tpNoEarlyWonderGuardCheckBox.isSelected()); + settings.setTrainersForceFullyEvolved(tpForceFullyEvolvedAtCheckBox.isSelected()); + settings.setTrainersForceFullyEvolvedLevel(tpForceFullyEvolvedAtSlider.getValue()); + settings.setTrainersLevelModified(tpPercentageLevelModifierCheckBox.isSelected()); + settings.setTrainersLevelModifier(tpPercentageLevelModifierSlider.getValue()); + settings.setEliteFourUniquePokemonNumber(tpEliteFourUniquePokemonCheckBox.isVisible() && tpEliteFourUniquePokemonCheckBox.isSelected() ? (int)tpEliteFourUniquePokemonSpinner.getValue() : 0); + settings.setAllowTrainerAlternateFormes(tpAllowAlternateFormesCheckBox.isSelected() && tpAllowAlternateFormesCheckBox.isVisible()); + settings.setSwapTrainerMegaEvos(tpSwapMegaEvosCheckBox.isSelected() && tpSwapMegaEvosCheckBox.isVisible()); + settings.setDoubleBattleMode(tpDoubleBattleModeCheckBox.isVisible() && tpDoubleBattleModeCheckBox.isSelected()); + settings.setAdditionalBossTrainerPokemon(tpBossTrainersCheckBox.isVisible() && tpBossTrainersCheckBox.isSelected() ? (int)tpBossTrainersSpinner.getValue() : 0); + settings.setAdditionalImportantTrainerPokemon(tpImportantTrainersCheckBox.isVisible() && tpImportantTrainersCheckBox.isSelected() ? (int)tpImportantTrainersSpinner.getValue() : 0); + settings.setAdditionalRegularTrainerPokemon(tpRegularTrainersCheckBox.isVisible() && tpRegularTrainersCheckBox.isSelected() ? (int)tpRegularTrainersSpinner.getValue() : 0); + settings.setShinyChance(tpRandomShinyTrainerPokemonCheckBox.isVisible() && tpRandomShinyTrainerPokemonCheckBox.isSelected()); + settings.setBetterTrainerMovesets(tpBetterMovesetsCheckBox.isVisible() && tpBetterMovesetsCheckBox.isSelected()); + settings.setRandomizeHeldItemsForBossTrainerPokemon(tpBossTrainersItemsCheckBox.isVisible() && tpBossTrainersItemsCheckBox.isSelected()); + settings.setRandomizeHeldItemsForImportantTrainerPokemon(tpImportantTrainersItemsCheckBox.isVisible() && tpImportantTrainersItemsCheckBox.isSelected()); + settings.setRandomizeHeldItemsForRegularTrainerPokemon(tpRegularTrainersItemsCheckBox.isVisible() && tpRegularTrainersItemsCheckBox.isSelected()); + settings.setConsumableItemsOnlyForTrainers(tpConsumableItemsOnlyCheckBox.isVisible() && tpConsumableItemsOnlyCheckBox.isSelected()); + settings.setSensibleItemsOnlyForTrainers(tpSensibleItemsCheckBox.isVisible() && tpSensibleItemsCheckBox.isSelected()); + settings.setHighestLevelGetsItemsForTrainers(tpHighestLevelGetsItemCheckBox.isVisible() && tpHighestLevelGetsItemCheckBox.isSelected()); + + settings.setTotemPokemonMod(totpUnchangedRadioButton.isSelected(), totpRandomRadioButton.isSelected(), totpRandomSimilarStrengthRadioButton.isSelected()); + settings.setAllyPokemonMod(totpAllyUnchangedRadioButton.isSelected(), totpAllyRandomRadioButton.isSelected(), totpAllyRandomSimilarStrengthRadioButton.isSelected()); + settings.setAuraMod(totpAuraUnchangedRadioButton.isSelected(), totpAuraRandomRadioButton.isSelected(), totpAuraRandomSameStrengthRadioButton.isSelected()); + settings.setRandomizeTotemHeldItems(totpRandomizeHeldItemsCheckBox.isSelected()); + settings.setAllowTotemAltFormes(totpAllowAltFormesCheckBox.isSelected()); + settings.setTotemLevelsModified(totpPercentageLevelModifierCheckBox.isSelected()); + settings.setTotemLevelModifier(totpPercentageLevelModifierSlider.getValue()); + + settings.setWildPokemonMod(wpUnchangedRadioButton.isSelected(), wpRandomRadioButton.isSelected(), wpArea1To1RadioButton.isSelected(), + wpGlobal1To1RadioButton.isSelected()); + settings.setWildPokemonRestrictionMod(wpARNoneRadioButton.isSelected(), wpARSimilarStrengthRadioButton.isSelected(), + wpARCatchEmAllModeRadioButton.isSelected(), wpARTypeThemeAreasRadioButton.isSelected()); + settings.setUseTimeBasedEncounters(wpUseTimeBasedEncountersCheckBox.isSelected()); + settings.setUseMinimumCatchRate(wpSetMinimumCatchRateCheckBox.isSelected()); + settings.setMinimumCatchRateLevel(wpSetMinimumCatchRateSlider.getValue()); + settings.setBlockWildLegendaries(wpDontUseLegendariesCheckBox.isSelected()); + settings.setRandomizeWildPokemonHeldItems(wpRandomizeHeldItemsCheckBox.isSelected() && wpRandomizeHeldItemsCheckBox.isVisible()); + settings.setBanBadRandomWildPokemonHeldItems(wpBanBadItemsCheckBox.isSelected() && wpBanBadItemsCheckBox.isVisible()); + settings.setBalanceShakingGrass(wpBalanceShakingGrassPokemonCheckBox.isSelected() && wpBalanceShakingGrassPokemonCheckBox.isVisible()); + settings.setWildLevelsModified(wpPercentageLevelModifierCheckBox.isSelected()); + settings.setWildLevelModifier(wpPercentageLevelModifierSlider.getValue()); + settings.setAllowWildAltFormes(wpAllowAltFormesCheckBox.isSelected() && wpAllowAltFormesCheckBox.isVisible()); + + settings.setStaticPokemonMod(stpUnchangedRadioButton.isSelected(), stpSwapLegendariesSwapStandardsRadioButton.isSelected(), + stpRandomCompletelyRadioButton.isSelected(), stpRandomSimilarStrengthRadioButton.isSelected()); + settings.setLimitMainGameLegendaries(stpLimitMainGameLegendariesCheckBox.isSelected() && stpLimitMainGameLegendariesCheckBox.isVisible()); + settings.setLimit600(stpRandomize600BSTCheckBox.isSelected()); + settings.setAllowStaticAltFormes(stpAllowAltFormesCheckBox.isSelected() && stpAllowAltFormesCheckBox.isVisible()); + settings.setSwapStaticMegaEvos(stpSwapMegaEvosCheckBox.isSelected() && stpSwapMegaEvosCheckBox.isVisible()); + settings.setStaticLevelModified(stpPercentageLevelModifierCheckBox.isSelected()); + settings.setStaticLevelModifier(stpPercentageLevelModifierSlider.getValue()); + settings.setCorrectStaticMusic(stpFixMusicCheckBox.isSelected() && stpFixMusicCheckBox.isVisible()); + + settings.setTmsMod(tmUnchangedRadioButton.isSelected(), tmRandomRadioButton.isSelected()); + + settings.setTmsHmsCompatibilityMod(thcUnchangedRadioButton.isSelected(), thcRandomPreferSameTypeRadioButton.isSelected(), + thcRandomCompletelyRadioButton.isSelected(), thcFullCompatibilityRadioButton.isSelected()); + settings.setTmLevelUpMoveSanity(tmLevelupMoveSanityCheckBox.isSelected()); + settings.setKeepFieldMoveTMs(tmKeepFieldMoveTMsCheckBox.isSelected()); + settings.setFullHMCompat(tmFullHMCompatibilityCheckBox.isSelected() && tmFullHMCompatibilityCheckBox.isVisible()); + settings.setTmsForceGoodDamaging(tmForceGoodDamagingCheckBox.isSelected()); + settings.setTmsGoodDamagingPercent(tmForceGoodDamagingSlider.getValue()); + settings.setBlockBrokenTMMoves(tmNoGameBreakingMovesCheckBox.isSelected()); + settings.setTmsFollowEvolutions(tmFollowEvolutionsCheckBox.isSelected()); + + settings.setMoveTutorMovesMod(mtUnchangedRadioButton.isSelected(), mtRandomRadioButton.isSelected()); + settings.setMoveTutorsCompatibilityMod(mtcUnchangedRadioButton.isSelected(), mtcRandomPreferSameTypeRadioButton.isSelected(), + mtcRandomCompletelyRadioButton.isSelected(), mtcFullCompatibilityRadioButton.isSelected()); + settings.setTutorLevelUpMoveSanity(mtLevelupMoveSanityCheckBox.isSelected()); + settings.setKeepFieldMoveTutors(mtKeepFieldMoveTutorsCheckBox.isSelected()); + settings.setTutorsForceGoodDamaging(mtForceGoodDamagingCheckBox.isSelected()); + settings.setTutorsGoodDamagingPercent(mtForceGoodDamagingSlider.getValue()); + settings.setBlockBrokenTutorMoves(mtNoGameBreakingMovesCheckBox.isSelected()); + settings.setTutorFollowEvolutions(mtFollowEvolutionsCheckBox.isSelected()); + + settings.setInGameTradesMod(igtUnchangedRadioButton.isSelected(), igtRandomizeGivenPokemonOnlyRadioButton.isSelected(), igtRandomizeBothRequestedGivenRadioButton.isSelected()); + settings.setRandomizeInGameTradesItems(igtRandomizeItemsCheckBox.isSelected()); + settings.setRandomizeInGameTradesIVs(igtRandomizeIVsCheckBox.isSelected()); + settings.setRandomizeInGameTradesNicknames(igtRandomizeNicknamesCheckBox.isSelected()); + settings.setRandomizeInGameTradesOTs(igtRandomizeOTsCheckBox.isSelected()); + + settings.setFieldItemsMod(fiUnchangedRadioButton.isSelected(), fiShuffleRadioButton.isSelected(), fiRandomRadioButton.isSelected(), fiRandomEvenDistributionRadioButton.isSelected()); + settings.setBanBadRandomFieldItems(fiBanBadItemsCheckBox.isSelected()); + + settings.setShopItemsMod(shUnchangedRadioButton.isSelected(), shShuffleRadioButton.isSelected(), shRandomRadioButton.isSelected()); + settings.setBanBadRandomShopItems(shBanBadItemsCheckBox.isSelected()); + settings.setBanRegularShopItems(shBanRegularShopItemsCheckBox.isSelected()); + settings.setBanOPShopItems(shBanOverpoweredShopItemsCheckBox.isSelected()); + settings.setBalanceShopPrices(shBalanceShopItemPricesCheckBox.isSelected()); + settings.setGuaranteeEvolutionItems(shGuaranteeEvolutionItemsCheckBox.isSelected()); + settings.setGuaranteeXItems(shGuaranteeXItemsCheckBox.isSelected()); + + settings.setPickupItemsMod(puUnchangedRadioButton.isSelected(), puRandomRadioButton.isSelected()); + settings.setBanBadRandomPickupItems(puBanBadItemsCheckBox.isSelected()); + + int currentMiscTweaks = 0; + int mtCount = MiscTweak.allTweaks.size(); + + for (int mti = 0; mti < mtCount; mti++) { + MiscTweak mt = MiscTweak.allTweaks.get(mti); + JCheckBox mtCB = tweakCheckBoxes.get(mti); + if (mtCB.isSelected()) { + currentMiscTweaks |= mt.getValue(); + } + } + + settings.setCurrentMiscTweaks(currentMiscTweaks); + + settings.setCustomNames(customNames); + + return settings; + } + + private Settings getCurrentSettings() throws IOException { + return createSettingsFromState(FileFunctions.getCustomNames()); + } + + private void attemptToLogException(Exception ex, String baseMessageKey, String noLogMessageKey, + String settingsString, String seedString) { + attemptToLogException(ex, baseMessageKey, noLogMessageKey, false, settingsString, seedString); + } + + private void attemptToLogException(Exception ex, String baseMessageKey, String noLogMessageKey, boolean showMessage, + String settingsString, String seedString) { + + // Make sure the operation dialog doesn't show up over the error + // dialog + SwingUtilities.invokeLater(() -> NewRandomizerGUI.this.opDialog.setVisible(false)); + + Date now = new Date(); + SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); + try { + String errlog = "error_" + ft.format(now) + ".txt"; + PrintStream ps = new PrintStream(new FileOutputStream(errlog)); + ps.println("Randomizer Version: " + Version.VERSION_STRING); + if (seedString != null) { + ps.println("Seed: " + seedString); + } + if (settingsString != null) { + ps.println("Settings String: " + Version.VERSION + settingsString); + } + ps.println("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vm.name")); + PrintStream e1 = System.err; + System.setErr(ps); + if (this.romHandler != null) { + try { + ps.println("ROM: " + romHandler.getROMName()); + ps.println("Code: " + romHandler.getROMCode()); + ps.println("Reported Support Level: " + romHandler.getSupportLevel()); + ps.println(); + } catch (Exception ex2) { + // Do nothing, just don't fail + } + } + ex.printStackTrace(); + ps.println(); + ps.println("--ROM Diagnostics--"); + if (!romHandler.isRomValid()) { + ps.println(bundle.getString("Log.InvalidRomLoaded")); + } + romHandler.printRomDiagnostics(ps); + System.setErr(e1); + ps.close(); + if (showMessage) { + JOptionPane.showMessageDialog(mainPanel, + String.format(bundle.getString(baseMessageKey), ex.getMessage(), errlog)); + } else { + JOptionPane.showMessageDialog(mainPanel, String.format(bundle.getString(baseMessageKey), errlog)); + } + } catch (Exception logex) { + if (showMessage) { + JOptionPane.showMessageDialog(mainPanel, String.format(bundle.getString(noLogMessageKey), ex.getMessage())); + } else { + JOptionPane.showMessageDialog(mainPanel, bundle.getString(noLogMessageKey)); + } + } + } + + public String getValidRequiredROMName(String config, CustomNamesSet customNames) + throws UnsupportedEncodingException, InvalidSupplementFilesException { + try { + Utils.validatePresetSupplementFiles(config, customNames); + } catch (InvalidSupplementFilesException e) { + switch (e.getType()) { + case CUSTOM_NAMES: + JOptionPane.showMessageDialog(null, bundle.getString("GUI.presetDifferentCustomNames")); + break; + default: + throw e; + } + } + byte[] data = Base64.getDecoder().decode(config); + + int nameLength = data[Settings.LENGTH_OF_SETTINGS_DATA] & 0xFF; + if (data.length != Settings.LENGTH_OF_SETTINGS_DATA + 9 + nameLength) { + return null; // not valid length + } + return new String(data, Settings.LENGTH_OF_SETTINGS_DATA + 1, nameLength, "US-ASCII"); + } + + private void initialState() { + + romNameLabel.setText(bundle.getString("GUI.noRomLoaded")); + romCodeLabel.setText(""); + romSupportLabel.setText(""); + + gameMascotLabel.setIcon(emptyIcon); + + limitPokemonCheckBox.setVisible(true); + limitPokemonCheckBox.setEnabled(false); + limitPokemonCheckBox.setSelected(false); + limitPokemonButton.setVisible(true); + limitPokemonButton.setEnabled(false); + noIrregularAltFormesCheckBox.setVisible(true); + noIrregularAltFormesCheckBox.setEnabled(false); + noIrregularAltFormesCheckBox.setSelected(false); + raceModeCheckBox.setVisible(true); + raceModeCheckBox.setEnabled(false); + raceModeCheckBox.setSelected(false); + + currentRestrictions = null; + + openROMButton.setVisible(true); + openROMButton.setEnabled(true); + openROMButton.setSelected(false); + randomizeSaveButton.setVisible(true); + randomizeSaveButton.setEnabled(true); + randomizeSaveButton.setSelected(false); + premadeSeedButton.setVisible(true); + premadeSeedButton.setEnabled(true); + premadeSeedButton.setSelected(false); + settingsButton.setVisible(true); + settingsButton.setEnabled(true); + settingsButton.setSelected(false); + + loadSettingsButton.setVisible(true); + loadSettingsButton.setEnabled(false); + loadSettingsButton.setSelected(false); + saveSettingsButton.setVisible(true); + saveSettingsButton.setEnabled(false); + saveSettingsButton.setSelected(false); + pbsUnchangedRadioButton.setVisible(true); + pbsUnchangedRadioButton.setEnabled(false); + pbsUnchangedRadioButton.setSelected(false); + pbsShuffleRadioButton.setVisible(true); + pbsShuffleRadioButton.setEnabled(false); + pbsShuffleRadioButton.setSelected(false); + pbsRandomRadioButton.setVisible(true); + pbsRandomRadioButton.setEnabled(false); + pbsRandomRadioButton.setSelected(false); + pbsLegendariesSlowRadioButton.setVisible(true); + pbsLegendariesSlowRadioButton.setEnabled(false); + pbsLegendariesSlowRadioButton.setSelected(false); + pbsStrongLegendariesSlowRadioButton.setVisible(true); + pbsStrongLegendariesSlowRadioButton.setEnabled(false); + pbsStrongLegendariesSlowRadioButton.setSelected(false); + pbsAllMediumFastRadioButton.setVisible(true); + pbsAllMediumFastRadioButton.setEnabled(false); + pbsAllMediumFastRadioButton.setSelected(false); + pbsStandardizeEXPCurvesCheckBox.setVisible(true); + pbsStandardizeEXPCurvesCheckBox.setEnabled(false); + pbsStandardizeEXPCurvesCheckBox.setSelected(false); + pbsEXPCurveComboBox.setVisible(true); + pbsEXPCurveComboBox.setEnabled(false); + pbsEXPCurveComboBox.setSelectedIndex(0); + pbsEXPCurveComboBox.setModel(new DefaultComboBoxModel<>(new String[] { "Medium Fast" })); + pbsFollowEvolutionsCheckBox.setVisible(true); + pbsFollowEvolutionsCheckBox.setEnabled(false); + pbsFollowEvolutionsCheckBox.setSelected(false); + pbsUpdateBaseStatsCheckBox.setVisible(true); + pbsUpdateBaseStatsCheckBox.setEnabled(false); + pbsUpdateBaseStatsCheckBox.setSelected(false); + pbsFollowMegaEvosCheckBox.setVisible(true); + pbsFollowMegaEvosCheckBox.setEnabled(false); + pbsFollowMegaEvosCheckBox.setSelected(false); + pbsUpdateComboBox.setVisible(true); + pbsUpdateComboBox.setEnabled(false); + pbsUpdateComboBox.setSelectedIndex(0); + pbsUpdateComboBox.setModel(new DefaultComboBoxModel<>(new String[] { "--" })); + pbsAssignEvoStatsRandomlyCheckBox.setVisible(true); + pbsAssignEvoStatsRandomlyCheckBox.setEnabled(false); + pbsAssignEvoStatsRandomlyCheckBox.setSelected(false); + ptUnchangedRadioButton.setVisible(true); + ptUnchangedRadioButton.setEnabled(false); + ptUnchangedRadioButton.setSelected(false); + ptRandomFollowEvolutionsRadioButton.setVisible(true); + ptRandomFollowEvolutionsRadioButton.setEnabled(false); + ptRandomFollowEvolutionsRadioButton.setSelected(false); + ptRandomCompletelyRadioButton.setVisible(true); + ptRandomCompletelyRadioButton.setEnabled(false); + ptRandomCompletelyRadioButton.setSelected(false); + ptFollowMegaEvosCheckBox.setVisible(true); + ptFollowMegaEvosCheckBox.setEnabled(false); + ptFollowMegaEvosCheckBox.setSelected(false); + ptIsDualTypeCheckBox.setVisible(true); + ptIsDualTypeCheckBox.setEnabled(false); + ptIsDualTypeCheckBox.setSelected(false); + pokemonAbilitiesPanel.setVisible(true); + paUnchangedRadioButton.setVisible(true); + paUnchangedRadioButton.setEnabled(false); + paUnchangedRadioButton.setSelected(false); + paRandomRadioButton.setVisible(true); + paRandomRadioButton.setEnabled(false); + paRandomRadioButton.setSelected(false); + paAllowWonderGuardCheckBox.setVisible(true); + paAllowWonderGuardCheckBox.setEnabled(false); + paAllowWonderGuardCheckBox.setSelected(false); + paFollowEvolutionsCheckBox.setVisible(true); + paFollowEvolutionsCheckBox.setEnabled(false); + paFollowEvolutionsCheckBox.setSelected(false); + paTrappingAbilitiesCheckBox.setVisible(true); + paTrappingAbilitiesCheckBox.setEnabled(false); + paTrappingAbilitiesCheckBox.setSelected(false); + paNegativeAbilitiesCheckBox.setVisible(true); + paNegativeAbilitiesCheckBox.setEnabled(false); + paNegativeAbilitiesCheckBox.setSelected(false); + paBadAbilitiesCheckBox.setVisible(true); + paBadAbilitiesCheckBox.setEnabled(false); + paBadAbilitiesCheckBox.setSelected(false); + paFollowMegaEvosCheckBox.setVisible(true); + paFollowMegaEvosCheckBox.setEnabled(false); + paFollowMegaEvosCheckBox.setSelected(false); + paWeighDuplicatesTogetherCheckBox.setVisible(true); + paWeighDuplicatesTogetherCheckBox.setEnabled(false); + paWeighDuplicatesTogetherCheckBox.setSelected(false); + paEnsureTwoAbilitiesCheckbox.setVisible(true); + paEnsureTwoAbilitiesCheckbox.setEnabled(false); + paEnsureTwoAbilitiesCheckbox.setSelected(false); + peUnchangedRadioButton.setVisible(true); + peUnchangedRadioButton.setEnabled(false); + peUnchangedRadioButton.setSelected(false); + peRandomRadioButton.setVisible(true); + peRandomRadioButton.setEnabled(false); + peRandomRadioButton.setSelected(false); + peRandomEveryLevelRadioButton.setVisible(true); + peRandomEveryLevelRadioButton.setEnabled(false); + peRandomEveryLevelRadioButton.setSelected(false); + peSimilarStrengthCheckBox.setVisible(true); + peSimilarStrengthCheckBox.setEnabled(false); + peSimilarStrengthCheckBox.setSelected(false); + peSameTypingCheckBox.setVisible(true); + peSameTypingCheckBox.setEnabled(false); + peSameTypingCheckBox.setSelected(false); + peLimitEvolutionsToThreeCheckBox.setVisible(true); + peLimitEvolutionsToThreeCheckBox.setEnabled(false); + peLimitEvolutionsToThreeCheckBox.setSelected(false); + peForceChangeCheckBox.setVisible(true); + peForceChangeCheckBox.setEnabled(false); + peForceChangeCheckBox.setSelected(false); + peChangeImpossibleEvosCheckBox.setVisible(true); + peChangeImpossibleEvosCheckBox.setEnabled(false); + peChangeImpossibleEvosCheckBox.setSelected(false); + peMakeEvolutionsEasierCheckBox.setVisible(true); + peMakeEvolutionsEasierCheckBox.setEnabled(false); + peMakeEvolutionsEasierCheckBox.setSelected(false); + peRemoveTimeBasedEvolutionsCheckBox.setVisible(true); + peRemoveTimeBasedEvolutionsCheckBox.setEnabled(false); + peRemoveTimeBasedEvolutionsCheckBox.setSelected(false); + peAllowAltFormesCheckBox.setVisible(true); + peAllowAltFormesCheckBox.setEnabled(false); + peAllowAltFormesCheckBox.setSelected(false); + spUnchangedRadioButton.setVisible(true); + spUnchangedRadioButton.setEnabled(false); + spUnchangedRadioButton.setSelected(false); + spCustomRadioButton.setVisible(true); + spCustomRadioButton.setEnabled(false); + spCustomRadioButton.setSelected(false); + spRandomCompletelyRadioButton.setVisible(true); + spRandomCompletelyRadioButton.setEnabled(false); + spRandomCompletelyRadioButton.setSelected(false); + spRandomTwoEvosRadioButton.setVisible(true); + spRandomTwoEvosRadioButton.setEnabled(false); + spRandomTwoEvosRadioButton.setSelected(false); + spComboBox1.setVisible(true); + spComboBox1.setEnabled(false); + spComboBox1.setSelectedIndex(0); + spComboBox1.setModel(new DefaultComboBoxModel<>(new String[] { "--" })); + spComboBox2.setVisible(true); + spComboBox2.setEnabled(false); + spComboBox2.setSelectedIndex(0); + spComboBox2.setModel(new DefaultComboBoxModel<>(new String[] { "--" })); + spComboBox3.setVisible(true); + spComboBox3.setEnabled(false); + spComboBox3.setSelectedIndex(0); + spComboBox3.setModel(new DefaultComboBoxModel<>(new String[] { "--" })); + spRandomizeStarterHeldItemsCheckBox.setVisible(true); + spRandomizeStarterHeldItemsCheckBox.setEnabled(false); + spRandomizeStarterHeldItemsCheckBox.setSelected(false); + spBanBadItemsCheckBox.setVisible(true); + spBanBadItemsCheckBox.setEnabled(false); + spBanBadItemsCheckBox.setSelected(false); + spAllowAltFormesCheckBox.setVisible(true); + spAllowAltFormesCheckBox.setEnabled(false); + spAllowAltFormesCheckBox.setSelected(false); + stpUnchangedRadioButton.setVisible(true); + stpUnchangedRadioButton.setEnabled(false); + stpUnchangedRadioButton.setSelected(false); + stpSwapLegendariesSwapStandardsRadioButton.setVisible(true); + stpSwapLegendariesSwapStandardsRadioButton.setEnabled(false); + stpSwapLegendariesSwapStandardsRadioButton.setSelected(false); + stpRandomCompletelyRadioButton.setVisible(true); + stpRandomCompletelyRadioButton.setEnabled(false); + stpRandomCompletelyRadioButton.setSelected(false); + stpRandomSimilarStrengthRadioButton.setVisible(true); + stpRandomSimilarStrengthRadioButton.setEnabled(false); + stpRandomSimilarStrengthRadioButton.setSelected(false); + stpPercentageLevelModifierCheckBox.setVisible(true); + stpPercentageLevelModifierCheckBox.setEnabled(false); + stpPercentageLevelModifierCheckBox.setSelected(false); + stpPercentageLevelModifierSlider.setVisible(true); + stpPercentageLevelModifierSlider.setEnabled(false); + stpPercentageLevelModifierSlider.setValue(0); + stpLimitMainGameLegendariesCheckBox.setVisible(true); + stpLimitMainGameLegendariesCheckBox.setEnabled(false); + stpLimitMainGameLegendariesCheckBox.setSelected(false); + stpRandomize600BSTCheckBox.setVisible(true); + stpRandomize600BSTCheckBox.setEnabled(false); + stpRandomize600BSTCheckBox.setSelected(false); + stpAllowAltFormesCheckBox.setVisible(true); + stpAllowAltFormesCheckBox.setEnabled(false); + stpAllowAltFormesCheckBox.setSelected(false); + stpSwapMegaEvosCheckBox.setVisible(true); + stpSwapMegaEvosCheckBox.setEnabled(false); + stpSwapMegaEvosCheckBox.setSelected(false); + stpFixMusicCheckBox.setVisible(true); + stpFixMusicCheckBox.setEnabled(false); + stpFixMusicCheckBox.setSelected(false); + igtUnchangedRadioButton.setVisible(true); + igtUnchangedRadioButton.setEnabled(false); + igtUnchangedRadioButton.setSelected(false); + igtRandomizeGivenPokemonOnlyRadioButton.setVisible(true); + igtRandomizeGivenPokemonOnlyRadioButton.setEnabled(false); + igtRandomizeGivenPokemonOnlyRadioButton.setSelected(false); + igtRandomizeBothRequestedGivenRadioButton.setVisible(true); + igtRandomizeBothRequestedGivenRadioButton.setEnabled(false); + igtRandomizeBothRequestedGivenRadioButton.setSelected(false); + igtRandomizeNicknamesCheckBox.setVisible(true); + igtRandomizeNicknamesCheckBox.setEnabled(false); + igtRandomizeNicknamesCheckBox.setSelected(false); + igtRandomizeOTsCheckBox.setVisible(true); + igtRandomizeOTsCheckBox.setEnabled(false); + igtRandomizeOTsCheckBox.setSelected(false); + igtRandomizeIVsCheckBox.setVisible(true); + igtRandomizeIVsCheckBox.setEnabled(false); + igtRandomizeIVsCheckBox.setSelected(false); + igtRandomizeItemsCheckBox.setVisible(true); + igtRandomizeItemsCheckBox.setEnabled(false); + igtRandomizeItemsCheckBox.setSelected(false); + mdRandomizeMovePowerCheckBox.setVisible(true); + mdRandomizeMovePowerCheckBox.setEnabled(false); + mdRandomizeMovePowerCheckBox.setSelected(false); + mdRandomizeMoveAccuracyCheckBox.setVisible(true); + mdRandomizeMoveAccuracyCheckBox.setEnabled(false); + mdRandomizeMoveAccuracyCheckBox.setSelected(false); + mdRandomizeMovePPCheckBox.setVisible(true); + mdRandomizeMovePPCheckBox.setEnabled(false); + mdRandomizeMovePPCheckBox.setSelected(false); + mdRandomizeMoveTypesCheckBox.setVisible(true); + mdRandomizeMoveTypesCheckBox.setEnabled(false); + mdRandomizeMoveTypesCheckBox.setSelected(false); + mdRandomizeMoveCategoryCheckBox.setVisible(true); + mdRandomizeMoveCategoryCheckBox.setEnabled(false); + mdRandomizeMoveCategoryCheckBox.setSelected(false); + mdUpdateMovesCheckBox.setVisible(true); + mdUpdateMovesCheckBox.setEnabled(false); + mdUpdateMovesCheckBox.setSelected(false); + mdUpdateComboBox.setVisible(true); + mdUpdateComboBox.setEnabled(false); + mdUpdateComboBox.setSelectedIndex(0); + mdUpdateComboBox.setModel(new DefaultComboBoxModel<>(new String[] { "--" })); + pmsUnchangedRadioButton.setVisible(true); + pmsUnchangedRadioButton.setEnabled(false); + pmsUnchangedRadioButton.setSelected(false); + pmsRandomPreferringSameTypeRadioButton.setVisible(true); + pmsRandomPreferringSameTypeRadioButton.setEnabled(false); + pmsRandomPreferringSameTypeRadioButton.setSelected(false); + pmsRandomCompletelyRadioButton.setVisible(true); + pmsRandomCompletelyRadioButton.setEnabled(false); + pmsRandomCompletelyRadioButton.setSelected(false); + pmsMetronomeOnlyModeRadioButton.setVisible(true); + pmsMetronomeOnlyModeRadioButton.setEnabled(false); + pmsMetronomeOnlyModeRadioButton.setSelected(false); + pmsGuaranteedLevel1MovesCheckBox.setVisible(true); + pmsGuaranteedLevel1MovesCheckBox.setEnabled(false); + pmsGuaranteedLevel1MovesCheckBox.setSelected(false); + pmsReorderDamagingMovesCheckBox.setVisible(true); + pmsReorderDamagingMovesCheckBox.setEnabled(false); + pmsReorderDamagingMovesCheckBox.setSelected(false); + pmsNoGameBreakingMovesCheckBox.setVisible(true); + pmsNoGameBreakingMovesCheckBox.setEnabled(false); + pmsNoGameBreakingMovesCheckBox.setSelected(false); + pmsForceGoodDamagingCheckBox.setVisible(true); + pmsForceGoodDamagingCheckBox.setEnabled(false); + pmsForceGoodDamagingCheckBox.setSelected(false); + pmsGuaranteedLevel1MovesSlider.setVisible(true); + pmsGuaranteedLevel1MovesSlider.setEnabled(false); + pmsGuaranteedLevel1MovesSlider.setValue(pmsGuaranteedLevel1MovesSlider.getMinimum()); + pmsForceGoodDamagingSlider.setVisible(true); + pmsForceGoodDamagingSlider.setEnabled(false); + pmsForceGoodDamagingSlider.setValue(pmsForceGoodDamagingSlider.getMinimum()); + pmsEvolutionMovesCheckBox.setVisible(true); + pmsEvolutionMovesCheckBox.setEnabled(false); + pmsEvolutionMovesCheckBox.setSelected(false); + tpComboBox.setVisible(true); + tpComboBox.setEnabled(false); + tpComboBox.setModel(new DefaultComboBoxModel<>(new String[] { "Unchanged" })); + tpRivalCarriesStarterCheckBox.setVisible(true); + tpRivalCarriesStarterCheckBox.setEnabled(false); + tpRivalCarriesStarterCheckBox.setSelected(false); + tpSimilarStrengthCheckBox.setVisible(true); + tpSimilarStrengthCheckBox.setEnabled(false); + tpSimilarStrengthCheckBox.setSelected(false); + tpWeightTypesCheckBox.setVisible(true); + tpWeightTypesCheckBox.setEnabled(false); + tpWeightTypesCheckBox.setSelected(false); + tpDontUseLegendariesCheckBox.setVisible(true); + tpDontUseLegendariesCheckBox.setEnabled(false); + tpDontUseLegendariesCheckBox.setSelected(false); + tpNoEarlyWonderGuardCheckBox.setVisible(true); + tpNoEarlyWonderGuardCheckBox.setEnabled(false); + tpNoEarlyWonderGuardCheckBox.setSelected(false); + tpRandomizeTrainerNamesCheckBox.setVisible(true); + tpRandomizeTrainerNamesCheckBox.setEnabled(false); + tpRandomizeTrainerNamesCheckBox.setSelected(false); + tpRandomizeTrainerClassNamesCheckBox.setVisible(true); + tpRandomizeTrainerClassNamesCheckBox.setEnabled(false); + tpRandomizeTrainerClassNamesCheckBox.setSelected(false); + tpForceFullyEvolvedAtCheckBox.setVisible(true); + tpForceFullyEvolvedAtCheckBox.setEnabled(false); + tpForceFullyEvolvedAtCheckBox.setSelected(false); + tpForceFullyEvolvedAtSlider.setVisible(true); + tpForceFullyEvolvedAtSlider.setEnabled(false); + tpForceFullyEvolvedAtSlider.setValue(tpForceFullyEvolvedAtSlider.getMinimum()); + tpPercentageLevelModifierSlider.setVisible(true); + tpPercentageLevelModifierSlider.setEnabled(false); + tpPercentageLevelModifierSlider.setValue(0); + tpPercentageLevelModifierCheckBox.setVisible(true); + tpPercentageLevelModifierCheckBox.setEnabled(false); + tpPercentageLevelModifierCheckBox.setSelected(false); + + tpEliteFourUniquePokemonCheckBox.setVisible(true); + tpEliteFourUniquePokemonCheckBox.setEnabled(false); + tpEliteFourUniquePokemonCheckBox.setSelected(false); + tpEliteFourUniquePokemonSpinner.setVisible(true); + tpEliteFourUniquePokemonSpinner.setEnabled(false); + tpEliteFourUniquePokemonSpinner.setValue(1); + + tpAllowAlternateFormesCheckBox.setVisible(true); + tpAllowAlternateFormesCheckBox.setEnabled(false); + tpAllowAlternateFormesCheckBox.setSelected(false); + tpSwapMegaEvosCheckBox.setVisible(true); + tpSwapMegaEvosCheckBox.setEnabled(false); + tpSwapMegaEvosCheckBox.setSelected(false); + tpDoubleBattleModeCheckBox.setVisible(true); + tpDoubleBattleModeCheckBox.setEnabled(false); + tpDoubleBattleModeCheckBox.setSelected(false); + tpBossTrainersCheckBox.setVisible(true); + tpBossTrainersCheckBox.setEnabled(false); + tpBossTrainersCheckBox.setSelected(false); + tpImportantTrainersCheckBox.setVisible(true); + tpImportantTrainersCheckBox.setEnabled(false); + tpImportantTrainersCheckBox.setSelected(false); + tpRegularTrainersCheckBox.setVisible(true); + tpRegularTrainersCheckBox.setEnabled(false); + tpRegularTrainersCheckBox.setSelected(false); + tpBossTrainersSpinner.setVisible(true); + tpBossTrainersSpinner.setEnabled(false); + tpBossTrainersSpinner.setValue(1); + tpImportantTrainersSpinner.setVisible(true); + tpImportantTrainersSpinner.setEnabled(false); + tpImportantTrainersSpinner.setValue(1); + tpRegularTrainersSpinner.setVisible(true); + tpRegularTrainersSpinner.setEnabled(false); + tpRegularTrainersSpinner.setValue(1); + tpAdditionalPokemonForLabel.setVisible(true); + tpHeldItemsLabel.setVisible(true); + tpBossTrainersItemsCheckBox.setVisible(true); + tpBossTrainersItemsCheckBox.setEnabled(false); + tpBossTrainersItemsCheckBox.setSelected(false); + tpImportantTrainersItemsCheckBox.setVisible(true); + tpImportantTrainersItemsCheckBox.setEnabled(false); + tpImportantTrainersItemsCheckBox.setSelected(false); + tpRegularTrainersItemsCheckBox.setVisible(true); + tpRegularTrainersItemsCheckBox.setEnabled(false); + tpRegularTrainersItemsCheckBox.setSelected(false); + tpConsumableItemsOnlyCheckBox.setVisible(true); + tpConsumableItemsOnlyCheckBox.setEnabled(false); + tpConsumableItemsOnlyCheckBox.setSelected(false); + tpSensibleItemsCheckBox.setVisible(true); + tpSensibleItemsCheckBox.setEnabled(false); + tpSensibleItemsCheckBox.setSelected(false); + tpHighestLevelGetsItemCheckBox.setVisible(true); + tpHighestLevelGetsItemCheckBox.setEnabled(false); + tpHighestLevelGetsItemCheckBox.setSelected(false); + tpRandomShinyTrainerPokemonCheckBox.setVisible(true); + tpRandomShinyTrainerPokemonCheckBox.setEnabled(false); + tpBetterMovesetsCheckBox.setVisible(true); + tpBetterMovesetsCheckBox.setEnabled(false); + tpBetterMovesetsCheckBox.setSelected(false); + totpPanel.setVisible(true); + totpAllyPanel.setVisible(true); + totpAuraPanel.setVisible(true); + totpUnchangedRadioButton.setVisible(true); + totpUnchangedRadioButton.setEnabled(false); + totpUnchangedRadioButton.setSelected(true); + totpRandomRadioButton.setVisible(true); + totpRandomRadioButton.setEnabled(false); + totpRandomRadioButton.setSelected(false); + totpRandomSimilarStrengthRadioButton.setVisible(true); + totpRandomSimilarStrengthRadioButton.setEnabled(false); + totpRandomSimilarStrengthRadioButton.setSelected(false); + totpAllyUnchangedRadioButton.setVisible(true); + totpAllyUnchangedRadioButton.setEnabled(false); + totpAllyUnchangedRadioButton.setSelected(true); + totpAllyRandomRadioButton.setVisible(true); + totpAllyRandomRadioButton.setEnabled(false); + totpAllyRandomRadioButton.setSelected(false); + totpAllyRandomSimilarStrengthRadioButton.setVisible(true); + totpAllyRandomSimilarStrengthRadioButton.setEnabled(false); + totpAllyRandomSimilarStrengthRadioButton.setSelected(false); + totpAuraUnchangedRadioButton.setVisible(true); + totpAuraUnchangedRadioButton.setEnabled(false); + totpAuraUnchangedRadioButton.setSelected(true); + totpAuraRandomRadioButton.setVisible(true); + totpAuraRandomRadioButton.setEnabled(false); + totpAuraRandomRadioButton.setSelected(false); + totpAuraRandomSameStrengthRadioButton.setVisible(true); + totpAuraRandomSameStrengthRadioButton.setEnabled(false); + totpAuraRandomSameStrengthRadioButton.setSelected(false); + totpPercentageLevelModifierCheckBox.setVisible(true); + totpPercentageLevelModifierCheckBox.setEnabled(false); + totpPercentageLevelModifierCheckBox.setSelected(false); + totpPercentageLevelModifierSlider.setVisible(true); + totpPercentageLevelModifierSlider.setEnabled(false); + totpPercentageLevelModifierSlider.setValue(0); + totpRandomizeHeldItemsCheckBox.setVisible(true); + totpRandomizeHeldItemsCheckBox.setEnabled(false); + totpRandomizeHeldItemsCheckBox.setSelected(false); + totpAllowAltFormesCheckBox.setVisible(true); + totpAllowAltFormesCheckBox.setEnabled(false); + totpAllowAltFormesCheckBox.setSelected(false); + wpUnchangedRadioButton.setVisible(true); + wpUnchangedRadioButton.setEnabled(false); + wpUnchangedRadioButton.setSelected(false); + wpRandomRadioButton.setVisible(true); + wpRandomRadioButton.setEnabled(false); + wpRandomRadioButton.setSelected(false); + wpArea1To1RadioButton.setVisible(true); + wpArea1To1RadioButton.setEnabled(false); + wpArea1To1RadioButton.setSelected(false); + wpGlobal1To1RadioButton.setVisible(true); + wpGlobal1To1RadioButton.setEnabled(false); + wpGlobal1To1RadioButton.setSelected(false); + wpARNoneRadioButton.setVisible(true); + wpARNoneRadioButton.setEnabled(false); + wpARNoneRadioButton.setSelected(false); + wpARSimilarStrengthRadioButton.setVisible(true); + wpARSimilarStrengthRadioButton.setEnabled(false); + wpARSimilarStrengthRadioButton.setSelected(false); + wpARCatchEmAllModeRadioButton.setVisible(true); + wpARCatchEmAllModeRadioButton.setEnabled(false); + wpARCatchEmAllModeRadioButton.setSelected(false); + wpARTypeThemeAreasRadioButton.setVisible(true); + wpARTypeThemeAreasRadioButton.setEnabled(false); + wpARTypeThemeAreasRadioButton.setSelected(false); + wpUseTimeBasedEncountersCheckBox.setVisible(true); + wpUseTimeBasedEncountersCheckBox.setEnabled(false); + wpUseTimeBasedEncountersCheckBox.setSelected(false); + wpDontUseLegendariesCheckBox.setVisible(true); + wpDontUseLegendariesCheckBox.setEnabled(false); + wpDontUseLegendariesCheckBox.setSelected(false); + wpSetMinimumCatchRateCheckBox.setVisible(true); + wpSetMinimumCatchRateCheckBox.setEnabled(false); + wpSetMinimumCatchRateCheckBox.setSelected(false); + wpRandomizeHeldItemsCheckBox.setVisible(true); + wpRandomizeHeldItemsCheckBox.setEnabled(false); + wpRandomizeHeldItemsCheckBox.setSelected(false); + wpBanBadItemsCheckBox.setVisible(true); + wpBanBadItemsCheckBox.setEnabled(false); + wpBanBadItemsCheckBox.setSelected(false); + wpBalanceShakingGrassPokemonCheckBox.setVisible(true); + wpBalanceShakingGrassPokemonCheckBox.setEnabled(false); + wpBalanceShakingGrassPokemonCheckBox.setSelected(false); + wpPercentageLevelModifierCheckBox.setVisible(true); + wpPercentageLevelModifierCheckBox.setEnabled(false); + wpPercentageLevelModifierCheckBox.setSelected(false); + wpPercentageLevelModifierSlider.setVisible(true); + wpPercentageLevelModifierSlider.setEnabled(false); + wpPercentageLevelModifierSlider.setValue(0); + wpSetMinimumCatchRateSlider.setVisible(true); + wpSetMinimumCatchRateSlider.setEnabled(false); + wpSetMinimumCatchRateSlider.setValue(wpSetMinimumCatchRateSlider.getMinimum()); + wpAllowAltFormesCheckBox.setVisible(true); + wpAllowAltFormesCheckBox.setEnabled(false); + wpAllowAltFormesCheckBox.setSelected(false); + tmUnchangedRadioButton.setVisible(true); + tmUnchangedRadioButton.setEnabled(false); + tmUnchangedRadioButton.setSelected(false); + tmRandomRadioButton.setVisible(true); + tmRandomRadioButton.setEnabled(false); + tmRandomRadioButton.setSelected(false); + tmNoGameBreakingMovesCheckBox.setVisible(true); + tmNoGameBreakingMovesCheckBox.setEnabled(false); + tmNoGameBreakingMovesCheckBox.setSelected(false); + tmFullHMCompatibilityCheckBox.setVisible(true); + tmFullHMCompatibilityCheckBox.setEnabled(false); + tmFullHMCompatibilityCheckBox.setSelected(false); + tmLevelupMoveSanityCheckBox.setVisible(true); + tmLevelupMoveSanityCheckBox.setEnabled(false); + tmLevelupMoveSanityCheckBox.setSelected(false); + tmKeepFieldMoveTMsCheckBox.setVisible(true); + tmKeepFieldMoveTMsCheckBox.setEnabled(false); + tmKeepFieldMoveTMsCheckBox.setSelected(false); + tmForceGoodDamagingCheckBox.setVisible(true); + tmForceGoodDamagingCheckBox.setEnabled(false); + tmForceGoodDamagingCheckBox.setSelected(false); + tmForceGoodDamagingSlider.setVisible(true); + tmForceGoodDamagingSlider.setEnabled(false); + tmForceGoodDamagingSlider.setValue(tmForceGoodDamagingSlider.getMinimum()); + tmFollowEvolutionsCheckBox.setVisible(true); + tmFollowEvolutionsCheckBox.setEnabled(false); + tmFollowEvolutionsCheckBox.setSelected(false); + thcUnchangedRadioButton.setVisible(true); + thcUnchangedRadioButton.setEnabled(false); + thcUnchangedRadioButton.setSelected(false); + thcRandomPreferSameTypeRadioButton.setVisible(true); + thcRandomPreferSameTypeRadioButton.setEnabled(false); + thcRandomPreferSameTypeRadioButton.setSelected(false); + thcRandomCompletelyRadioButton.setVisible(true); + thcRandomCompletelyRadioButton.setEnabled(false); + thcRandomCompletelyRadioButton.setSelected(false); + thcFullCompatibilityRadioButton.setVisible(true); + thcFullCompatibilityRadioButton.setEnabled(false); + thcFullCompatibilityRadioButton.setSelected(false); + mtUnchangedRadioButton.setVisible(true); + mtUnchangedRadioButton.setEnabled(false); + mtUnchangedRadioButton.setSelected(false); + mtRandomRadioButton.setVisible(true); + mtRandomRadioButton.setEnabled(false); + mtRandomRadioButton.setSelected(false); + mtNoGameBreakingMovesCheckBox.setVisible(true); + mtNoGameBreakingMovesCheckBox.setEnabled(false); + mtNoGameBreakingMovesCheckBox.setSelected(false); + mtLevelupMoveSanityCheckBox.setVisible(true); + mtLevelupMoveSanityCheckBox.setEnabled(false); + mtLevelupMoveSanityCheckBox.setSelected(false); + mtKeepFieldMoveTutorsCheckBox.setVisible(true); + mtKeepFieldMoveTutorsCheckBox.setEnabled(false); + mtKeepFieldMoveTutorsCheckBox.setSelected(false); + mtForceGoodDamagingCheckBox.setVisible(true); + mtForceGoodDamagingCheckBox.setEnabled(false); + mtForceGoodDamagingCheckBox.setSelected(false); + mtForceGoodDamagingSlider.setVisible(true); + mtForceGoodDamagingSlider.setEnabled(false); + mtForceGoodDamagingSlider.setValue(mtForceGoodDamagingSlider.getMinimum()); + mtFollowEvolutionsCheckBox.setVisible(true); + mtFollowEvolutionsCheckBox.setEnabled(false); + mtFollowEvolutionsCheckBox.setSelected(false); + mtcUnchangedRadioButton.setVisible(true); + mtcUnchangedRadioButton.setEnabled(false); + mtcUnchangedRadioButton.setSelected(false); + mtcRandomPreferSameTypeRadioButton.setVisible(true); + mtcRandomPreferSameTypeRadioButton.setEnabled(false); + mtcRandomPreferSameTypeRadioButton.setSelected(false); + mtcRandomCompletelyRadioButton.setVisible(true); + mtcRandomCompletelyRadioButton.setEnabled(false); + mtcRandomCompletelyRadioButton.setSelected(false); + mtcFullCompatibilityRadioButton.setVisible(true); + mtcFullCompatibilityRadioButton.setEnabled(false); + mtcFullCompatibilityRadioButton.setSelected(false); + fiUnchangedRadioButton.setVisible(true); + fiUnchangedRadioButton.setEnabled(false); + fiUnchangedRadioButton.setSelected(false); + fiShuffleRadioButton.setVisible(true); + fiShuffleRadioButton.setEnabled(false); + fiShuffleRadioButton.setSelected(false); + fiRandomRadioButton.setVisible(true); + fiRandomRadioButton.setEnabled(false); + fiRandomRadioButton.setSelected(false); + fiRandomEvenDistributionRadioButton.setVisible(true); + fiRandomEvenDistributionRadioButton.setEnabled(false); + fiRandomEvenDistributionRadioButton.setSelected(false); + fiBanBadItemsCheckBox.setVisible(true); + fiBanBadItemsCheckBox.setEnabled(false); + fiBanBadItemsCheckBox.setSelected(false); + shUnchangedRadioButton.setVisible(true); + shUnchangedRadioButton.setEnabled(false); + shUnchangedRadioButton.setSelected(false); + shShuffleRadioButton.setVisible(true); + shShuffleRadioButton.setEnabled(false); + shShuffleRadioButton.setSelected(false); + shRandomRadioButton.setVisible(true); + shRandomRadioButton.setEnabled(false); + shRandomRadioButton.setSelected(false); + shBanOverpoweredShopItemsCheckBox.setVisible(true); + shBanOverpoweredShopItemsCheckBox.setEnabled(false); + shBanOverpoweredShopItemsCheckBox.setSelected(false); + shBanBadItemsCheckBox.setVisible(true); + shBanBadItemsCheckBox.setEnabled(false); + shBanBadItemsCheckBox.setSelected(false); + shBanRegularShopItemsCheckBox.setVisible(true); + shBanRegularShopItemsCheckBox.setEnabled(false); + shBanRegularShopItemsCheckBox.setSelected(false); + shBalanceShopItemPricesCheckBox.setVisible(true); + shBalanceShopItemPricesCheckBox.setEnabled(false); + shBalanceShopItemPricesCheckBox.setSelected(false); + shGuaranteeEvolutionItemsCheckBox.setVisible(true); + shGuaranteeEvolutionItemsCheckBox.setEnabled(false); + shGuaranteeEvolutionItemsCheckBox.setSelected(false); + shGuaranteeXItemsCheckBox.setVisible(true); + shGuaranteeXItemsCheckBox.setEnabled(false); + shGuaranteeXItemsCheckBox.setSelected(false); + puUnchangedRadioButton.setVisible(true); + puUnchangedRadioButton.setEnabled(false); + puUnchangedRadioButton.setSelected(false); + puRandomRadioButton.setVisible(true); + puRandomRadioButton.setEnabled(false); + puRandomRadioButton.setSelected(false); + puBanBadItemsCheckBox.setVisible(true); + puBanBadItemsCheckBox.setEnabled(false); + puBanBadItemsCheckBox.setSelected(false); + miscBWExpPatchCheckBox.setVisible(true); + miscBWExpPatchCheckBox.setEnabled(false); + miscBWExpPatchCheckBox.setSelected(false); + miscNerfXAccuracyCheckBox.setVisible(true); + miscNerfXAccuracyCheckBox.setEnabled(false); + miscNerfXAccuracyCheckBox.setSelected(false); + miscFixCritRateCheckBox.setVisible(true); + miscFixCritRateCheckBox.setEnabled(false); + miscFixCritRateCheckBox.setSelected(false); + miscFastestTextCheckBox.setVisible(true); + miscFastestTextCheckBox.setEnabled(false); + miscFastestTextCheckBox.setSelected(false); + miscRunningShoesIndoorsCheckBox.setVisible(true); + miscRunningShoesIndoorsCheckBox.setEnabled(false); + miscRunningShoesIndoorsCheckBox.setSelected(false); + miscRandomizePCPotionCheckBox.setVisible(true); + miscRandomizePCPotionCheckBox.setEnabled(false); + miscRandomizePCPotionCheckBox.setSelected(false); + miscAllowPikachuEvolutionCheckBox.setVisible(true); + miscAllowPikachuEvolutionCheckBox.setEnabled(false); + miscAllowPikachuEvolutionCheckBox.setSelected(false); + miscGiveNationalDexAtCheckBox.setVisible(true); + miscGiveNationalDexAtCheckBox.setEnabled(false); + miscGiveNationalDexAtCheckBox.setSelected(false); + miscUpdateTypeEffectivenessCheckBox.setVisible(true); + miscUpdateTypeEffectivenessCheckBox.setEnabled(false); + miscUpdateTypeEffectivenessCheckBox.setSelected(false); + miscLowerCasePokemonNamesCheckBox.setVisible(true); + miscLowerCasePokemonNamesCheckBox.setEnabled(false); + miscLowerCasePokemonNamesCheckBox.setSelected(false); + miscRandomizeCatchingTutorialCheckBox.setVisible(true); + miscRandomizeCatchingTutorialCheckBox.setEnabled(false); + miscRandomizeCatchingTutorialCheckBox.setSelected(false); + miscBanLuckyEggCheckBox.setVisible(true); + miscBanLuckyEggCheckBox.setEnabled(false); + miscBanLuckyEggCheckBox.setSelected(false); + miscNoFreeLuckyEggCheckBox.setVisible(true); + miscNoFreeLuckyEggCheckBox.setEnabled(false); + miscNoFreeLuckyEggCheckBox.setSelected(false); + miscBanBigMoneyManiacCheckBox.setVisible(true); + miscBanBigMoneyManiacCheckBox.setEnabled(false); + miscBanBigMoneyManiacCheckBox.setSelected(false); + mtNoExistLabel.setVisible(false); + mtNoneAvailableLabel.setVisible(false); + + liveTweaksPanel.setVisible(false); + miscTweaksPanel.setVisible(true); + } + + private void romLoaded() { + + try { + int pokemonGeneration = romHandler.generationOfPokemon(); + + setRomNameLabel(); + romCodeLabel.setText(romHandler.getROMCode()); + romSupportLabel.setText(bundle.getString("GUI.romSupportPrefix") + " " + + this.romHandler.getSupportLevel()); + + if (!romHandler.isRomValid()) { + romNameLabel.setForeground(Color.RED); + romCodeLabel.setForeground(Color.RED); + romSupportLabel.setForeground(Color.RED); + romSupportLabel.setText("" + bundle.getString("GUI.romSupportPrefix") + " Unofficial ROM"); + showInvalidRomPopup(); + } else { + romNameLabel.setForeground(Color.BLACK); + romCodeLabel.setForeground(Color.BLACK); + romSupportLabel.setForeground(Color.BLACK); + } + + limitPokemonCheckBox.setVisible(true); + limitPokemonCheckBox.setEnabled(true); + limitPokemonButton.setVisible(true); + + noIrregularAltFormesCheckBox.setVisible(pokemonGeneration >= 4); + noIrregularAltFormesCheckBox.setEnabled(pokemonGeneration >= 4); + + raceModeCheckBox.setEnabled(true); + + loadSettingsButton.setEnabled(true); + saveSettingsButton.setEnabled(true); + + // Pokemon Traits + + // Pokemon Base Statistics + pbsUnchangedRadioButton.setEnabled(true); + pbsUnchangedRadioButton.setSelected(true); + pbsShuffleRadioButton.setEnabled(true); + pbsRandomRadioButton.setEnabled(true); + + pbsStandardizeEXPCurvesCheckBox.setEnabled(true); + pbsLegendariesSlowRadioButton.setSelected(true); + pbsUpdateBaseStatsCheckBox.setEnabled(pokemonGeneration < GlobalConstants.HIGHEST_POKEMON_GEN); + pbsFollowMegaEvosCheckBox.setVisible(romHandler.hasMegaEvolutions()); + pbsUpdateComboBox.setVisible(pokemonGeneration < 8); + ExpCurve[] expCurves = getEXPCurvesForGeneration(pokemonGeneration); + String[] expCurveNames = new String[expCurves.length]; + for (int i = 0; i < expCurves.length; i++) { + expCurveNames[i] = expCurves[i].toString(); + } + pbsEXPCurveComboBox.setModel(new DefaultComboBoxModel<>(expCurveNames)); + pbsEXPCurveComboBox.setSelectedIndex(0); + + // Pokemon Types + ptUnchangedRadioButton.setEnabled(true); + ptUnchangedRadioButton.setSelected(true); + ptRandomFollowEvolutionsRadioButton.setEnabled(true); + ptRandomCompletelyRadioButton.setEnabled(true); + ptFollowMegaEvosCheckBox.setVisible(romHandler.hasMegaEvolutions()); + ptIsDualTypeCheckBox.setEnabled(false); + + // Pokemon Abilities + if (pokemonGeneration >= 3) { + paUnchangedRadioButton.setEnabled(true); + paUnchangedRadioButton.setSelected(true); + paRandomRadioButton.setEnabled(true); + + paAllowWonderGuardCheckBox.setEnabled(false); + paFollowEvolutionsCheckBox.setEnabled(false); + paTrappingAbilitiesCheckBox.setEnabled(false); + paNegativeAbilitiesCheckBox.setEnabled(false); + paBadAbilitiesCheckBox.setEnabled(false); + paFollowMegaEvosCheckBox.setVisible(romHandler.hasMegaEvolutions()); + paWeighDuplicatesTogetherCheckBox.setEnabled(false); + paEnsureTwoAbilitiesCheckbox.setEnabled(false); + } else { + pokemonAbilitiesPanel.setVisible(false); + } + + // Pokemon Evolutions + peUnchangedRadioButton.setEnabled(true); + peUnchangedRadioButton.setSelected(true); + peRandomRadioButton.setEnabled(true); + peRandomEveryLevelRadioButton.setVisible(pokemonGeneration >= 3); + peRandomEveryLevelRadioButton.setEnabled(pokemonGeneration >= 3); + peChangeImpossibleEvosCheckBox.setEnabled(true); + peMakeEvolutionsEasierCheckBox.setEnabled(true); + peRemoveTimeBasedEvolutionsCheckBox.setEnabled(true); + peAllowAltFormesCheckBox.setVisible(pokemonGeneration >= 7); + + // Starters, Statics & Trades + + // Starter Pokemon + spUnchangedRadioButton.setEnabled(true); + spUnchangedRadioButton.setSelected(true); + + spCustomRadioButton.setEnabled(true); + spRandomCompletelyRadioButton.setEnabled(true); + spRandomTwoEvosRadioButton.setEnabled(true); + spAllowAltFormesCheckBox.setVisible(romHandler.hasStarterAltFormes()); + if (romHandler.isYellow()) { + spComboBox3.setVisible(false); + } + populateDropdowns(); + + boolean supportsStarterHeldItems = romHandler.supportsStarterHeldItems(); + spRandomizeStarterHeldItemsCheckBox.setEnabled(supportsStarterHeldItems); + spRandomizeStarterHeldItemsCheckBox.setVisible(supportsStarterHeldItems); + spBanBadItemsCheckBox.setEnabled(false); + spBanBadItemsCheckBox.setVisible(supportsStarterHeldItems); + + stpUnchangedRadioButton.setEnabled(true); + stpUnchangedRadioButton.setSelected(true); + if (romHandler.canChangeStaticPokemon()) { + stpSwapLegendariesSwapStandardsRadioButton.setEnabled(true); + stpRandomCompletelyRadioButton.setEnabled(true); + stpRandomSimilarStrengthRadioButton.setEnabled(true); + stpLimitMainGameLegendariesCheckBox.setVisible(romHandler.hasMainGameLegendaries()); + stpLimitMainGameLegendariesCheckBox.setEnabled(false); + stpAllowAltFormesCheckBox.setVisible(romHandler.hasStaticAltFormes()); + stpSwapMegaEvosCheckBox.setVisible(pokemonGeneration == 6 && !romHandler.forceSwapStaticMegaEvos()); + stpPercentageLevelModifierCheckBox.setVisible(true); + stpPercentageLevelModifierCheckBox.setEnabled(true); + stpPercentageLevelModifierSlider.setVisible(true); + stpPercentageLevelModifierSlider.setEnabled(false); + stpFixMusicCheckBox.setVisible(romHandler.hasStaticMusicFix()); + stpFixMusicCheckBox.setEnabled(false); + } else { + stpSwapLegendariesSwapStandardsRadioButton.setVisible(false); + stpRandomCompletelyRadioButton.setVisible(false); + stpRandomSimilarStrengthRadioButton.setVisible(false); + stpRandomize600BSTCheckBox.setVisible(false); + stpLimitMainGameLegendariesCheckBox.setVisible(false); + stpPercentageLevelModifierCheckBox.setVisible(false); + stpPercentageLevelModifierSlider.setVisible(false); + stpFixMusicCheckBox.setVisible(false); + } + + igtUnchangedRadioButton.setEnabled(true); + igtUnchangedRadioButton.setSelected(true); + igtRandomizeGivenPokemonOnlyRadioButton.setEnabled(true); + igtRandomizeBothRequestedGivenRadioButton.setEnabled(true); + + igtRandomizeNicknamesCheckBox.setEnabled(false); + igtRandomizeOTsCheckBox.setEnabled(false); + igtRandomizeIVsCheckBox.setEnabled(false); + igtRandomizeItemsCheckBox.setEnabled(false); + + if (pokemonGeneration == 1) { + igtRandomizeOTsCheckBox.setVisible(false); + igtRandomizeIVsCheckBox.setVisible(false); + igtRandomizeItemsCheckBox.setVisible(false); + } + + // Move Data + mdRandomizeMovePowerCheckBox.setEnabled(true); + mdRandomizeMoveAccuracyCheckBox.setEnabled(true); + mdRandomizeMovePPCheckBox.setEnabled(true); + mdRandomizeMoveTypesCheckBox.setEnabled(true); + mdRandomizeMoveCategoryCheckBox.setEnabled(romHandler.hasPhysicalSpecialSplit()); + mdRandomizeMoveCategoryCheckBox.setVisible(romHandler.hasPhysicalSpecialSplit()); + mdUpdateMovesCheckBox.setEnabled(pokemonGeneration < 8); + mdUpdateMovesCheckBox.setVisible(pokemonGeneration < 8); + + // Pokemon Movesets + pmsUnchangedRadioButton.setEnabled(true); + pmsUnchangedRadioButton.setSelected(true); + pmsRandomPreferringSameTypeRadioButton.setEnabled(true); + pmsRandomCompletelyRadioButton.setEnabled(true); + pmsMetronomeOnlyModeRadioButton.setEnabled(true); + + pmsGuaranteedLevel1MovesCheckBox.setVisible(romHandler.supportsFourStartingMoves()); + pmsGuaranteedLevel1MovesSlider.setVisible(romHandler.supportsFourStartingMoves()); + pmsEvolutionMovesCheckBox.setVisible(pokemonGeneration >= 7); + + tpComboBox.setEnabled(true); + tpAllowAlternateFormesCheckBox.setVisible(romHandler.hasFunctionalFormes()); + tpForceFullyEvolvedAtCheckBox.setEnabled(true); + tpPercentageLevelModifierCheckBox.setEnabled(true); + tpSwapMegaEvosCheckBox.setVisible(romHandler.hasMegaEvolutions()); + tpDoubleBattleModeCheckBox.setVisible(pokemonGeneration >= 3); + + boolean additionalPokemonAvailable = pokemonGeneration >= 3; + + tpAdditionalPokemonForLabel.setVisible(additionalPokemonAvailable); + tpBossTrainersCheckBox.setVisible(additionalPokemonAvailable); + tpBossTrainersCheckBox.setEnabled(false); + tpBossTrainersSpinner.setVisible(additionalPokemonAvailable); + tpImportantTrainersCheckBox.setVisible(additionalPokemonAvailable); + tpImportantTrainersCheckBox.setEnabled(false); + tpImportantTrainersSpinner.setVisible(additionalPokemonAvailable); + tpRegularTrainersCheckBox.setVisible(additionalPokemonAvailable); + tpRegularTrainersCheckBox.setEnabled(false); + tpRegularTrainersSpinner.setVisible(additionalPokemonAvailable); + + boolean trainersHeldItemSupport = pokemonGeneration >= 3; + tpHeldItemsLabel.setVisible(trainersHeldItemSupport); + tpBossTrainersItemsCheckBox.setVisible(trainersHeldItemSupport); + tpBossTrainersItemsCheckBox.setEnabled(false); + tpImportantTrainersItemsCheckBox.setVisible(trainersHeldItemSupport); + tpImportantTrainersItemsCheckBox.setEnabled(false); + tpRegularTrainersItemsCheckBox.setVisible(trainersHeldItemSupport); + tpRegularTrainersItemsCheckBox.setEnabled(false); + tpConsumableItemsOnlyCheckBox.setVisible(trainersHeldItemSupport); + tpConsumableItemsOnlyCheckBox.setEnabled(false); + tpSensibleItemsCheckBox.setVisible(trainersHeldItemSupport); + tpSensibleItemsCheckBox.setEnabled(false); + tpHighestLevelGetsItemCheckBox.setVisible(trainersHeldItemSupport); + tpHighestLevelGetsItemCheckBox.setEnabled(false); + + tpEliteFourUniquePokemonCheckBox.setVisible(pokemonGeneration >= 3); + tpEliteFourUniquePokemonSpinner.setVisible(pokemonGeneration >= 3); + + tpRandomizeTrainerNamesCheckBox.setEnabled(true); + tpRandomizeTrainerClassNamesCheckBox.setEnabled(true); + tpNoEarlyWonderGuardCheckBox.setVisible(pokemonGeneration >= 3); + tpRandomShinyTrainerPokemonCheckBox.setVisible(pokemonGeneration >= 7); + tpBetterMovesetsCheckBox.setVisible(pokemonGeneration >= 3); + tpBetterMovesetsCheckBox.setEnabled(pokemonGeneration >= 3); + + totpPanel.setVisible(pokemonGeneration == 7); + if (totpPanel.isVisible()) { + totpUnchangedRadioButton.setEnabled(true); + totpRandomRadioButton.setEnabled(true); + totpRandomSimilarStrengthRadioButton.setEnabled(true); + + totpAllyPanel.setVisible(pokemonGeneration == 7); + totpAllyUnchangedRadioButton.setEnabled(true); + totpAllyRandomRadioButton.setEnabled(true); + totpAllyRandomSimilarStrengthRadioButton.setEnabled(true); + + totpAuraPanel.setVisible(pokemonGeneration == 7); + totpAuraUnchangedRadioButton.setEnabled(true); + totpAuraRandomRadioButton.setEnabled(true); + totpAuraRandomSameStrengthRadioButton.setEnabled(true); + + totpRandomizeHeldItemsCheckBox.setEnabled(true); + totpAllowAltFormesCheckBox.setEnabled(false); + totpPercentageLevelModifierCheckBox.setEnabled(true); + totpPercentageLevelModifierSlider.setEnabled(false); + } + + // Wild Pokemon + wpUnchangedRadioButton.setEnabled(true); + wpUnchangedRadioButton.setSelected(true); + wpRandomRadioButton.setEnabled(true); + wpArea1To1RadioButton.setEnabled(true); + wpGlobal1To1RadioButton.setEnabled(true); + + wpARNoneRadioButton.setSelected(true); + + wpUseTimeBasedEncountersCheckBox.setVisible(romHandler.hasTimeBasedEncounters()); + wpSetMinimumCatchRateCheckBox.setEnabled(true); + wpRandomizeHeldItemsCheckBox.setEnabled(true); + wpRandomizeHeldItemsCheckBox.setVisible(pokemonGeneration != 1); + wpBanBadItemsCheckBox.setVisible(pokemonGeneration != 1); + wpBalanceShakingGrassPokemonCheckBox.setVisible(pokemonGeneration == 5); + wpPercentageLevelModifierCheckBox.setEnabled(true); + wpAllowAltFormesCheckBox.setVisible(romHandler.hasWildAltFormes()); + + tmUnchangedRadioButton.setEnabled(true); + tmUnchangedRadioButton.setSelected(true); + tmRandomRadioButton.setEnabled(true); + tmFullHMCompatibilityCheckBox.setVisible(pokemonGeneration < 7); + if (tmFullHMCompatibilityCheckBox.isVisible()) { + tmFullHMCompatibilityCheckBox.setEnabled(true); + } + + thcUnchangedRadioButton.setEnabled(true); + thcUnchangedRadioButton.setSelected(true); + thcRandomPreferSameTypeRadioButton.setEnabled(true); + thcRandomCompletelyRadioButton.setEnabled(true); + thcFullCompatibilityRadioButton.setEnabled(true); + + if (romHandler.hasMoveTutors()) { + mtMovesPanel.setVisible(true); + mtCompatPanel.setVisible(true); + mtNoExistLabel.setVisible(false); + + mtUnchangedRadioButton.setEnabled(true); + mtUnchangedRadioButton.setSelected(true); + mtRandomRadioButton.setEnabled(true); + + mtcUnchangedRadioButton.setEnabled(true); + mtcUnchangedRadioButton.setSelected(true); + mtcRandomPreferSameTypeRadioButton.setEnabled(true); + mtcRandomCompletelyRadioButton.setEnabled(true); + mtcFullCompatibilityRadioButton.setEnabled(true); + } else { + mtMovesPanel.setVisible(false); + mtCompatPanel.setVisible(false); + mtNoExistLabel.setVisible(true); + } + + fiUnchangedRadioButton.setEnabled(true); + fiUnchangedRadioButton.setSelected(true); + fiShuffleRadioButton.setEnabled(true); + fiRandomRadioButton.setEnabled(true); + fiRandomEvenDistributionRadioButton.setEnabled(true); + + shopItemsPanel.setVisible(romHandler.hasShopRandomization()); + shUnchangedRadioButton.setEnabled(true); + shUnchangedRadioButton.setSelected(true); + shShuffleRadioButton.setEnabled(true); + shRandomRadioButton.setEnabled(true); + + pickupItemsPanel.setVisible(romHandler.abilitiesPerPokemon() > 0); + puUnchangedRadioButton.setEnabled(true); + puUnchangedRadioButton.setSelected(true); + puRandomRadioButton.setEnabled(true); + + int mtsAvailable = romHandler.miscTweaksAvailable(); + int mtCount = MiscTweak.allTweaks.size(); + List usableCheckBoxes = new ArrayList<>(); + + for (int mti = 0; mti < mtCount; mti++) { + MiscTweak mt = MiscTweak.allTweaks.get(mti); + JCheckBox mtCB = tweakCheckBoxes.get(mti); + mtCB.setSelected(false); + if ((mtsAvailable & mt.getValue()) != 0) { + mtCB.setVisible(true); + mtCB.setEnabled(true); + usableCheckBoxes.add(mtCB); + } else { + mtCB.setVisible(false); + mtCB.setEnabled(false); + } + } + + if (usableCheckBoxes.size() > 0) { + setTweaksPanel(usableCheckBoxes); + //tabbedPane1.setComponentAt(7,makeTweaksLayout(usableCheckBoxes)); + //miscTweaksPanel.setLayout(makeTweaksLayout(usableCheckBoxes)); + } else { + mtNoneAvailableLabel.setVisible(true); + liveTweaksPanel.setVisible(false); + miscTweaksPanel.setVisible(true); + //miscTweaksPanel.setLayout(noTweaksLayout); + } + + gameMascotLabel.setIcon(makeMascotIcon()); + + if (romHandler instanceof AbstractDSRomHandler) { + ((AbstractDSRomHandler) romHandler).closeInnerRom(); + } else if (romHandler instanceof Abstract3DSRomHandler) { + ((Abstract3DSRomHandler) romHandler).closeInnerRom(); + } + } catch (Exception e) { + attemptToLogException(e, "GUI.processFailed","GUI.processFailedNoLog", null, null); + romHandler = null; + initialState(); + } + } + + private void setRomNameLabel() { + if (romHandler.hasGameUpdateLoaded()) { + romNameLabel.setText(romHandler.getROMName() + " (" + romHandler.getGameUpdateVersion() + ")"); + } else { + romNameLabel.setText(romHandler.getROMName()); + } + } + + private void setTweaksPanel(List usableCheckBoxes) { + mtNoneAvailableLabel.setVisible(false); + miscTweaksPanel.setVisible(false); + baseTweaksPanel.remove(liveTweaksPanel); + makeTweaksLayout(usableCheckBoxes); + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.weightx = 0.1; + c.weighty = 0.1; + c.gridx = 1; + c.gridy = 1; + baseTweaksPanel.add(liveTweaksPanel,c); + liveTweaksPanel.setVisible(true); + } + + private void enableOrDisableSubControls() { + + if (limitPokemonCheckBox.isSelected()) { + limitPokemonButton.setEnabled(true); + } else { + limitPokemonButton.setEnabled(false); + } + + boolean followEvolutionControlsEnabled = !peRandomEveryLevelRadioButton.isSelected(); + boolean followMegaEvolutionControlsEnabled = !(peRandomEveryLevelRadioButton.isSelected() && !noIrregularAltFormesCheckBox.isSelected() && peAllowAltFormesCheckBox.isSelected()); + + if (peRandomEveryLevelRadioButton.isSelected()) { + // If Evolve Every Level is enabled, unselect all "Follow Evolutions" controls + pbsFollowEvolutionsCheckBox.setSelected(false); + ptRandomFollowEvolutionsRadioButton.setEnabled(false); + if (ptRandomFollowEvolutionsRadioButton.isSelected()) { + ptRandomFollowEvolutionsRadioButton.setSelected(false); + ptRandomCompletelyRadioButton.setSelected(true); + } + spRandomTwoEvosRadioButton.setEnabled(false); + if (spRandomTwoEvosRadioButton.isSelected()) { + spRandomTwoEvosRadioButton.setSelected(false); + spRandomCompletelyRadioButton.setSelected(true); + } + paFollowEvolutionsCheckBox.setSelected(false); + tmFollowEvolutionsCheckBox.setSelected(false); + mtFollowEvolutionsCheckBox.setSelected(false); + + // If the Follow Mega Evolution controls should be disabled, deselect them here too + if (!followMegaEvolutionControlsEnabled) { + pbsFollowMegaEvosCheckBox.setSelected(false); + ptFollowMegaEvosCheckBox.setSelected(false); + paFollowMegaEvosCheckBox.setSelected(false); + } + + // Also disable/unselect all the settings that make evolutions easier/possible, + // since they aren't relevant in this scenario at all. + peChangeImpossibleEvosCheckBox.setEnabled(false); + peChangeImpossibleEvosCheckBox.setSelected(false); + peMakeEvolutionsEasierCheckBox.setEnabled(false); + peMakeEvolutionsEasierCheckBox.setSelected(false); + peRemoveTimeBasedEvolutionsCheckBox.setEnabled(false); + peRemoveTimeBasedEvolutionsCheckBox.setSelected(false); + + // Disable "Force Fully Evolved" Trainer Pokemon + tpForceFullyEvolvedAtCheckBox.setSelected(false); + tpForceFullyEvolvedAtCheckBox.setEnabled(false); + tpForceFullyEvolvedAtSlider.setEnabled(false); + tpForceFullyEvolvedAtSlider.setValue(tpForceFullyEvolvedAtSlider.getMinimum()); + } else { + // All other "Follow Evolutions" controls get properly set/unset below + // except this one, so manually enable it again. + ptRandomFollowEvolutionsRadioButton.setEnabled(true); + spRandomTwoEvosRadioButton.setEnabled(true); + + // The controls that make evolutions easier/possible, however, + // need to all be manually re-enabled. + peChangeImpossibleEvosCheckBox.setEnabled(true); + peMakeEvolutionsEasierCheckBox.setEnabled(true); + peRemoveTimeBasedEvolutionsCheckBox.setEnabled(true); + + // Re-enable "Force Fully Evolved" Trainer Pokemon + tpForceFullyEvolvedAtCheckBox.setEnabled(true); + } + + if (pbsUnchangedRadioButton.isSelected()) { + pbsFollowEvolutionsCheckBox.setEnabled(false); + pbsFollowEvolutionsCheckBox.setSelected(false); + pbsFollowMegaEvosCheckBox.setEnabled(false); + pbsFollowMegaEvosCheckBox.setSelected(false); + } else { + pbsFollowEvolutionsCheckBox.setEnabled(followEvolutionControlsEnabled); + pbsFollowMegaEvosCheckBox.setEnabled(followMegaEvolutionControlsEnabled); + } + + if (pbsRandomRadioButton.isSelected()) { + if (pbsFollowEvolutionsCheckBox.isSelected() || pbsFollowMegaEvosCheckBox.isSelected()) { + pbsAssignEvoStatsRandomlyCheckBox.setEnabled(true); + } else { + pbsAssignEvoStatsRandomlyCheckBox.setEnabled(false); + pbsAssignEvoStatsRandomlyCheckBox.setSelected(false); + } + } else { + pbsAssignEvoStatsRandomlyCheckBox.setEnabled(false); + pbsAssignEvoStatsRandomlyCheckBox.setSelected(false); + } + + if (pbsStandardizeEXPCurvesCheckBox.isSelected()) { + pbsLegendariesSlowRadioButton.setEnabled(true); + pbsStrongLegendariesSlowRadioButton.setEnabled(true); + pbsAllMediumFastRadioButton.setEnabled(true); + pbsEXPCurveComboBox.setEnabled(true); + } else { + pbsLegendariesSlowRadioButton.setEnabled(false); + pbsLegendariesSlowRadioButton.setSelected(true); + pbsStrongLegendariesSlowRadioButton.setEnabled(false); + pbsAllMediumFastRadioButton.setEnabled(false); + pbsEXPCurveComboBox.setEnabled(false); + } + + if (pbsUpdateBaseStatsCheckBox.isSelected()) { + pbsUpdateComboBox.setEnabled(true); + } else { + pbsUpdateComboBox.setEnabled(false); + } + + if (ptUnchangedRadioButton.isSelected()) { + ptFollowMegaEvosCheckBox.setEnabled(false); + ptFollowMegaEvosCheckBox.setSelected(false); + ptIsDualTypeCheckBox.setEnabled(false); + ptIsDualTypeCheckBox.setSelected(false); + } else { + ptFollowMegaEvosCheckBox.setEnabled(followMegaEvolutionControlsEnabled); + ptIsDualTypeCheckBox.setEnabled(true); + } + + if (paRandomRadioButton.isSelected()) { + paAllowWonderGuardCheckBox.setEnabled(true); + paFollowEvolutionsCheckBox.setEnabled(followEvolutionControlsEnabled); + paFollowMegaEvosCheckBox.setEnabled(followMegaEvolutionControlsEnabled); + paTrappingAbilitiesCheckBox.setEnabled(true); + paNegativeAbilitiesCheckBox.setEnabled(true); + paBadAbilitiesCheckBox.setEnabled(true); + paWeighDuplicatesTogetherCheckBox.setEnabled(true); + paEnsureTwoAbilitiesCheckbox.setEnabled(true); + } else { + paAllowWonderGuardCheckBox.setEnabled(false); + paAllowWonderGuardCheckBox.setSelected(false); + paFollowEvolutionsCheckBox.setEnabled(false); + paFollowEvolutionsCheckBox.setSelected(false); + paTrappingAbilitiesCheckBox.setEnabled(false); + paTrappingAbilitiesCheckBox.setSelected(false); + paNegativeAbilitiesCheckBox.setEnabled(false); + paNegativeAbilitiesCheckBox.setSelected(false); + paBadAbilitiesCheckBox.setEnabled(false); + paBadAbilitiesCheckBox.setSelected(false); + paFollowMegaEvosCheckBox.setEnabled(false); + paFollowMegaEvosCheckBox.setSelected(false); + paWeighDuplicatesTogetherCheckBox.setEnabled(false); + paWeighDuplicatesTogetherCheckBox.setSelected(false); + paEnsureTwoAbilitiesCheckbox.setEnabled(false); + paEnsureTwoAbilitiesCheckbox.setSelected(false); + } + + if (peRandomRadioButton.isSelected()) { + peSimilarStrengthCheckBox.setEnabled(true); + peSameTypingCheckBox.setEnabled(true); + peLimitEvolutionsToThreeCheckBox.setEnabled(true); + peForceChangeCheckBox.setEnabled(true); + peAllowAltFormesCheckBox.setEnabled(true); + } else if (peRandomEveryLevelRadioButton.isSelected()) { + peSimilarStrengthCheckBox.setEnabled(false); + peSimilarStrengthCheckBox.setSelected(false); + peSameTypingCheckBox.setEnabled(true); + peLimitEvolutionsToThreeCheckBox.setEnabled(false); + peLimitEvolutionsToThreeCheckBox.setSelected(false); + peForceChangeCheckBox.setEnabled(true); + peAllowAltFormesCheckBox.setEnabled(true); + } else { + peSimilarStrengthCheckBox.setEnabled(false); + peSimilarStrengthCheckBox.setSelected(false); + peSameTypingCheckBox.setEnabled(false); + peSameTypingCheckBox.setSelected(false); + peLimitEvolutionsToThreeCheckBox.setEnabled(false); + peLimitEvolutionsToThreeCheckBox.setSelected(false); + peForceChangeCheckBox.setEnabled(false); + peForceChangeCheckBox.setSelected(false); + peAllowAltFormesCheckBox.setEnabled(false); + peAllowAltFormesCheckBox.setSelected(false); + } + + boolean spCustomStatus = spCustomRadioButton.isSelected(); + spComboBox1.setEnabled(spCustomStatus); + spComboBox2.setEnabled(spCustomStatus); + spComboBox3.setEnabled(spCustomStatus); + + if (spUnchangedRadioButton.isSelected()) { + spAllowAltFormesCheckBox.setEnabled(false); + spAllowAltFormesCheckBox.setSelected(false); + } else { + spAllowAltFormesCheckBox.setEnabled(true); + } + + if (spRandomizeStarterHeldItemsCheckBox.isSelected()) { + spBanBadItemsCheckBox.setEnabled(true); + } else { + spBanBadItemsCheckBox.setEnabled(false); + spBanBadItemsCheckBox.setSelected(false); + } + + if (stpUnchangedRadioButton.isSelected()) { + stpRandomize600BSTCheckBox.setEnabled(false); + stpRandomize600BSTCheckBox.setSelected(false); + stpAllowAltFormesCheckBox.setEnabled(false); + stpAllowAltFormesCheckBox.setSelected(false); + stpSwapMegaEvosCheckBox.setEnabled(false); + stpSwapMegaEvosCheckBox.setSelected(false); + stpFixMusicCheckBox.setEnabled(false); + stpFixMusicCheckBox.setSelected(false); + } else { + stpRandomize600BSTCheckBox.setEnabled(true); + stpAllowAltFormesCheckBox.setEnabled(true); + stpSwapMegaEvosCheckBox.setEnabled(true); + stpFixMusicCheckBox.setEnabled(true); + } + + if (stpRandomSimilarStrengthRadioButton.isSelected()) { + stpLimitMainGameLegendariesCheckBox.setEnabled(stpLimitMainGameLegendariesCheckBox.isVisible()); + } else { + stpLimitMainGameLegendariesCheckBox.setEnabled(false); + stpLimitMainGameLegendariesCheckBox.setSelected(false); + } + + if (stpPercentageLevelModifierCheckBox.isSelected()) { + stpPercentageLevelModifierSlider.setEnabled(true); + } else { + stpPercentageLevelModifierSlider.setEnabled(false); + stpPercentageLevelModifierSlider.setValue(0); + } + + if (igtUnchangedRadioButton.isSelected()) { + igtRandomizeItemsCheckBox.setEnabled(false); + igtRandomizeItemsCheckBox.setSelected(false); + igtRandomizeIVsCheckBox.setEnabled(false); + igtRandomizeIVsCheckBox.setSelected(false); + igtRandomizeNicknamesCheckBox.setEnabled(false); + igtRandomizeNicknamesCheckBox.setSelected(false); + igtRandomizeOTsCheckBox.setEnabled(false); + igtRandomizeOTsCheckBox.setSelected(false); + } else { + igtRandomizeItemsCheckBox.setEnabled(true); + igtRandomizeIVsCheckBox.setEnabled(true); + igtRandomizeNicknamesCheckBox.setEnabled(true); + igtRandomizeOTsCheckBox.setEnabled(true); + } + + if (mdUpdateMovesCheckBox.isSelected()) { + mdUpdateComboBox.setEnabled(true); + } else { + mdUpdateComboBox.setEnabled(false); + } + + if (pmsMetronomeOnlyModeRadioButton.isSelected() || pmsUnchangedRadioButton.isSelected()) { + pmsGuaranteedLevel1MovesCheckBox.setEnabled(false); + pmsGuaranteedLevel1MovesCheckBox.setSelected(false); + pmsForceGoodDamagingCheckBox.setEnabled(false); + pmsForceGoodDamagingCheckBox.setSelected(false); + pmsReorderDamagingMovesCheckBox.setEnabled(false); + pmsReorderDamagingMovesCheckBox.setSelected(false); + pmsNoGameBreakingMovesCheckBox.setEnabled(false); + pmsNoGameBreakingMovesCheckBox.setSelected(false); + pmsEvolutionMovesCheckBox.setEnabled(false); + pmsEvolutionMovesCheckBox.setSelected(false); + } else { + pmsGuaranteedLevel1MovesCheckBox.setEnabled(true); + pmsForceGoodDamagingCheckBox.setEnabled(true); + pmsReorderDamagingMovesCheckBox.setEnabled(true); + pmsNoGameBreakingMovesCheckBox.setEnabled(true); + pmsEvolutionMovesCheckBox.setEnabled(true); + } + + if (pmsGuaranteedLevel1MovesCheckBox.isSelected()) { + pmsGuaranteedLevel1MovesSlider.setEnabled(true); + } else { + pmsGuaranteedLevel1MovesSlider.setEnabled(false); + pmsGuaranteedLevel1MovesSlider.setValue(pmsGuaranteedLevel1MovesSlider.getMinimum()); + } + + if (pmsForceGoodDamagingCheckBox.isSelected()) { + pmsForceGoodDamagingSlider.setEnabled(true); + } else { + pmsForceGoodDamagingSlider.setEnabled(false); + pmsForceGoodDamagingSlider.setValue(pmsForceGoodDamagingSlider.getMinimum()); + } + + if (isTrainerSetting(TRAINER_UNCHANGED)) { + tpSimilarStrengthCheckBox.setEnabled(false); + tpSimilarStrengthCheckBox.setSelected(false); + tpDontUseLegendariesCheckBox.setEnabled(false); + tpDontUseLegendariesCheckBox.setSelected(false); + tpNoEarlyWonderGuardCheckBox.setEnabled(false); + tpNoEarlyWonderGuardCheckBox.setSelected(false); + tpAllowAlternateFormesCheckBox.setEnabled(false); + tpAllowAlternateFormesCheckBox.setSelected(false); + tpSwapMegaEvosCheckBox.setEnabled(false); + tpSwapMegaEvosCheckBox.setSelected(false); + tpRandomShinyTrainerPokemonCheckBox.setEnabled(false); + tpRandomShinyTrainerPokemonCheckBox.setSelected(false); + tpDoubleBattleModeCheckBox.setEnabled(false); + tpDoubleBattleModeCheckBox.setSelected(false); + tpBossTrainersCheckBox.setEnabled(false); + tpBossTrainersCheckBox.setSelected(false); + tpImportantTrainersCheckBox.setEnabled(false); + tpImportantTrainersCheckBox.setSelected(false); + tpRegularTrainersCheckBox.setEnabled(false); + tpRegularTrainersCheckBox.setSelected(false); + tpBossTrainersItemsCheckBox.setEnabled(false); + tpBossTrainersItemsCheckBox.setSelected(false); + tpImportantTrainersItemsCheckBox.setEnabled(false); + tpImportantTrainersItemsCheckBox.setSelected(false); + tpRegularTrainersItemsCheckBox.setEnabled(false); + tpRegularTrainersItemsCheckBox.setSelected(false); + tpConsumableItemsOnlyCheckBox.setEnabled(false); + tpConsumableItemsOnlyCheckBox.setSelected(false); + tpSensibleItemsCheckBox.setEnabled(false); + tpSensibleItemsCheckBox.setSelected(false); + tpHighestLevelGetsItemCheckBox.setEnabled(false); + tpHighestLevelGetsItemCheckBox.setSelected(false); + tpEliteFourUniquePokemonCheckBox.setEnabled(false); + tpEliteFourUniquePokemonCheckBox.setSelected(false); + } else { + tpSimilarStrengthCheckBox.setEnabled(true); + tpDontUseLegendariesCheckBox.setEnabled(true); + tpNoEarlyWonderGuardCheckBox.setEnabled(true); + tpAllowAlternateFormesCheckBox.setEnabled(true); + if (currentRestrictions == null || currentRestrictions.allowTrainerSwapMegaEvolvables( + romHandler.forceSwapStaticMegaEvos(), isTrainerSetting(TRAINER_TYPE_THEMED) || + isTrainerSetting(TRAINER_TYPE_THEMED_ELITE4_GYMS))) { + tpSwapMegaEvosCheckBox.setEnabled(true); + } else { + tpSwapMegaEvosCheckBox.setEnabled(false); + tpSwapMegaEvosCheckBox.setSelected(false); + } + tpRandomShinyTrainerPokemonCheckBox.setEnabled(true); + tpDoubleBattleModeCheckBox.setEnabled(tpDoubleBattleModeCheckBox.isVisible()); + tpBossTrainersCheckBox.setEnabled(tpBossTrainersCheckBox.isVisible()); + tpImportantTrainersCheckBox.setEnabled(tpImportantTrainersCheckBox.isVisible()); + tpRegularTrainersCheckBox.setEnabled(tpRegularTrainersCheckBox.isVisible()); + tpBossTrainersItemsCheckBox.setEnabled(tpBossTrainersItemsCheckBox.isVisible()); + tpImportantTrainersItemsCheckBox.setEnabled(tpImportantTrainersItemsCheckBox.isVisible()); + tpRegularTrainersItemsCheckBox.setEnabled(tpRegularTrainersItemsCheckBox.isVisible()); + tpEliteFourUniquePokemonCheckBox.setEnabled(tpEliteFourUniquePokemonCheckBox.isVisible()); + } + + if (tpForceFullyEvolvedAtCheckBox.isSelected()) { + tpForceFullyEvolvedAtSlider.setEnabled(true); + } else { + tpForceFullyEvolvedAtSlider.setEnabled(false); + tpForceFullyEvolvedAtSlider.setValue(tpForceFullyEvolvedAtSlider.getMinimum()); + } + + if (tpPercentageLevelModifierCheckBox.isSelected()) { + tpPercentageLevelModifierSlider.setEnabled(true); + } else { + tpPercentageLevelModifierSlider.setEnabled(false); + tpPercentageLevelModifierSlider.setValue(0); + } + + if (tpBossTrainersCheckBox.isSelected()) { + tpBossTrainersSpinner.setEnabled(true); + } else { + tpBossTrainersSpinner.setEnabled(false); + tpBossTrainersSpinner.setValue(1); + } + + if (tpImportantTrainersCheckBox.isSelected()) { + tpImportantTrainersSpinner.setEnabled(true); + } else { + tpImportantTrainersSpinner.setEnabled(false); + tpImportantTrainersSpinner.setValue(1); + } + + if (tpRegularTrainersCheckBox.isSelected()) { + tpRegularTrainersSpinner.setEnabled(true); + } else { + tpRegularTrainersSpinner.setEnabled(false); + tpRegularTrainersSpinner.setValue(1); + } + + if (tpBossTrainersItemsCheckBox.isSelected() || tpImportantTrainersItemsCheckBox.isSelected() || + tpRegularTrainersItemsCheckBox.isSelected()) { + tpConsumableItemsOnlyCheckBox.setEnabled(true); + tpSensibleItemsCheckBox.setEnabled(true); + tpHighestLevelGetsItemCheckBox.setEnabled(true); + } else { + tpConsumableItemsOnlyCheckBox.setEnabled(false); + tpSensibleItemsCheckBox.setEnabled(false); + tpHighestLevelGetsItemCheckBox.setEnabled(false); + } + + if (!peRandomEveryLevelRadioButton.isSelected() && (!spUnchangedRadioButton.isSelected() || !isTrainerSetting(TRAINER_UNCHANGED))) { + tpRivalCarriesStarterCheckBox.setEnabled(true); + } else { + tpRivalCarriesStarterCheckBox.setEnabled(false); + tpRivalCarriesStarterCheckBox.setSelected(false); + } + + if (isTrainerSetting(TRAINER_TYPE_THEMED)) { + tpWeightTypesCheckBox.setEnabled(true); + } else { + tpWeightTypesCheckBox.setEnabled(false); + tpWeightTypesCheckBox.setSelected(false); + } + + if (tpEliteFourUniquePokemonCheckBox.isSelected()) { + tpEliteFourUniquePokemonSpinner.setEnabled(true); + } else { + tpEliteFourUniquePokemonSpinner.setEnabled(false); + tpEliteFourUniquePokemonSpinner.setValue(1); + } + + if (!totpUnchangedRadioButton.isSelected() || !totpAllyUnchangedRadioButton.isSelected()) { + totpAllowAltFormesCheckBox.setEnabled(true); + } else { + totpAllowAltFormesCheckBox.setEnabled(false); + totpAllowAltFormesCheckBox.setSelected(false); + } + + if (totpPercentageLevelModifierCheckBox.isSelected()) { + totpPercentageLevelModifierSlider.setEnabled(true); + } else { + totpPercentageLevelModifierSlider.setEnabled(false); + totpPercentageLevelModifierSlider.setValue(0); + } + + if (wpRandomRadioButton.isSelected()) { + wpARNoneRadioButton.setEnabled(true); + wpARSimilarStrengthRadioButton.setEnabled(true); + wpARCatchEmAllModeRadioButton.setEnabled(true); + wpARTypeThemeAreasRadioButton.setEnabled(true); + wpBalanceShakingGrassPokemonCheckBox.setEnabled(true); + } else if (wpArea1To1RadioButton.isSelected()) { + wpARNoneRadioButton.setEnabled(true); + wpARSimilarStrengthRadioButton.setEnabled(true); + wpARCatchEmAllModeRadioButton.setEnabled(true); + wpARTypeThemeAreasRadioButton.setEnabled(true); + wpBalanceShakingGrassPokemonCheckBox.setEnabled(false); + } else if (wpGlobal1To1RadioButton.isSelected()) { + if (wpARCatchEmAllModeRadioButton.isSelected() || wpARTypeThemeAreasRadioButton.isSelected()) { + wpARNoneRadioButton.setSelected(true); + } + wpARNoneRadioButton.setEnabled(true); + wpARSimilarStrengthRadioButton.setEnabled(true); + wpARCatchEmAllModeRadioButton.setEnabled(false); + wpARTypeThemeAreasRadioButton.setEnabled(false); + wpBalanceShakingGrassPokemonCheckBox.setEnabled(false); + } else { + wpARNoneRadioButton.setEnabled(false); + wpARNoneRadioButton.setSelected(true); + wpARSimilarStrengthRadioButton.setEnabled(false); + wpARCatchEmAllModeRadioButton.setEnabled(false); + wpARTypeThemeAreasRadioButton.setEnabled(false); + wpBalanceShakingGrassPokemonCheckBox.setEnabled(false); + } + + if (wpUnchangedRadioButton.isSelected()) { + wpUseTimeBasedEncountersCheckBox.setEnabled(false); + wpUseTimeBasedEncountersCheckBox.setSelected(false); + wpDontUseLegendariesCheckBox.setEnabled(false); + wpDontUseLegendariesCheckBox.setSelected(false); + wpAllowAltFormesCheckBox.setEnabled(false); + wpAllowAltFormesCheckBox.setSelected(false); + } else { + wpUseTimeBasedEncountersCheckBox.setEnabled(true); + wpDontUseLegendariesCheckBox.setEnabled(true); + wpAllowAltFormesCheckBox.setEnabled(true); + } + + if (wpRandomizeHeldItemsCheckBox.isSelected() + && wpRandomizeHeldItemsCheckBox.isVisible() + && wpRandomizeHeldItemsCheckBox.isEnabled()) { // ??? why all three + wpBanBadItemsCheckBox.setEnabled(true); + } else { + wpBanBadItemsCheckBox.setEnabled(false); + wpBanBadItemsCheckBox.setSelected(false); + } + + if (wpSetMinimumCatchRateCheckBox.isSelected()) { + wpSetMinimumCatchRateSlider.setEnabled(true); + } else { + wpSetMinimumCatchRateSlider.setEnabled(false); + wpSetMinimumCatchRateSlider.setValue(0); + } + + if (wpPercentageLevelModifierCheckBox.isSelected()) { + wpPercentageLevelModifierSlider.setEnabled(true); + } else { + wpPercentageLevelModifierSlider.setEnabled(false); + wpPercentageLevelModifierSlider.setValue(0); + } + + if (pmsMetronomeOnlyModeRadioButton.isSelected()) { + tmUnchangedRadioButton.setEnabled(false); + tmRandomRadioButton.setEnabled(false); + tmUnchangedRadioButton.setSelected(true); + + mtUnchangedRadioButton.setEnabled(false); + mtRandomRadioButton.setEnabled(false); + mtUnchangedRadioButton.setSelected(true); + + tmLevelupMoveSanityCheckBox.setEnabled(false); + tmLevelupMoveSanityCheckBox.setSelected(false); + tmKeepFieldMoveTMsCheckBox.setEnabled(false); + tmKeepFieldMoveTMsCheckBox.setSelected(false); + tmForceGoodDamagingCheckBox.setEnabled(false); + tmForceGoodDamagingCheckBox.setSelected(false); + tmNoGameBreakingMovesCheckBox.setEnabled(false); + tmNoGameBreakingMovesCheckBox.setSelected(false); + tmFollowEvolutionsCheckBox.setEnabled(false); + tmFollowEvolutionsCheckBox.setSelected(false); + + mtLevelupMoveSanityCheckBox.setEnabled(false); + mtLevelupMoveSanityCheckBox.setSelected(false); + mtKeepFieldMoveTutorsCheckBox.setEnabled(false); + mtKeepFieldMoveTutorsCheckBox.setSelected(false); + mtForceGoodDamagingCheckBox.setEnabled(false); + mtForceGoodDamagingCheckBox.setSelected(false); + mtNoGameBreakingMovesCheckBox.setEnabled(false); + mtNoGameBreakingMovesCheckBox.setSelected(false); + mtFollowEvolutionsCheckBox.setEnabled(false); + mtFollowEvolutionsCheckBox.setSelected(false); + } else { + tmUnchangedRadioButton.setEnabled(true); + tmRandomRadioButton.setEnabled(true); + + mtUnchangedRadioButton.setEnabled(true); + mtRandomRadioButton.setEnabled(true); + + if (!(pmsUnchangedRadioButton.isSelected()) || !(tmUnchangedRadioButton.isSelected()) + || !(thcUnchangedRadioButton.isSelected())) { + tmLevelupMoveSanityCheckBox.setEnabled(true); + } else { + tmLevelupMoveSanityCheckBox.setEnabled(false); + tmLevelupMoveSanityCheckBox.setSelected(false); + } + + if ((!thcUnchangedRadioButton.isSelected()) || (tmLevelupMoveSanityCheckBox.isSelected())) { + tmFollowEvolutionsCheckBox.setEnabled(followEvolutionControlsEnabled); + } + else { + tmFollowEvolutionsCheckBox.setEnabled(false); + tmFollowEvolutionsCheckBox.setSelected(false); + } + + if (!(tmUnchangedRadioButton.isSelected())) { + tmKeepFieldMoveTMsCheckBox.setEnabled(true); + tmForceGoodDamagingCheckBox.setEnabled(true); + tmNoGameBreakingMovesCheckBox.setEnabled(true); + } else { + tmKeepFieldMoveTMsCheckBox.setEnabled(false); + tmKeepFieldMoveTMsCheckBox.setSelected(false); + tmForceGoodDamagingCheckBox.setEnabled(false); + tmForceGoodDamagingCheckBox.setSelected(false); + tmNoGameBreakingMovesCheckBox.setEnabled(false); + tmNoGameBreakingMovesCheckBox.setSelected(false); + } + + if (romHandler.hasMoveTutors() + && (!(pmsUnchangedRadioButton.isSelected()) || !(mtUnchangedRadioButton.isSelected()) + || !(mtcUnchangedRadioButton.isSelected()))) { + mtLevelupMoveSanityCheckBox.setEnabled(true); + } else { + mtLevelupMoveSanityCheckBox.setEnabled(false); + mtLevelupMoveSanityCheckBox.setSelected(false); + } + + if (!(mtcUnchangedRadioButton.isSelected()) || (mtLevelupMoveSanityCheckBox.isSelected())) { + mtFollowEvolutionsCheckBox.setEnabled(followEvolutionControlsEnabled); + } + else { + mtFollowEvolutionsCheckBox.setEnabled(false); + mtFollowEvolutionsCheckBox.setSelected(false); + } + + if (romHandler.hasMoveTutors() && !(mtUnchangedRadioButton.isSelected())) { + mtKeepFieldMoveTutorsCheckBox.setEnabled(true); + mtForceGoodDamagingCheckBox.setEnabled(true); + mtNoGameBreakingMovesCheckBox.setEnabled(true); + } else { + mtKeepFieldMoveTutorsCheckBox.setEnabled(false); + mtKeepFieldMoveTutorsCheckBox.setSelected(false); + mtForceGoodDamagingCheckBox.setEnabled(false); + mtForceGoodDamagingCheckBox.setSelected(false); + mtNoGameBreakingMovesCheckBox.setEnabled(false); + mtNoGameBreakingMovesCheckBox.setSelected(false); + } + } + + if (tmForceGoodDamagingCheckBox.isSelected()) { + tmForceGoodDamagingSlider.setEnabled(true); + } else { + tmForceGoodDamagingSlider.setEnabled(false); + tmForceGoodDamagingSlider.setValue(tmForceGoodDamagingSlider.getMinimum()); + } + + if (mtForceGoodDamagingCheckBox.isSelected()) { + mtForceGoodDamagingSlider.setEnabled(true); + } else { + mtForceGoodDamagingSlider.setEnabled(false); + mtForceGoodDamagingSlider.setValue(mtForceGoodDamagingSlider.getMinimum()); + } + + tmFullHMCompatibilityCheckBox.setEnabled(!thcFullCompatibilityRadioButton.isSelected()); + + if (fiRandomRadioButton.isSelected() && fiRandomRadioButton.isVisible() && fiRandomRadioButton.isEnabled()) { + fiBanBadItemsCheckBox.setEnabled(true); + } else if (fiRandomEvenDistributionRadioButton.isSelected() && fiRandomEvenDistributionRadioButton.isVisible() + && fiRandomEvenDistributionRadioButton.isEnabled()) { + fiBanBadItemsCheckBox.setEnabled(true); + } else { + fiBanBadItemsCheckBox.setEnabled(false); + fiBanBadItemsCheckBox.setSelected(false); + } + + if (shRandomRadioButton.isSelected() && shRandomRadioButton.isVisible() && shRandomRadioButton.isEnabled()) { + shBanBadItemsCheckBox.setEnabled(true); + shBanRegularShopItemsCheckBox.setEnabled(true); + shBanOverpoweredShopItemsCheckBox.setEnabled(true); + shBalanceShopItemPricesCheckBox.setEnabled(true); + shGuaranteeEvolutionItemsCheckBox.setEnabled(true); + shGuaranteeXItemsCheckBox.setEnabled(true); + } else { + shBanBadItemsCheckBox.setEnabled(false); + shBanBadItemsCheckBox.setSelected(false); + shBanRegularShopItemsCheckBox.setEnabled(false); + shBanRegularShopItemsCheckBox.setSelected(false); + shBanOverpoweredShopItemsCheckBox.setEnabled(false); + shBanOverpoweredShopItemsCheckBox.setSelected(false); + shBalanceShopItemPricesCheckBox.setEnabled(false); + shBalanceShopItemPricesCheckBox.setSelected(false); + shGuaranteeEvolutionItemsCheckBox.setEnabled(false); + shGuaranteeEvolutionItemsCheckBox.setSelected(false); + shGuaranteeXItemsCheckBox.setEnabled(false); + shGuaranteeXItemsCheckBox.setSelected(false); + } + + if (puRandomRadioButton.isSelected() && puRandomRadioButton.isVisible() && puRandomRadioButton.isEnabled()) { + puBanBadItemsCheckBox.setEnabled(true); + } else { + puBanBadItemsCheckBox.setEnabled(false); + puBanBadItemsCheckBox.setSelected(false); + } + } + + private void initTweaksPanel() { + tweakCheckBoxes = new ArrayList<>(); + int numTweaks = MiscTweak.allTweaks.size(); + for (int i = 0; i < numTweaks; i++) { + MiscTweak ct = MiscTweak.allTweaks.get(i); + JCheckBox tweakBox = new JCheckBox(); + tweakBox.setText(ct.getTweakName()); + tweakBox.setToolTipText(ct.getTooltipText()); + tweakCheckBoxes.add(tweakBox); + } + } + + private void makeTweaksLayout(List tweaks) { + liveTweaksPanel = new JPanel(new GridBagLayout()); + TitledBorder border = BorderFactory.createTitledBorder("Misc. Tweaks"); + border.setTitleFont(border.getTitleFont().deriveFont(Font.BOLD)); + liveTweaksPanel.setBorder(border); + + int numTweaks = tweaks.size(); + Iterator tweaksIterator = tweaks.iterator(); + + GridBagConstraints c = new GridBagConstraints(); + c.anchor = GridBagConstraints.FIRST_LINE_START; + c.insets = new Insets(5,5,0,5); + + int TWEAK_COLS = 4; + int numCols = Math.min(TWEAK_COLS, numTweaks); + + for (int row = 0; row <= numTweaks / numCols; row++) { + for (int col = 0; col < numCols; col++) { + if (!tweaksIterator.hasNext()) break; + c.gridx = col; + c.gridy = row; + liveTweaksPanel.add(tweaksIterator.next(),c); + } + } + + // Pack the checkboxes together + + GridBagConstraints horizontalC = new GridBagConstraints(); + horizontalC.gridx = numCols; + horizontalC.gridy = 0; + horizontalC.weightx = 0.1; + + GridBagConstraints verticalC = new GridBagConstraints(); + verticalC.gridx = 0; + verticalC.gridy = (numTweaks / numCols) + 1; + verticalC.weighty = 0.1; + + liveTweaksPanel.add(new JSeparator(SwingConstants.HORIZONTAL),horizontalC); + liveTweaksPanel.add(new JSeparator(SwingConstants.VERTICAL),verticalC); + } + + private void populateDropdowns() { + List currentStarters = romHandler.getStarters(); + List allPokes = + romHandler.generationOfPokemon() >= 6 ? + romHandler.getPokemonInclFormes() + .stream() + .filter(pk -> pk == null || !pk.actuallyCosmetic) + .collect(Collectors.toList()) : + romHandler.getPokemon(); + String[] pokeNames = new String[allPokes.size()]; + pokeNames[0] = "Random"; + for (int i = 1; i < allPokes.size(); i++) { + pokeNames[i] = allPokes.get(i).fullName(); + + } + + spComboBox1.setModel(new DefaultComboBoxModel<>(pokeNames)); + spComboBox1.setSelectedIndex(allPokes.indexOf(currentStarters.get(0))); + spComboBox2.setModel(new DefaultComboBoxModel<>(pokeNames)); + spComboBox2.setSelectedIndex(allPokes.indexOf(currentStarters.get(1))); + if (!romHandler.isYellow()) { + spComboBox3.setModel(new DefaultComboBoxModel<>(pokeNames)); + spComboBox3.setSelectedIndex(allPokes.indexOf(currentStarters.get(2))); + } + + String[] baseStatGenerationNumbers = new String[Math.min(4, GlobalConstants.HIGHEST_POKEMON_GEN - romHandler.generationOfPokemon())]; + int j = Math.max(6, romHandler.generationOfPokemon() + 1); + for (int i = 0; i < baseStatGenerationNumbers.length; i++) { + baseStatGenerationNumbers[i] = String.valueOf(j); + j++; + } + pbsUpdateComboBox.setModel(new DefaultComboBoxModel<>(baseStatGenerationNumbers)); + + String[] moveGenerationNumbers = new String[GlobalConstants.HIGHEST_POKEMON_GEN - romHandler.generationOfPokemon()]; + j = romHandler.generationOfPokemon() + 1; + for (int i = 0; i < moveGenerationNumbers.length; i++) { + moveGenerationNumbers[i] = String.valueOf(j); + j++; + } + mdUpdateComboBox.setModel(new DefaultComboBoxModel<>(moveGenerationNumbers)); + + + tpComboBox.setModel(new DefaultComboBoxModel<>(getTrainerSettingsForGeneration(romHandler.generationOfPokemon()))); + tpComboBox.setRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + JComponent comp = (JComponent) super.getListCellRendererComponent(list, + value, index, isSelected, cellHasFocus); + + if (index >= 0 && value != null) { + list.setToolTipText(bundle.getString(trainerSettingToolTips.get(trainerSettings.indexOf(value)))); + } + return comp; + } + }); + } + + private ImageIcon makeMascotIcon() { + try { + BufferedImage handlerImg = romHandler.getMascotImage(); + + if (handlerImg == null) { + return emptyIcon; + } + + BufferedImage nImg = new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB); + int hW = handlerImg.getWidth(); + int hH = handlerImg.getHeight(); + nImg.getGraphics().drawImage(handlerImg, 64 - hW / 2, 64 - hH / 2, frame); + return new ImageIcon(nImg); + } catch (Exception ex) { + return emptyIcon; + } + } + + private void checkCustomNames() { + String[] cnamefiles = new String[] { SysConstants.tnamesFile, SysConstants.tclassesFile, + SysConstants.nnamesFile }; + + boolean foundFile = false; + for (int file = 0; file < 3; file++) { + File currentFile = new File(SysConstants.ROOT_PATH + cnamefiles[file]); + if (currentFile.exists()) { + foundFile = true; + break; + } + } + + if (foundFile) { + int response = JOptionPane.showConfirmDialog(frame, + bundle.getString("GUI.convertNameFilesDialog.text"), + bundle.getString("GUI.convertNameFilesDialog.title"), JOptionPane.YES_NO_OPTION); + if (response == JOptionPane.YES_OPTION) { + try { + CustomNamesSet newNamesData = CustomNamesSet.importOldNames(); + byte[] data = newNamesData.getBytes(); + FileFunctions.writeBytesToFile(SysConstants.customNamesFile, data); + } catch (IOException ex) { + JOptionPane.showMessageDialog(frame, bundle.getString("GUI.convertNameFilesFailed")); + } + } + + haveCheckedCustomNames = true; + } + + } + + private void testForRequiredConfigs() { + try { + Utils.testForRequiredConfigs(); + } catch (FileNotFoundException e) { + JOptionPane.showMessageDialog(null, + String.format(bundle.getString("GUI.configFileMissing"), e.getMessage())); + System.exit(1); + } + } + + private ExpCurve[] getEXPCurvesForGeneration(int generation) { + ExpCurve[] result; + if (generation < 3) { + result = new ExpCurve[]{ ExpCurve.MEDIUM_FAST, ExpCurve.MEDIUM_SLOW, ExpCurve.FAST, ExpCurve.SLOW }; + } else { + result = new ExpCurve[]{ ExpCurve.MEDIUM_FAST, ExpCurve.MEDIUM_SLOW, ExpCurve.FAST, ExpCurve.SLOW, ExpCurve.ERRATIC, ExpCurve.FLUCTUATING }; + } + return result; + } + + private String[] getTrainerSettingsForGeneration(int generation) { + List result = new ArrayList<>(trainerSettings); + if (generation != 5) { + result.remove(bundle.getString("GUI.tpMain3RandomEvenDistributionMainGame.text")); + } + return result.toArray(new String[0]); + } + + private boolean isTrainerSetting(int setting) { + return trainerSettings.indexOf(tpComboBox.getSelectedItem()) == setting; + } + + public static void main(String[] args) { + String firstCliArg = args.length > 0 ? args[0] : ""; + // invoke as CLI program + if (firstCliArg.equals("cli")) { + // snip the "cli" flag arg off the args array and invoke command + String[] commandArgs = Arrays.copyOfRange(args, 1, args.length); + int exitCode = CliRandomizer.invoke(commandArgs); + System.exit(exitCode); + } else { + launcherInput = firstCliArg; + if (launcherInput.equals("please-use-the-launcher")) usedLauncher = true; + SwingUtilities.invokeLater(() -> { + frame = new JFrame("NewRandomizerGUI"); + try { + String lafName = javax.swing.UIManager.getSystemLookAndFeelClassName(); + // NEW: Only set Native LaF on windows. + if (lafName.equalsIgnoreCase("com.sun.java.swing.plaf.windows.WindowsLookAndFeel")) { + javax.swing.UIManager.setLookAndFeel(lafName); + } + } catch (ClassNotFoundException | InstantiationException | UnsupportedLookAndFeelException | IllegalAccessException ex) { + java.util.logging.Logger.getLogger(NewRandomizerGUI.class.getName()).log(java.util.logging.Level.SEVERE, null, + ex); + } + frame.setContentPane(new NewRandomizerGUI().mainPanel); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.pack(); + frame.setVisible(true); + }); + } + } +} diff --git a/src/com/pkrandom/newgui/OperationDialog.java b/src/com/pkrandom/newgui/OperationDialog.java new file mode 100644 index 0000000..0028582 --- /dev/null +++ b/src/com/pkrandom/newgui/OperationDialog.java @@ -0,0 +1,137 @@ +package com.pkrandom.newgui; + +/*----------------------------------------------------------------------------*/ +/*-- OperationDialog.java - a dialog that displays the loading spinner --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.FileFunctions; + +import javax.swing.*; +import java.awt.*; +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @author Stewart + */ +public class OperationDialog extends javax.swing.JDialog { + + /** + * + */ + private static final long serialVersionUID = 5965463550336235236L; + + /** + * Creates new form OperationDialog + */ + public OperationDialog(String text, Frame parent, boolean modal) { + super(parent, modal); + initComponents(); + this.loadingLabel.setText(text); + setLocationRelativeTo(parent); + } + + public OperationDialog(String text, Dialog parent, boolean modal) { + super(parent, modal); + initComponents(); + this.loadingLabel.setText(text); + setLocationRelativeTo(parent); + } + + /* @formatter:off */ + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + jPanel1 = new javax.swing.JPanel(); + loadingLabel = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE); + setResizable(false); + setUndecorated(true); + + jPanel1.setBorder(new javax.swing.border.LineBorder(new java.awt.Color( + 0, 0, 0), 2, true)); + + loadingLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + loadingLabel.setIcon(getLoadingIcon()); + loadingLabel.setText("Loading..."); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout( + jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup(jPanel1Layout.createParallelGroup( + javax.swing.GroupLayout.Alignment.LEADING).addGroup( + jPanel1Layout.createSequentialGroup().addContainerGap() + .addComponent(loadingLabel) + .addContainerGap(53, Short.MAX_VALUE))); + jPanel1Layout.setVerticalGroup(jPanel1Layout.createParallelGroup( + javax.swing.GroupLayout.Alignment.LEADING).addGroup( + jPanel1Layout + .createSequentialGroup() + .addContainerGap() + .addComponent(loadingLabel) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE))); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout( + getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup(layout.createParallelGroup( + javax.swing.GroupLayout.Alignment.LEADING).addComponent( + jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)); + layout.setVerticalGroup(layout.createParallelGroup( + javax.swing.GroupLayout.Alignment.LEADING).addComponent( + jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE)); + + pack(); + }// //GEN-END:initComponents + + private ImageIcon getLoadingIcon() { + try { + InputStream in = OperationDialog.class + .getResourceAsStream("/com/pkrandom/newgui/loading.gif"); + byte[] buf = FileFunctions.readFullyIntoBuffer(in, in.available()); + in.close(); + Image image = Toolkit.getDefaultToolkit().createImage(buf); + return new ImageIcon(image); + } catch (IOException ex) { + return null; + } + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel jPanel1; + private javax.swing.JLabel loadingLabel; + + // End of variables declaration//GEN-END:variables + /* @formatter:on */ +} diff --git a/src/com/pkrandom/newgui/PresetFileFilter.java b/src/com/pkrandom/newgui/PresetFileFilter.java new file mode 100644 index 0000000..14ffda0 --- /dev/null +++ b/src/com/pkrandom/newgui/PresetFileFilter.java @@ -0,0 +1,56 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.pkrandom.newgui; + +/*----------------------------------------------------------------------------*/ +/*-- PresetFileFilter.java - a file filter for the "randomization presets" --*/ +/*-- which allow the same random ROM to be produced--*/ +/*-- on demand. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import javax.swing.filechooser.FileFilter; +import java.io.File; + +public class PresetFileFilter extends FileFilter { + + @Override + public boolean accept(File arg0) { + if (arg0.isDirectory()) { + return true; // needed to allow directory navigation + } + String filename = arg0.getName(); + if (!filename.contains(".")) { + return false; + } + String extension = arg0.getName().substring( + arg0.getName().lastIndexOf('.') + 1); + return extension.toLowerCase().equals("rndp"); + } + + @Override + public String getDescription() { + return "Pokemon Randomizer Preset (*.rndp)"; + } + +} diff --git a/src/com/pkrandom/newgui/PresetLoadDialog.java b/src/com/pkrandom/newgui/PresetLoadDialog.java new file mode 100644 index 0000000..4e73793 --- /dev/null +++ b/src/com/pkrandom/newgui/PresetLoadDialog.java @@ -0,0 +1,504 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.pkrandom.newgui; + +/*----------------------------------------------------------------------------*/ +/*-- PresetLoadDialog.java - a dialog to allow use of preset files or --*/ +/*-- random seed/config string pairs to produce --*/ +/*-- premade ROMs. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.*; +import com.pkrandom.exceptions.InvalidSupplementFilesException; +import com.pkrandom.romhandlers.Abstract3DSRomHandler; +import com.pkrandom.romhandlers.RomHandler; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * + * @author Stewart + */ +public class PresetLoadDialog extends JDialog { + + /** + * + */ + private static final long serialVersionUID = -7898067118947765260L; + private NewRandomizerGUI parentGUI; + private RomHandler currentROM; + private boolean completed = false; + private String requiredName = null; + private volatile boolean changeFieldsWithoutCheck = false; + private CustomNamesSet customNames; + private java.util.ResourceBundle bundle; + + /** + * Creates new form PresetLoadDialog + */ + public PresetLoadDialog(NewRandomizerGUI parent, JFrame frame) { + super(frame, true); + bundle = java.util.ResourceBundle.getBundle("com/pkrandom/newgui/Bundle"); // NOI18N + initComponents(); + this.parentGUI = parent; + this.presetFileChooser.setCurrentDirectory(new File("./")); + this.romFileChooser.setCurrentDirectory(new File("./")); + initialState(); + setLocationRelativeTo(frame); + setVisible(true); + } + + private void initialState() { + this.romFileButton.setEnabled(false); + this.acceptButton.setEnabled(false); + addChangeListener(this.randomSeedField); + addChangeListener(this.configStringField); + } + + private void addChangeListener(JTextField field) { + field.getDocument().addDocumentListener(new DocumentListener() { + + @Override + public void insertUpdate(DocumentEvent e) { + if (!changeFieldsWithoutCheck) + PresetLoadDialog.this.checkValues(); + + } + + @Override + public void removeUpdate(DocumentEvent e) { + if (!changeFieldsWithoutCheck) + PresetLoadDialog.this.checkValues(); + + } + + @Override + public void changedUpdate(DocumentEvent e) { + if (!changeFieldsWithoutCheck) + PresetLoadDialog.this.checkValues(); + + } + }); + + } + + private boolean checkValues() { + String name; + try { + Long.parseLong(this.randomSeedField.getText()); + } catch (NumberFormatException ex) { + invalidValues(); + return false; + } + + // 161 onwards: look for version number + String configString = this.configStringField.getText(); + if (configString.length() < 3) { + invalidValues(); + return false; + } + + try { + int presetVersionNumber = Integer.parseInt(configString.substring(0, 3)); + if (presetVersionNumber != Version.VERSION) { + promptForDifferentRandomizerVersion(presetVersionNumber); + safelyClearFields(); + invalidValues(); + return false; + } + } catch (NumberFormatException ex) { + invalidValues(); + return false; + } + + try { + name = this.parentGUI.getValidRequiredROMName(configString.substring(3), customNames); + } catch (InvalidSupplementFilesException ex) { + safelyClearFields(); + invalidValues(); + return false; + } catch (Exception ex) { + // other exception, just call it invalid for now + invalidValues(); + return false; + } + if (name == null) { + invalidValues(); + return false; + } + requiredName = name; + this.romRequiredLabel.setText(String.format(bundle.getString("PresetLoadDialog.romRequiredLabel.textWithROM"), + name)); + this.romFileButton.setEnabled(true); + + if (currentROM != null && !currentROM.getROMName().equals(name)) { + this.currentROM = null; + this.acceptButton.setEnabled(false); + this.romFileField.setText(""); + } + return true; + } + + private void promptForDifferentRandomizerVersion(int presetVN) { + // so what version number was it? + if (presetVN > Version.VERSION) { + // it's for a newer version + JOptionPane.showMessageDialog(this, bundle.getString("PresetLoadDialog.newerVersionRequired")); + } else { + // tell them which older version to use to load this preset + // this should be the newest version that used that value + // for the constant PRESET_FILE_VERSION + String versionWanted = Version.oldVersions.getOrDefault(presetVN,"Unknown"); + JOptionPane.showMessageDialog(this, + String.format(bundle.getString("PresetLoadDialog.olderVersionRequired"), versionWanted)); + } + } + + private void safelyClearFields() { + SwingUtilities.invokeLater(() -> { + changeFieldsWithoutCheck = true; + configStringField.setText(""); + randomSeedField.setText(""); + changeFieldsWithoutCheck = false; + }); + } + + private void invalidValues() { + this.currentROM = null; + this.romFileField.setText(""); + this.romRequiredLabel.setText(bundle.getString("PresetLoadDialog.romRequiredLabel.text")); + this.romFileButton.setEnabled(false); + this.acceptButton.setEnabled(false); + this.requiredName = null; + + } + + public boolean isCompleted() { + return completed; + } + + public RomHandler getROM() { + return currentROM; + } + + public long getSeed() { + return Long.parseLong(this.randomSeedField.getText()); + } + + public String getConfigString() { + return this.configStringField.getText().substring(3); + } + + public CustomNamesSet getCustomNames() { + return customNames; + } + + private void presetFileButtonActionPerformed() {// GEN-FIRST:event_presetFileButtonActionPerformed + presetFileChooser.setSelectedFile(null); + int returnVal = presetFileChooser.showOpenDialog(this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File fh = presetFileChooser.getSelectedFile(); + try { + DataInputStream dis = new DataInputStream(new FileInputStream(fh)); + int checkInt = dis.readInt(); + if (checkInt != Version.VERSION) { + dis.close(); + promptForDifferentRandomizerVersion(checkInt); + return; + } + long seed = dis.readLong(); + String preset = dis.readUTF(); + customNames = new CustomNamesSet(dis); + changeFieldsWithoutCheck = true; + this.randomSeedField.setText(Long.toString(seed)); + this.configStringField.setText(checkInt + "" + preset); + changeFieldsWithoutCheck = false; + if (checkValues()) { + this.randomSeedField.setEnabled(false); + this.configStringField.setEnabled(false); + this.presetFileField.setText(fh.getAbsolutePath()); + } else { + this.randomSeedField.setText(""); + this.configStringField.setText(""); + this.randomSeedField.setEnabled(true); + this.configStringField.setEnabled(true); + this.presetFileField.setText(""); + customNames = null; + JOptionPane.showMessageDialog(this, bundle.getString("PresetLoadDialog.invalidSeedFile")); + } + dis.close(); + } catch (IOException ex) { + JOptionPane.showMessageDialog(this, bundle.getString("PresetLoadDialog.loadingSeedFileFailed")); + } + } + }// GEN-LAST:event_presetFileButtonActionPerformed + + private void romFileButtonActionPerformed() {// GEN-FIRST:event_romFileButtonActionPerformed + romFileChooser.setSelectedFile(null); + int returnVal = romFileChooser.showOpenDialog(this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + final File fh = romFileChooser.getSelectedFile(); + for (RomHandler.Factory rhf : parentGUI.checkHandlers) { + if (rhf.isLoadable(fh.getAbsolutePath())) { + final RomHandler checkHandler = rhf.create(RandomSource.instance()); + if (!NewRandomizerGUI.usedLauncher && checkHandler instanceof Abstract3DSRomHandler) { + String message = bundle.getString("GUI.pleaseUseTheLauncher"); + Object[] messages = {message}; + JOptionPane.showMessageDialog(this, messages); + return; + } + final JDialog opDialog = new OperationDialog(bundle.getString("GUI.loadingText"), this, + true); + Thread t = new Thread(() -> { + SwingUtilities.invokeLater(() -> opDialog.setVisible(true)); + try { + checkHandler.loadRom(fh.getAbsolutePath()); + } catch (Exception ex) { + JOptionPane.showMessageDialog(PresetLoadDialog.this, + bundle.getString("GUI.loadFailedNoLog")); + } + SwingUtilities.invokeLater(() -> { + opDialog.setVisible(false); + if (checkHandler.getROMName().equals(requiredName)) { + // Got it + romFileField.setText(fh.getAbsolutePath()); + currentROM = checkHandler; + acceptButton.setEnabled(true); + return; + } else { + JOptionPane.showMessageDialog(PresetLoadDialog.this, String.format( + bundle.getString("PresetLoadDialog.notRequiredROM"), requiredName, + checkHandler.getROMName())); + return; + } + }); + }); + t.start(); + return; + } + } + JOptionPane.showMessageDialog(this, + String.format(bundle.getString("GUI.unsupportedRom"), fh.getName())); + } + }// GEN-LAST:event_romFileButtonActionPerformed + + private void acceptButtonActionPerformed() {// GEN-FIRST:event_acceptButtonActionPerformed + if (customNames == null) { + try { + customNames = FileFunctions.getCustomNames(); + } catch (IOException e) { + e.printStackTrace(); + } + } + completed = true; + this.setVisible(false); + }// GEN-LAST:event_acceptButtonActionPerformed + + private void cancelButtonActionPerformed() {// GEN-FIRST:event_cancelButtonActionPerformed + completed = false; + this.setVisible(false); + }// GEN-LAST:event_cancelButtonActionPerformed + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + presetFileChooser = new JFileChooser(); + romFileChooser = new JFileChooser(); + presetFileLabel = new javax.swing.JLabel(); + presetFileField = new JTextField(); + presetFileButton = new javax.swing.JButton(); + orLabel = new javax.swing.JLabel(); + seedBoxLabel = new javax.swing.JLabel(); + randomSeedField = new JTextField(); + configStringBoxLabel = new javax.swing.JLabel(); + configStringField = new JTextField(); + romRequiredLabel = new javax.swing.JLabel(); + romFileBoxLabel = new javax.swing.JLabel(); + romFileField = new JTextField(); + romFileButton = new javax.swing.JButton(); + acceptButton = new javax.swing.JButton(); + cancelButton = new javax.swing.JButton(); + + presetFileChooser.setFileFilter(new PresetFileFilter()); + + romFileChooser.setFileFilter(new ROMFilter()); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("com/pkrandom/newgui/Bundle"); // NOI18N + setTitle(bundle.getString("PresetLoadDialog.title")); // NOI18N + setModal(true); + setResizable(false); + + presetFileLabel.setText(bundle.getString("PresetLoadDialog.presetFileLabel.text")); // NOI18N + + presetFileField.setEditable(false); + + presetFileButton.setText(bundle.getString("PresetLoadDialog.presetFileButton.text")); // NOI18N + presetFileButton.addActionListener(evt -> presetFileButtonActionPerformed()); + + orLabel.setFont(new java.awt.Font("Tahoma", Font.BOLD, 11)); // NOI18N + orLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + orLabel.setText(bundle.getString("PresetLoadDialog.orLabel.text")); // NOI18N + + seedBoxLabel.setText(bundle.getString("PresetLoadDialog.seedBoxLabel.text")); // NOI18N + + configStringBoxLabel.setText(bundle.getString("PresetLoadDialog.configStringBoxLabel.text")); // NOI18N + + romRequiredLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + romRequiredLabel.setText(bundle.getString("PresetLoadDialog.romRequiredLabel.text")); // NOI18N + + romFileBoxLabel.setText(bundle.getString("PresetLoadDialog.romFileBoxLabel.text")); // NOI18N + + romFileField.setEditable(false); + + romFileButton.setText(bundle.getString("PresetLoadDialog.romFileButton.text")); // NOI18N + romFileButton.addActionListener(evt -> romFileButtonActionPerformed()); + + acceptButton.setText(bundle.getString("PresetLoadDialog.acceptButton.text")); // NOI18N + acceptButton.addActionListener(evt -> acceptButtonActionPerformed()); + + cancelButton.setText(bundle.getString("PresetLoadDialog.cancelButton.text")); // NOI18N + cancelButton.addActionListener(evt -> cancelButtonActionPerformed()); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup(layout + .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup( + layout.createSequentialGroup() + .addContainerGap() + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(romFileBoxLabel, + javax.swing.GroupLayout.Alignment.LEADING, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(presetFileLabel, + javax.swing.GroupLayout.Alignment.LEADING, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(seedBoxLabel, javax.swing.GroupLayout.Alignment.LEADING, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(configStringBoxLabel, + javax.swing.GroupLayout.Alignment.LEADING, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(18, 18, 18) + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup( + layout.createSequentialGroup() + .addComponent(acceptButton) + .addPreferredGap( + javax.swing.LayoutStyle.ComponentPlacement.RELATED, + 169, Short.MAX_VALUE) + .addComponent(cancelButton)) + .addComponent(randomSeedField).addComponent(configStringField) + .addComponent(presetFileField).addComponent(romFileField)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(presetFileButton, javax.swing.GroupLayout.PREFERRED_SIZE, + 26, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(romFileButton, javax.swing.GroupLayout.PREFERRED_SIZE, 1, + Short.MAX_VALUE)).addGap(12, 12, 12)) + .addComponent(orLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE) + .addComponent(romRequiredLabel, javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)); + layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup( + layout.createSequentialGroup() + .addContainerGap() + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(presetFileLabel) + .addComponent(presetFileField, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(presetFileButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(orLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(seedBoxLabel) + .addComponent(randomSeedField, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(configStringBoxLabel) + .addComponent(configStringField, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(romRequiredLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(romFileBoxLabel) + .addComponent(romFileField, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(romFileButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 23, Short.MAX_VALUE) + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(acceptButton).addComponent(cancelButton)).addContainerGap())); + + pack(); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton acceptButton; + private javax.swing.JButton cancelButton; + private javax.swing.JLabel configStringBoxLabel; + private JTextField configStringField; + private javax.swing.JLabel orLabel; + private javax.swing.JButton presetFileButton; + private JFileChooser presetFileChooser; + private JTextField presetFileField; + private javax.swing.JLabel presetFileLabel; + private JTextField randomSeedField; + private javax.swing.JLabel romFileBoxLabel; + private javax.swing.JButton romFileButton; + private JFileChooser romFileChooser; + private JTextField romFileField; + private javax.swing.JLabel romRequiredLabel; + private javax.swing.JLabel seedBoxLabel; + // End of variables declaration//GEN-END:variables +} diff --git a/src/com/pkrandom/newgui/PresetMakeDialog.java b/src/com/pkrandom/newgui/PresetMakeDialog.java new file mode 100644 index 0000000..da91e71 --- /dev/null +++ b/src/com/pkrandom/newgui/PresetMakeDialog.java @@ -0,0 +1,265 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.pkrandom.newgui; + +/*----------------------------------------------------------------------------*/ +/*-- PresetMakeDialog.java - a dialog to allow preset pairs to either be --*/ +/*-- copied down or saved to a binary file for --*/ +/*-- later use. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.FileFunctions; +import com.pkrandom.Settings; +import com.pkrandom.SysConstants; +import com.pkrandom.Version; + +import javax.swing.*; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.*; + +/** + * + * @author Stewart + */ +public class PresetMakeDialog extends javax.swing.JDialog { + /** + * + */ + private static final long serialVersionUID = 7663903108783731673L; + private long seed; + private String configString; + + /** + * Creates new form PresetMakeDialog + */ + public PresetMakeDialog(java.awt.Frame parent, long seed, String configString) { + super(parent, true); + initComponents(); + randomSeedField.setText(Long.toString(seed)); + configStringField.setText(Version.VERSION + "" + configString); + this.seed = seed; + this.configString = configString; + presetFileChooser.setCurrentDirectory(new File("./")); + this.randomSeedField.addMouseListener(new SelectTextListener(this.randomSeedField)); + this.configStringField.addMouseListener(new SelectTextListener(this.configStringField)); + setLocationRelativeTo(parent); + setVisible(true); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + presetFileChooser = new JFileChooser(); + gameRandomizedLabel = new javax.swing.JLabel(); + settingsToGiveLabel = new javax.swing.JLabel(); + seedFieldLabel = new javax.swing.JLabel(); + randomSeedField = new JTextField(); + configStringFieldLabel = new javax.swing.JLabel(); + configStringField = new JTextField(); + canProduceFileLabel = new javax.swing.JLabel(); + produceFileButton = new javax.swing.JButton(); + doneButton = new javax.swing.JButton(); + + presetFileChooser.setFileFilter(new PresetFileFilter()); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("com/pkrandom/newgui/Bundle"); // NOI18N + setTitle(bundle.getString("PresetMakeDialog.title")); // NOI18N + setModal(true); + setResizable(false); + + gameRandomizedLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + gameRandomizedLabel.setText(bundle.getString("PresetMakeDialog.gameRandomizedLabel.text")); // NOI18N + + settingsToGiveLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + settingsToGiveLabel.setText(bundle.getString("PresetMakeDialog.settingsToGiveLabel.text")); // NOI18N + + seedFieldLabel.setText(bundle.getString("PresetMakeDialog.seedFieldLabel.text")); // NOI18N + + randomSeedField.setEditable(false); + + configStringFieldLabel.setText(bundle.getString("PresetMakeDialog.configStringFieldLabel.text")); // NOI18N + + configStringField.setEditable(false); + + canProduceFileLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + canProduceFileLabel.setText(bundle.getString("PresetMakeDialog.canProduceFileLabel.text")); // NOI18N + + produceFileButton.setText(bundle.getString("PresetMakeDialog.produceFileButton.text")); // NOI18N + produceFileButton.addActionListener(evt -> produceFileButtonActionPerformed()); + + doneButton.setText(bundle.getString("PresetMakeDialog.doneButton.text")); // NOI18N + doneButton.addActionListener(evt -> doneButtonActionPerformed()); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup(layout + .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(gameRandomizedLabel, javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(settingsToGiveLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 599, Short.MAX_VALUE) + .addGroup( + layout.createSequentialGroup() + .addContainerGap() + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(seedFieldLabel).addComponent(configStringFieldLabel)) + .addGap(18, 18, 18) + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(randomSeedField).addComponent(configStringField)) + .addContainerGap()) + .addComponent(canProduceFileLabel, javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup( + layout.createSequentialGroup() + .addGap(67, 67, 67) + .addComponent(produceFileButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, + javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE).addComponent(doneButton) + .addGap(66, 66, 66))); + layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup( + layout.createSequentialGroup() + .addComponent(gameRandomizedLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(settingsToGiveLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(seedFieldLabel) + .addComponent(randomSeedField, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(configStringFieldLabel) + .addComponent(configStringField, javax.swing.GroupLayout.PREFERRED_SIZE, + javax.swing.GroupLayout.DEFAULT_SIZE, + javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(canProduceFileLabel) + .addGap(18, 18, 18) + .addGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(produceFileButton).addComponent(doneButton)) + .addGap(0, 11, Short.MAX_VALUE))); + + pack(); + }// //GEN-END:initComponents + + private void produceFileButtonActionPerformed() {// GEN-FIRST:event_produceFileButtonActionPerformed + presetFileChooser.setSelectedFile(null); + int returnVal = presetFileChooser.showSaveDialog(this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File fh = presetFileChooser.getSelectedFile(); + // Fix extension? + fh = FileFunctions.fixFilename(fh, "rndp"); + try { + DataOutputStream dos = new DataOutputStream(new FileOutputStream(fh)); + dos.writeInt(Version.VERSION); + dos.writeLong(seed); + dos.writeUTF(configString); + byte[] customnames = readFile(FileFunctions.openConfig(SysConstants.customNamesFile)); + dos.write(customnames); + dos.close(); + JOptionPane.showMessageDialog(this, "Preset file saved to\n" + fh.getAbsolutePath()); + } catch (IOException ex) { + JOptionPane.showMessageDialog(this, "Could not save the preset file."); + } + } + }// GEN-LAST:event_produceFileButtonActionPerformed + + private static byte[] readFile(InputStream is) throws IOException { + byte[] file = FileFunctions.readFullyIntoBuffer(is, is.available()); + is.close(); + return file; + } + + private void doneButtonActionPerformed() {// GEN-FIRST:event_doneButtonActionPerformed + this.setVisible(false); + }// GEN-LAST:event_doneButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel canProduceFileLabel; + private JTextField configStringField; + private javax.swing.JLabel configStringFieldLabel; + private javax.swing.JButton doneButton; + private javax.swing.JLabel gameRandomizedLabel; + private JFileChooser presetFileChooser; + private javax.swing.JButton produceFileButton; + private JTextField randomSeedField; + private javax.swing.JLabel seedFieldLabel; + private javax.swing.JLabel settingsToGiveLabel; + + // End of variables declaration//GEN-END:variables + + public class SelectTextListener implements MouseListener { + + private JTextField fieldFor; + + public SelectTextListener(JTextField fieldFor) { + this.fieldFor = fieldFor; + } + + @Override + public void mouseClicked(MouseEvent arg0) { + // select all text + SwingUtilities.invokeLater(() -> fieldFor.selectAll()); + } + + @Override + public void mouseEntered(MouseEvent arg0) { + // do nothing + + } + + @Override + public void mouseExited(MouseEvent arg0) { + // do nothing + + } + + @Override + public void mousePressed(MouseEvent arg0) { + // do nothing + + } + + @Override + public void mouseReleased(MouseEvent arg0) { + // do nothing + + } + + } +} diff --git a/src/com/pkrandom/newgui/QSFileFilter.java b/src/com/pkrandom/newgui/QSFileFilter.java new file mode 100644 index 0000000..cfdb567 --- /dev/null +++ b/src/com/pkrandom/newgui/QSFileFilter.java @@ -0,0 +1,55 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.pkrandom.newgui; + +/*----------------------------------------------------------------------------*/ +/*-- PresetFileFilter.java - a file filter for the "randomization presets" --*/ +/*-- which allow the same random ROM to be produced--*/ +/*-- on demand. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import javax.swing.filechooser.FileFilter; +import java.io.File; + +public class QSFileFilter extends FileFilter { + + @Override + public boolean accept(File arg0) { + if (arg0.isDirectory()) { + return true; // needed to allow directory navigation + } + String filename = arg0.getName(); + if (!filename.contains(".")) { + return false; + } + String extension = arg0.getName().substring(arg0.getName().lastIndexOf('.') + 1); + return extension.toLowerCase().equals("rnqs"); + } + + @Override + public String getDescription() { + return "Randomization Quick Settings (*.rnqs)"; + } + +} diff --git a/src/com/pkrandom/newgui/ROMFilter.java b/src/com/pkrandom/newgui/ROMFilter.java new file mode 100644 index 0000000..68f7c55 --- /dev/null +++ b/src/com/pkrandom/newgui/ROMFilter.java @@ -0,0 +1,56 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.pkrandom.newgui; + +/*----------------------------------------------------------------------------*/ +/*-- ROMFilter.java - a file filter for the various games which can be --*/ +/*-- randomized with this program. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import javax.swing.filechooser.FileFilter; +import java.io.File; + +public class ROMFilter extends FileFilter { + + @Override + public boolean accept(File arg0) { + if (arg0.isDirectory()) { + return true; // needed to allow directory navigation + } + String filename = arg0.getName(); + if (!filename.contains(".")) { + return false; + } + String extension = arg0.getName().substring(arg0.getName().lastIndexOf('.') + 1).toLowerCase(); + return extension.equals("gb") || extension.equals("sgb") || extension.equals("gbc") + || extension.equals("gba") || extension.equals("nds") + || extension.equals("3ds") || extension.equals("cci") || extension.equals("cxi") || extension.equals("cia") || extension.equals("app"); + } + + @Override + public String getDescription() { + return "Nintendo GB(C/A)/(3)DS ROM File (*.gb,*.sgb,*.gbc,*.gba,*.nds,*.3ds,*.cci,*.cxi,*.cia,*.app)"; + } + +} diff --git a/src/com/pkrandom/newgui/emptyIcon.png b/src/com/pkrandom/newgui/emptyIcon.png new file mode 100644 index 0000000..d21ab66 Binary files /dev/null and b/src/com/pkrandom/newgui/emptyIcon.png differ diff --git a/src/com/pkrandom/newgui/loading.gif b/src/com/pkrandom/newgui/loading.gif new file mode 100644 index 0000000..3288d10 Binary files /dev/null and b/src/com/pkrandom/newgui/loading.gif differ diff --git a/src/com/pkrandom/newnds/CRC16.java b/src/com/pkrandom/newnds/CRC16.java new file mode 100755 index 0000000..8ae8ad2 --- /dev/null +++ b/src/com/pkrandom/newnds/CRC16.java @@ -0,0 +1,52 @@ +package com.pkrandom.newnds; + +/*----------------------------------------------------------------------------*/ +/*-- CRC16.java - crc16 calculator for NDS checksums --*/ +/*-- Code derived from "Nintendo DS rom tool", copyright (C) DevkitPro --*/ +/*-- Original Code by Rafael Vuijk, Dave Murphy, Alexei Karpenko --*/ +/*-- --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class CRC16 { + private static final int[] table = { 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, + 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, + 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, + 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, + 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, + 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, + 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, + 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, + 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, + 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, + 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, + 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, + 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, + 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, + 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, + 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, + 0x5500, 0x95C1, 0x9481, 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, + 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, + 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, + 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 }; + + public static short calculate(byte[] data, int offset, int length) { + int crc = 0xFFFF; + for (int i = 0; i < length; i++) { + crc = ((crc >>> 8) ^ table[(crc ^ data[i + offset]) & 0xff]); + } + return (short) crc; + } +} \ No newline at end of file diff --git a/src/com/pkrandom/newnds/NARCArchive.java b/src/com/pkrandom/newnds/NARCArchive.java new file mode 100644 index 0000000..dd6c239 --- /dev/null +++ b/src/com/pkrandom/newnds/NARCArchive.java @@ -0,0 +1,221 @@ +package com.pkrandom.newnds; + +/*----------------------------------------------------------------------------*/ +/*-- NARCArchive.java - class for packing/unpacking GARC archives --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class NARCArchive { + + private List filenames = new ArrayList<>(); + public List files = new ArrayList<>(); + + private boolean hasFilenames = false; + + public NARCArchive() { + // creates a new empty NARC with no filenames by default + } + + public NARCArchive(byte[] data) throws IOException { + Map frames = readNitroFrames(data); + if (!frames.containsKey("FATB") || !frames.containsKey("FNTB") || !frames.containsKey("FIMG")) { + throw new IOException("Not a valid narc file"); + } + + // File contents + byte[] fatbframe = frames.get("FATB"); + byte[] fimgframe = frames.get("FIMG"); + int fileCount = readLong(fatbframe, 0); + for (int i = 0; i < fileCount; i++) { + int startOffset = readLong(fatbframe, 4 + i * 8); + int endOffset = readLong(fatbframe, 8 + i * 8); + int length = (endOffset - startOffset); + byte[] thisFile = new byte[length]; + System.arraycopy(fimgframe, startOffset, thisFile, 0, length); + files.add(thisFile); + } + + // Filenames? + byte[] fntbframe = frames.get("FNTB"); + int unk1 = readLong(fntbframe, 0); + if (unk1 == 8) { + // Filenames exist + hasFilenames = true; + int offset = 8; + for (int i = 0; i < fileCount; i++) { + int fnLength = (fntbframe[offset] & 0xFF); + offset++; + byte[] filenameBA = new byte[fnLength]; + System.arraycopy(fntbframe, offset, filenameBA, 0, fnLength); + String filename = new String(filenameBA, "US-ASCII"); + filenames.add(filename); + } + } else { + hasFilenames = false; + for (int i = 0; i < fileCount; i++) { + filenames.add(null); + } + } + } + + public byte[] getBytes() throws IOException { + // Get bytes required for FIMG frame + int bytesRequired = 0; + for (byte[] file : files) { + bytesRequired += Math.ceil(file.length / 4.0) * 4; + } + // FIMG frame & FATB frame build + + // 4 for numentries, 8*size for entries, 8 for nitro header + byte[] fatbFrame = new byte[4 + files.size() * 8 + 8]; + // bytesRequired + 8 for nitro header + byte[] fimgFrame = new byte[bytesRequired + 8]; + + // Nitro headers + fatbFrame[0] = 'B'; + fatbFrame[1] = 'T'; + fatbFrame[2] = 'A'; + fatbFrame[3] = 'F'; + writeLong(fatbFrame, 4, fatbFrame.length); + + fimgFrame[0] = 'G'; + fimgFrame[1] = 'M'; + fimgFrame[2] = 'I'; + fimgFrame[3] = 'F'; + writeLong(fimgFrame, 4, fimgFrame.length); + int offset = 0; + + writeLong(fatbFrame, 8, files.size()); + for (int i = 0; i < files.size(); i++) { + byte[] file = files.get(i); + int bytesRequiredForFile = (int) (Math.ceil(file.length / 4.0) * 4); + System.arraycopy(file, 0, fimgFrame, offset + 8, file.length); + for (int filler = file.length; filler < bytesRequiredForFile; filler++) { + fimgFrame[offset + 8 + filler] = (byte) 0xFF; + } + writeLong(fatbFrame, 12 + i * 8, offset); + writeLong(fatbFrame, 16 + i * 8, offset + file.length); + offset += bytesRequiredForFile; + } + + // FNTB Frame + int bytesForFNTBFrame = 16; + if (hasFilenames) { + for (String filename : filenames) { + bytesForFNTBFrame += filename.getBytes("US-ASCII").length + 1; + } + } + byte[] fntbFrame = new byte[bytesForFNTBFrame]; + + fntbFrame[0] = 'B'; + fntbFrame[1] = 'T'; + fntbFrame[2] = 'N'; + fntbFrame[3] = 'F'; + writeLong(fntbFrame, 4, fntbFrame.length); + + if (hasFilenames) { + writeLong(fntbFrame, 8, 8); + writeLong(fntbFrame, 12, 0x10000); + int fntbOffset = 16; + for (String filename : filenames) { + byte[] fntbfilename = filename.getBytes("US-ASCII"); + fntbFrame[fntbOffset] = (byte) fntbfilename.length; + System.arraycopy(fntbfilename, 0, fntbFrame, fntbOffset + 1, fntbfilename.length); + fntbOffset += 1 + fntbfilename.length; + } + } else { + writeLong(fntbFrame, 8, 4); + writeLong(fntbFrame, 12, 0x10000); + } + + // Now for the actual Nitro file + int nitrolength = 16 + fatbFrame.length + fntbFrame.length + fimgFrame.length; + byte[] nitroFile = new byte[nitrolength]; + nitroFile[0] = 'N'; + nitroFile[1] = 'A'; + nitroFile[2] = 'R'; + nitroFile[3] = 'C'; + writeWord(nitroFile, 4, 0xFFFE); + writeWord(nitroFile, 6, 0x0100); + writeLong(nitroFile, 8, nitrolength); + writeWord(nitroFile, 12, 0x10); + writeWord(nitroFile, 14, 3); + System.arraycopy(fatbFrame, 0, nitroFile, 16, fatbFrame.length); + System.arraycopy(fntbFrame, 0, nitroFile, 16 + fatbFrame.length, fntbFrame.length); + System.arraycopy(fimgFrame, 0, nitroFile, 16 + fatbFrame.length + fntbFrame.length, fimgFrame.length); + + return nitroFile; + } + + private Map readNitroFrames(byte[] data) throws IOException { + + // Read the number of frames + int frameCount = readWord(data, 0x0E); + + // each frame + int offset = 0x10; + Map frames = new TreeMap<>(); + for (int i = 0; i < frameCount; i++) { + byte[] magic = new byte[] { data[offset + 3], data[offset + 2], data[offset + 1], data[offset] }; + String magicS = new String(magic, "US-ASCII"); + + int frame_size = readLong(data, offset + 4); + // Patch for BB/VW and other DS hacks which don't update + // the size of their expanded NARCs correctly + if (i == frameCount - 1 && offset + frame_size < data.length) { + frame_size = data.length - offset; + } + byte[] frame = new byte[frame_size - 8]; + System.arraycopy(data, offset + 8, frame, 0, frame_size - 8); + frames.put(magicS, frame); + offset += frame_size; + } + return frames; + } + + private int readWord(byte[] data, int offset) { + return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8); + } + + private int readLong(byte[] data, int offset) { + return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) | ((data[offset + 2] & 0xFF) << 16) + | ((data[offset + 3] & 0xFF) << 24); + } + + private void writeWord(byte[] data, int offset, int value) { + data[offset] = (byte) (value & 0xFF); + data[offset + 1] = (byte) ((value >> 8) & 0xFF); + } + + private void writeLong(byte[] data, int offset, int value) { + data[offset] = (byte) (value & 0xFF); + data[offset + 1] = (byte) ((value >> 8) & 0xFF); + data[offset + 2] = (byte) ((value >> 16) & 0xFF); + data[offset + 3] = (byte) ((value >> 24) & 0xFF); + } + +} diff --git a/src/com/pkrandom/newnds/NDSFile.java b/src/com/pkrandom/newnds/NDSFile.java new file mode 100755 index 0000000..c3564f2 --- /dev/null +++ b/src/com/pkrandom/newnds/NDSFile.java @@ -0,0 +1,118 @@ +package com.pkrandom.newnds; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; + +import com.pkrandom.FileFunctions; + +/*----------------------------------------------------------------------------*/ +/*-- NDSFile.java - an entry in the FAT/FNT filesystem --*/ +/*-- Code based on "Nintendo DS rom tool", copyright (C) DevkitPro --*/ +/*-- Original Code by Rafael Vuijk, Dave Murphy, Alexei Karpenko --*/ +/*-- --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class NDSFile { + + private NDSRom parent; + public int offset, size; + public int fileID; + public String fullPath; + private Extracted status = Extracted.NOT; + private String extFilename; + public byte[] data; + public long originalCRC; + + public NDSFile(NDSRom parent) { + this.parent = parent; + } + + public byte[] getContents() throws IOException { + if (this.status == Extracted.NOT) { + // extract file + parent.reopenROM(); + RandomAccessFile rom = parent.getBaseRom(); + byte[] buf = new byte[this.size]; + rom.seek(this.offset); + rom.readFully(buf); + originalCRC = FileFunctions.getCRC32(buf); + if (parent.isWritingEnabled()) { + // make a file + String tmpDir = parent.getTmpFolder(); + this.extFilename = fullPath.replaceAll("[^A-Za-z0-9_]+", ""); + File tmpFile = new File(tmpDir + extFilename); + FileOutputStream fos = new FileOutputStream(tmpFile); + fos.write(buf); + fos.close(); + tmpFile.deleteOnExit(); + this.status = Extracted.TO_FILE; + this.data = null; + return buf; + } else { + this.status = Extracted.TO_RAM; + this.data = buf; + byte[] newcopy = new byte[buf.length]; + System.arraycopy(buf, 0, newcopy, 0, buf.length); + return newcopy; + } + } else if (this.status == Extracted.TO_RAM) { + byte[] newcopy = new byte[this.data.length]; + System.arraycopy(this.data, 0, newcopy, 0, this.data.length); + return newcopy; + } else { + String tmpDir = parent.getTmpFolder(); + return FileFunctions.readFileFullyIntoBuffer(tmpDir + this.extFilename); + } + } + + public void writeOverride(byte[] data) throws IOException { + if (status == Extracted.NOT) { + // temp extract + getContents(); + } + if (status == Extracted.TO_FILE) { + String tmpDir = parent.getTmpFolder(); + FileOutputStream fos = new FileOutputStream(new File(tmpDir + this.extFilename)); + fos.write(data); + fos.close(); + } else { + if (this.data.length == data.length) { + // copy new in + System.arraycopy(data, 0, this.data, 0, data.length); + } else { + // make new array + this.data = null; + this.data = new byte[data.length]; + System.arraycopy(data, 0, this.data, 0, data.length); + } + } + } + + // returns null if no override + public byte[] getOverrideContents() throws IOException { + if (status == Extracted.NOT) { + return null; + } + return getContents(); + } + + private enum Extracted { + NOT, TO_FILE, TO_RAM + } + +} diff --git a/src/com/pkrandom/newnds/NDSRom.java b/src/com/pkrandom/newnds/NDSRom.java new file mode 100755 index 0000000..fee90a1 --- /dev/null +++ b/src/com/pkrandom/newnds/NDSRom.java @@ -0,0 +1,712 @@ +package com.pkrandom.newnds; + +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +import com.pkrandom.SysConstants; +import com.pkrandom.FileFunctions; +import com.pkrandom.RomFunctions; + +import com.pkrandom.exceptions.CannotWriteToLocationException; +import com.pkrandom.exceptions.RandomizerIOException; +import cuecompressors.BLZCoder; + +/*----------------------------------------------------------------------------*/ +/*-- NDSRom.java - base class for opening/saving ROMs --*/ +/*-- Code based on "Nintendo DS rom tool", copyright (C) DevkitPro --*/ +/*-- Original Code by Rafael Vuijk, Dave Murphy, Alexei Karpenko --*/ +/*-- --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class NDSRom { + + private String romCode; + private byte version; + private String romFilename; + private RandomAccessFile baseRom; + private boolean romOpen; + private Map files; + private Map filesByID; + private Map arm9overlaysByFileID; + private NDSY9Entry[] arm9overlays; + private byte[] fat; + private String tmpFolder; + private boolean writingEnabled; + private boolean arm9_open, arm9_changed, arm9_has_footer; + private boolean arm9_compressed; + private int arm9_ramoffset; + private int arm9_szoffset; + private byte[] arm9_footer; + private byte[] arm9_ramstored; + private long originalArm9CRC; + + private static final int arm9_align = 0x1FF, arm7_align = 0x1FF; + private static final int fnt_align = 0x1FF, fat_align = 0x1FF; + private static final int banner_align = 0x1FF, file_align = 0x1FF; + + public NDSRom(String filename) throws IOException { + this.romFilename = filename; + this.baseRom = new RandomAccessFile(filename, "r"); + this.romOpen = true; + // TMP folder? + String rawFilename = new File(filename).getName(); + String dataFolder = "tmp_" + rawFilename.substring(0, rawFilename.lastIndexOf('.')); + // remove nonsensical chars + dataFolder = dataFolder.replaceAll("[^A-Za-z0-9_]+", ""); + File tmpFolder = new File(SysConstants.ROOT_PATH + dataFolder); + tmpFolder.mkdir(); + if (tmpFolder.canWrite()) { + writingEnabled = true; + this.tmpFolder = SysConstants.ROOT_PATH + dataFolder + File.separator; + tmpFolder.deleteOnExit(); + } else { + writingEnabled = false; + } + readFileSystem(); + arm9_open = false; + arm9_changed = false; + arm9_ramstored = null; + } + + public void reopenROM() throws IOException { + if (!this.romOpen) { + this.baseRom = new RandomAccessFile(this.romFilename, "r"); + this.romOpen = true; + } + } + + public void closeROM() throws IOException { + if (this.romOpen && this.baseRom != null) { + this.baseRom.close(); + this.baseRom = null; + this.romOpen = false; + } + } + + private void readFileSystem() throws IOException { + // read rom code + baseRom.seek(0x0C); + + byte[] sig = new byte[4]; + baseRom.readFully(sig); + this.romCode = new String(sig, "US-ASCII"); + + baseRom.seek(0x1E); + this.version = baseRom.readByte(); + + baseRom.seek(0x28); + this.arm9_ramoffset = readFromFile(baseRom, 4); + + baseRom.seek(0x40); + int fntOffset = readFromFile(baseRom, 4); + readFromFile(baseRom, 4); // fntSize not needed + int fatOffset = readFromFile(baseRom, 4); + int fatSize = readFromFile(baseRom, 4); + + // Read full FAT table + baseRom.seek(fatOffset); + fat = new byte[fatSize]; + baseRom.readFully(fat); + + Map directoryPaths = new HashMap<>(); + directoryPaths.put(0xF000, ""); + int dircount = readFromFile(baseRom, fntOffset + 0x6, 2); + files = new HashMap<>(); + filesByID = new HashMap<>(); + + // read fnt table + baseRom.seek(fntOffset); + int[] subTableOffsets = new int[dircount]; + int[] firstFileIDs = new int[dircount]; + int[] parentDirIDs = new int[dircount]; + for (int i = 0; i < dircount && i < 0x1000; i++) { + subTableOffsets[i] = readFromFile(baseRom, 4) + fntOffset; + firstFileIDs[i] = readFromFile(baseRom, 2); + parentDirIDs[i] = readFromFile(baseRom, 2); + } + + // get dirnames + String[] directoryNames = new String[dircount]; + Map filenames = new TreeMap<>(); + Map fileDirectories = new HashMap<>(); + for (int i = 0; i < dircount && i < 0x1000; i++) { + firstPassDirectory(i, subTableOffsets[i], firstFileIDs[i], directoryNames, filenames, fileDirectories); + } + + // get full dirnames + for (int i = 1; i < dircount && i < 0x1000; i++) { + String dirname = directoryNames[i]; + if (dirname != null) { + StringBuilder fullDirName = new StringBuilder(); + int curDir = i; + while (dirname != null && !dirname.isEmpty()) { + if (fullDirName.length() > 0) { + fullDirName.insert(0, "/"); + } + fullDirName.insert(0, dirname); + int parentDir = parentDirIDs[curDir]; + if (parentDir >= 0xF001 && parentDir <= 0xFFFF) { + curDir = parentDir - 0xF000; + dirname = directoryNames[curDir]; + } else { + break; + } + } + directoryPaths.put(i + 0xF000, fullDirName.toString()); + } else { + directoryPaths.put(i + 0xF000, ""); + } + } + + // parse files + for (int fileID : filenames.keySet()) { + String filename = filenames.get(fileID); + int directory = fileDirectories.get(fileID); + String dirPath = directoryPaths.get(directory + 0xF000); + String fullFilename = filename; + if (!dirPath.isEmpty()) { + fullFilename = dirPath + "/" + filename; + } + NDSFile nf = new NDSFile(this); + int start = readFromByteArr(fat, fileID * 8, 4); + int end = readFromByteArr(fat, fileID * 8 + 4, 4); + nf.offset = start; + nf.size = end - start; + nf.fullPath = fullFilename; + nf.fileID = fileID; + files.put(fullFilename, nf); + filesByID.put(fileID, nf); + } + + // arm9 overlays + int arm9_ovl_table_offset = readFromFile(baseRom, 0x50, 4); + int arm9_ovl_table_size = readFromFile(baseRom, 0x54, 4); + int arm9_ovl_count = arm9_ovl_table_size / 32; + byte[] y9table = new byte[arm9_ovl_table_size]; + arm9overlays = new NDSY9Entry[arm9_ovl_count]; + arm9overlaysByFileID = new HashMap<>(); + baseRom.seek(arm9_ovl_table_offset); + baseRom.readFully(y9table); + + // parse overlays + for (int i = 0; i < arm9_ovl_count; i++) { + NDSY9Entry overlay = new NDSY9Entry(this); + int fileID = readFromByteArr(y9table, i * 32 + 24, 4); + int start = readFromByteArr(fat, fileID * 8, 4); + int end = readFromByteArr(fat, fileID * 8 + 4, 4); + overlay.offset = start; + overlay.size = end - start; + overlay.original_size = end - start; + overlay.fileID = fileID; + overlay.overlay_id = i; + overlay.ram_address = readFromByteArr(y9table, i * 32 + 4, 4); + overlay.ram_size = readFromByteArr(y9table, i * 32 + 8, 4); + overlay.bss_size = readFromByteArr(y9table, i * 32 + 12, 4); + overlay.static_start = readFromByteArr(y9table, i * 32 + 16, 4); + overlay.static_end = readFromByteArr(y9table, i * 32 + 20, 4); + overlay.compressed_size = readFromByteArr(y9table, i * 32 + 28, 3); + overlay.compress_flag = y9table[i * 32 + 31] & 0xFF; + arm9overlays[i] = overlay; + arm9overlaysByFileID.put(fileID, overlay); + } + } + + public void saveTo(String filename) throws IOException { + this.reopenROM(); + + // Initialize new ROM + RandomAccessFile fNew = new RandomAccessFile(filename, "rw"); + + int headersize = readFromFile(this.baseRom, 0x84, 4); + this.baseRom.seek(0); + copy(this.baseRom, fNew, headersize); + + // arm9 + int arm9_offset = ((int) (fNew.getFilePointer() + arm9_align)) & (~arm9_align); + int old_arm9_offset = readFromFile(this.baseRom, 0x20, 4); + int arm9_size = readFromFile(this.baseRom, 0x2C, 4); + if (arm9_open && arm9_changed) { + // custom arm9 + byte[] newARM9 = getARM9(); + if (arm9_compressed) { + newARM9 = new BLZCoder(null).BLZ_EncodePub(newARM9, true, false, "arm9.bin"); + if (arm9_szoffset > 0) { + int newValue = newARM9.length + arm9_ramoffset; + writeToByteArr(newARM9, arm9_szoffset, 4, newValue); + } + } + arm9_size = newARM9.length; + // copy new arm9 + fNew.seek(arm9_offset); + fNew.write(newARM9); + // footer? + if (arm9_has_footer) { + fNew.write(arm9_footer); + } + + } else { + // copy arm9+footer + this.baseRom.seek(old_arm9_offset); + fNew.seek(arm9_offset); + copy(this.baseRom, fNew, arm9_size + 12); + } + + // arm9 ovl + int arm9_ovl_offset = (int) fNew.getFilePointer(); + int arm9_ovl_size = arm9overlays.length * 32; + + // don't actually write arm9 ovl yet + + // arm7 + int arm7_offset = arm9_ovl_offset + arm9_ovl_size + arm7_align & (~arm7_align); + int old_arm7_offset = readFromFile(this.baseRom, 0x30, 4); + int arm7_size = readFromFile(this.baseRom, 0x3C, 4); + // copy arm7 + this.baseRom.seek(old_arm7_offset); + fNew.seek(arm7_offset); + copy(this.baseRom, fNew, arm7_size); + + // arm7 ovl + int arm7_ovl_offset = (int) fNew.getFilePointer(); + int old_arm7_ovl_offset = readFromFile(this.baseRom, 0x58, 4); + int arm7_ovl_size = readFromFile(this.baseRom, 0x5C, 4); + + // copy arm7 ovl + this.baseRom.seek(old_arm7_ovl_offset); + fNew.seek(arm7_ovl_offset); + copy(this.baseRom, fNew, arm7_ovl_size); + + // banner + int banner_offset = ((int) (fNew.getFilePointer() + banner_align)) & (~banner_align); + int old_banner_offset = readFromFile(this.baseRom, 0x68, 4); + int banner_size = 0x840; + // copy banner + this.baseRom.seek(old_banner_offset); + fNew.seek(banner_offset); + copy(this.baseRom, fNew, banner_size); + + // filename table (doesn't change) + int fnt_offset = ((int) (fNew.getFilePointer() + fnt_align)) & (~fnt_align); + int old_fnt_offset = readFromFile(this.baseRom, 0x40, 4); + int fnt_size = readFromFile(this.baseRom, 0x44, 4); + // copy fnt + this.baseRom.seek(old_fnt_offset); + fNew.seek(fnt_offset); + copy(this.baseRom, fNew, fnt_size); + + // make space for the FAT table + int fat_offset = ((int) (fNew.getFilePointer() + fat_align)) & (~fat_align); + int fat_size = fat.length; + + // Now for actual files + // Make a new FAT as needed + // also make a new y9 table + byte[] newfat = new byte[fat.length]; + byte[] y9table = new byte[arm9overlays.length * 32]; + int base_offset = fat_offset + fat_size; + int filecount = fat.length / 8; + for (int fid = 0; fid < filecount; fid++) { + int offset_of_file = (base_offset + file_align) & (~file_align); + int file_len = 0; + boolean copiedCustom = false; + if (filesByID.containsKey(fid)) { + byte[] customContents = filesByID.get(fid).getOverrideContents(); + if (customContents != null) { + // copy custom + fNew.seek(offset_of_file); + fNew.write(customContents); + copiedCustom = true; + file_len = customContents.length; + } + } + if (arm9overlaysByFileID.containsKey(fid)) { + NDSY9Entry entry = arm9overlaysByFileID.get(fid); + int overlay_id = entry.overlay_id; + byte[] customContents = entry.getOverrideContents(); + if (customContents != null) { + // copy custom + fNew.seek(offset_of_file); + fNew.write(customContents); + copiedCustom = true; + file_len = customContents.length; + } + // regardless, fill in y9 table + writeToByteArr(y9table, overlay_id * 32, 4, overlay_id); + writeToByteArr(y9table, overlay_id * 32 + 4, 4, entry.ram_address); + writeToByteArr(y9table, overlay_id * 32 + 8, 4, entry.ram_size); + writeToByteArr(y9table, overlay_id * 32 + 12, 4, entry.bss_size); + writeToByteArr(y9table, overlay_id * 32 + 16, 4, entry.static_start); + writeToByteArr(y9table, overlay_id * 32 + 20, 4, entry.static_end); + writeToByteArr(y9table, overlay_id * 32 + 24, 4, fid); + writeToByteArr(y9table, overlay_id * 32 + 28, 3, entry.compressed_size); + writeToByteArr(y9table, overlay_id * 32 + 31, 1, entry.compress_flag); + } + if (!copiedCustom) { + // copy from original ROM + int file_starts = readFromByteArr(fat, fid * 8, 4); + int file_ends = readFromByteArr(fat, fid * 8 + 4, 4); + file_len = file_ends - file_starts; + this.baseRom.seek(file_starts); + fNew.seek(offset_of_file); + copy(this.baseRom, fNew, file_len); + } + // write to new FAT + writeToByteArr(newfat, fid * 8, 4, offset_of_file); + writeToByteArr(newfat, fid * 8 + 4, 4, offset_of_file + file_len); + // update base_offset + base_offset = offset_of_file + file_len; + } + + // write new FAT table + fNew.seek(fat_offset); + fNew.write(newfat); + + // write y9 table + fNew.seek(arm9_ovl_offset); + fNew.write(y9table); + + // tidy up ending + // base_offset is the end of the last file + int newfilesize = base_offset; + newfilesize = (newfilesize + 3) & ~3; + int application_end_offset = newfilesize; + if (newfilesize != base_offset) { + fNew.seek(newfilesize - 1); + fNew.write(0); + } + + // calculate device capacity; + newfilesize |= newfilesize >> 16; + newfilesize |= newfilesize >> 8; + newfilesize |= newfilesize >> 4; + newfilesize |= newfilesize >> 2; + newfilesize |= newfilesize >> 1; + newfilesize++; + if (newfilesize <= 128 * 1024) { + newfilesize = 128 * 1024; + } + int devcap = -18; + int x = newfilesize; + while (x != 0) { + x >>= 1; + devcap++; + } + int devicecap = ((devcap < 0) ? 0 : devcap); + + // Update offsets in ROM header + writeToFile(fNew, 0x20, 4, arm9_offset); + writeToFile(fNew, 0x2C, 4, arm9_size); + writeToFile(fNew, 0x30, 4, arm7_offset); + writeToFile(fNew, 0x3C, 4, arm7_size); + writeToFile(fNew, 0x40, 4, fnt_offset); + writeToFile(fNew, 0x48, 4, fat_offset); + writeToFile(fNew, 0x50, 4, arm9_ovl_offset); + writeToFile(fNew, 0x58, 4, arm7_ovl_offset); + writeToFile(fNew, 0x68, 4, banner_offset); + writeToFile(fNew, 0x80, 4, application_end_offset); + writeToFile(fNew, 0x14, 1, devicecap); + + // Update header CRC + fNew.seek(0); + byte[] headerForCRC = new byte[0x15E]; + fNew.readFully(headerForCRC); + short crc = CRC16.calculate(headerForCRC, 0, 0x15E); + writeToFile(fNew, 0x15E, 2, (crc & 0xFFFF)); + + // done + fNew.close(); + closeROM(); + } + + private void copy(RandomAccessFile from, RandomAccessFile to, int bytes) throws IOException { + int sizeof_copybuf = Math.min(256 * 1024, bytes); + byte[] copybuf = new byte[sizeof_copybuf]; + while (bytes > 0) { + int size2 = (bytes >= sizeof_copybuf) ? sizeof_copybuf : bytes; + int read = from.read(copybuf, 0, size2); + to.write(copybuf, 0, read); + bytes -= read; + } + } + + // get rom code for opened rom + public String getCode() { + return this.romCode; + } + + public byte getVersion() { + return this.version; + } + + // returns null if file doesn't exist + public byte[] getFile(String filename) throws IOException { + if (files.containsKey(filename)) { + return files.get(filename).getContents(); + } else { + return null; + } + } + + public byte[] getOverlay(int number) throws IOException { + if (number >= 0 && number < arm9overlays.length) { + return arm9overlays[number].getContents(); + } else { + return null; + } + } + + public int getOverlayAddress(int number) { + if (number >= 0 && number < arm9overlays.length) { + return arm9overlays[number].ram_address; + } else { + return -1; + } + } + + public byte[] getARM9() throws IOException { + if (!arm9_open) { + arm9_open = true; + this.reopenROM(); + int arm9_offset = readFromFile(this.baseRom, 0x20, 4); + int arm9_size = readFromFile(this.baseRom, 0x2C, 4); + byte[] arm9 = new byte[arm9_size]; + this.baseRom.seek(arm9_offset); + this.baseRom.readFully(arm9); + originalArm9CRC = FileFunctions.getCRC32(arm9); + // footer check + int nitrocode = readFromFile(this.baseRom, 4); + if (nitrocode == 0xDEC00621) { + // found a footer + arm9_footer = new byte[12]; + writeToByteArr(arm9_footer, 0, 4, 0xDEC00621); + this.baseRom.readFully(arm9_footer, 4, 8); + arm9_has_footer = true; + } else { + arm9_has_footer = false; + } + // Any extras? + while ((readFromByteArr(arm9, arm9.length - 12, 4) == 0xDEC00621) + || ((readFromByteArr(arm9, arm9.length - 12, 4) == 0 + && readFromByteArr(arm9, arm9.length - 8, 4) == 0 && readFromByteArr(arm9, arm9.length - 4, + 4) == 0))) { + if (!arm9_has_footer) { + arm9_has_footer = true; + arm9_footer = new byte[0]; + } + byte[] newfooter = new byte[arm9_footer.length + 12]; + System.arraycopy(arm9, arm9.length - 12, newfooter, 0, 12); + System.arraycopy(arm9_footer, 0, newfooter, 12, arm9_footer.length); + arm9_footer = newfooter; + byte[] newarm9 = new byte[arm9.length - 12]; + System.arraycopy(arm9, 0, newarm9, 0, arm9.length - 12); + arm9 = newarm9; + } + // Compression? + arm9_compressed = false; + arm9_szoffset = 0; + if (((int) arm9[arm9.length - 5]) >= 0x08 && ((int) arm9[arm9.length - 5]) <= 0x0B) { + int compSize = readFromByteArr(arm9, arm9.length - 8, 3); + if (compSize > (arm9.length * 9 / 10) && compSize < (arm9.length * 11 / 10)) { + arm9_compressed = true; + byte[] compLength = new byte[4]; + writeToByteArr(compLength, 0, 4, arm9.length + arm9_ramoffset); + List foundOffsets = RomFunctions.search(arm9, compLength); + if (foundOffsets.size() == 1) { + arm9_szoffset = foundOffsets.get(0); + } else { + throw new RandomizerIOException("Could not read ARM9 size offset. May be a bad ROM."); + } + } + } + + if (arm9_compressed) { + arm9 = new BLZCoder(null).BLZ_DecodePub(arm9, "arm9.bin"); + } + + // Now actually make the copy or w/e + if (writingEnabled) { + File arm9file = new File(tmpFolder + "arm9.bin"); + FileOutputStream fos = new FileOutputStream(arm9file); + fos.write(arm9); + fos.close(); + arm9file.deleteOnExit(); + this.arm9_ramstored = null; + return arm9; + } else { + this.arm9_ramstored = arm9; + byte[] newcopy = new byte[arm9.length]; + System.arraycopy(arm9, 0, newcopy, 0, arm9.length); + return newcopy; + } + } else { + if (writingEnabled) { + return FileFunctions.readFileFullyIntoBuffer(tmpFolder + "arm9.bin"); + } else { + byte[] newcopy = new byte[this.arm9_ramstored.length]; + System.arraycopy(this.arm9_ramstored, 0, newcopy, 0, this.arm9_ramstored.length); + return newcopy; + } + } + } + + // returns null if file doesn't exist + public void writeFile(String filename, byte[] data) throws IOException { + if (files.containsKey(filename)) { + files.get(filename).writeOverride(data); + } + } + + public void writeOverlay(int number, byte[] data) throws IOException { + if (number >= 0 && number <= arm9overlays.length) { + arm9overlays[number].writeOverride(data); + } + } + + public void writeARM9(byte[] arm9) throws IOException { + if (!arm9_open) { + getARM9(); + } + arm9_changed = true; + if (writingEnabled) { + FileOutputStream fos = new FileOutputStream(new File(tmpFolder + "arm9.bin")); + fos.write(arm9); + fos.close(); + } else { + if (this.arm9_ramstored.length == arm9.length) { + // copy new in + System.arraycopy(arm9, 0, this.arm9_ramstored, 0, arm9.length); + } else { + // make new array + this.arm9_ramstored = null; + this.arm9_ramstored = new byte[arm9.length]; + System.arraycopy(arm9, 0, this.arm9_ramstored, 0, arm9.length); + } + } + } + + private void firstPassDirectory(int dir, int subTableOffset, int firstFileID, String[] directoryNames, + Map filenames, Map fileDirectories) throws IOException { + // read subtable + baseRom.seek(subTableOffset); + while (true) { + int control = baseRom.read(); + if (control == 0x00) { + // done + break; + } + int namelen = control & 0x7F; + byte[] rawname = new byte[namelen]; + baseRom.readFully(rawname); + String name = new String(rawname, "US-ASCII"); + if ((control & 0x80) > 0x00) { + // sub-directory + int subDirectoryID = readFromFile(baseRom, 2); + directoryNames[subDirectoryID - 0xF000] = name; + } else { + int fileID = firstFileID++; + filenames.put(fileID, name); + fileDirectories.put(fileID, dir); + } + } + } + + public void printRomDiagnostics(PrintStream logStream) { + List overlayList = new ArrayList<>(); + List fileList = new ArrayList<>(); + for (Map.Entry entry : arm9overlaysByFileID.entrySet()) { + if (entry.getValue().originalCRC != 0) { + overlayList.add("overlay9_" + entry.getKey() + ": " + String.format("%08X", entry.getValue().originalCRC)); + } + } + for (Map.Entry entry : files.entrySet()) { + if (entry.getValue().originalCRC != 0) { + fileList.add(entry.getKey() + ": " + String.format("%08X", entry.getValue().originalCRC)); + } + } + Collections.sort(overlayList); + Collections.sort(fileList); + Path p = Paths.get(this.romFilename); + logStream.println("File name: " + p.getFileName().toString()); + logStream.println("arm9: " + String.format("%08X", originalArm9CRC)); + for (String overlayLog : overlayList) { + logStream.println(overlayLog); + } + for (String fileLog : fileList) { + logStream.println(fileLog); + } + } + + public String getTmpFolder() { + return tmpFolder; + } + + public RandomAccessFile getBaseRom() { + return baseRom; + } + + public boolean isWritingEnabled() { + return writingEnabled; + } + + private int readFromByteArr(byte[] data, int offset, int size) { + int result = 0; + for (int i = 0; i < size; i++) { + result |= (data[i + offset] & 0xFF) << (i * 8); + } + return result; + } + + private void writeToByteArr(byte[] data, int offset, int size, int value) { + for (int i = 0; i < size; i++) { + data[offset + i] = (byte) ((value >> (i * 8)) & 0xFF); + } + } + + private int readFromFile(RandomAccessFile file, int size) throws IOException { + return readFromFile(file, -1, size); + } + + // use -1 offset to read from current position + // useful if you want to read blocks + private int readFromFile(RandomAccessFile file, int offset, int size) throws IOException { + byte[] buf = new byte[size]; + if (offset >= 0) + file.seek(offset); + file.readFully(buf); + int result = 0; + for (int i = 0; i < size; i++) { + result |= (buf[i] & 0xFF) << (i * 8); + } + return result; + } + + public void writeToFile(RandomAccessFile file, int size, int value) throws IOException { + writeToFile(file, -1, size, value); + } + + private void writeToFile(RandomAccessFile file, int offset, int size, int value) throws IOException { + byte[] buf = new byte[size]; + for (int i = 0; i < size; i++) { + buf[i] = (byte) ((value >> (i * 8)) & 0xFF); + } + if (offset >= 0) + file.seek(offset); + file.write(buf); + } + +} diff --git a/src/com/pkrandom/newnds/NDSY9Entry.java b/src/com/pkrandom/newnds/NDSY9Entry.java new file mode 100755 index 0000000..20c2836 --- /dev/null +++ b/src/com/pkrandom/newnds/NDSY9Entry.java @@ -0,0 +1,139 @@ +package com.pkrandom.newnds; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; + +import com.pkrandom.FileFunctions; + +import cuecompressors.BLZCoder; + +/*----------------------------------------------------------------------------*/ +/*-- NDSY9Entry.java - an entry in the arm9 overlay system --*/ +/*-- Code based on "Nintendo DS rom tool", copyright (C) DevkitPro --*/ +/*-- Original Code by Rafael Vuijk, Dave Murphy, Alexei Karpenko --*/ +/*-- --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class NDSY9Entry { + + private NDSRom parent; + public int offset, size, original_size; + public int fileID; + public int overlay_id; + public int ram_address, ram_size; + public int bss_size; + public int static_start, static_end; + public int compressed_size; + public int compress_flag; + private Extracted status = Extracted.NOT; + private String extFilename; + public byte[] data; + public long originalCRC; + private boolean decompressed_data = false; + + public NDSY9Entry(NDSRom parent) { + this.parent = parent; + } + + public byte[] getContents() throws IOException { + if (this.status == Extracted.NOT) { + // extract file + parent.reopenROM(); + RandomAccessFile rom = parent.getBaseRom(); + byte[] buf = new byte[this.original_size]; + rom.seek(this.offset); + rom.readFully(buf); + originalCRC = FileFunctions.getCRC32(buf); + // Compression? + if (compress_flag != 0 && this.original_size == this.compressed_size && this.compressed_size != 0) { + buf = new BLZCoder(null).BLZ_DecodePub(buf, "overlay " + overlay_id); + decompressed_data = true; + } + if (parent.isWritingEnabled()) { + // make a file + String tmpDir = parent.getTmpFolder(); + String fullPath = String.format("overlay_%04d", overlay_id); + this.extFilename = fullPath.replaceAll("[^A-Za-z0-9_]+", ""); + File tmpFile = new File(tmpDir + extFilename); + FileOutputStream fos = new FileOutputStream(tmpFile); + fos.write(buf); + fos.close(); + tmpFile.deleteOnExit(); + this.status = Extracted.TO_FILE; + this.data = null; + return buf; + } else { + this.status = Extracted.TO_RAM; + this.data = buf; + byte[] newcopy = new byte[buf.length]; + System.arraycopy(buf, 0, newcopy, 0, buf.length); + return newcopy; + } + } else if (this.status == Extracted.TO_RAM) { + byte[] newcopy = new byte[this.data.length]; + System.arraycopy(this.data, 0, newcopy, 0, this.data.length); + return newcopy; + } else { + String tmpDir = parent.getTmpFolder(); + return FileFunctions.readFileFullyIntoBuffer(tmpDir + this.extFilename); + } + } + + public void writeOverride(byte[] data) throws IOException { + if (status == Extracted.NOT) { + // temp extract + getContents(); + } + size = data.length; + if (status == Extracted.TO_FILE) { + String tmpDir = parent.getTmpFolder(); + FileOutputStream fos = new FileOutputStream(new File(tmpDir + this.extFilename)); + fos.write(data); + fos.close(); + } else { + if (this.data.length == data.length) { + // copy new in + System.arraycopy(data, 0, this.data, 0, data.length); + } else { + // make new array + this.data = null; + this.data = new byte[data.length]; + System.arraycopy(data, 0, this.data, 0, data.length); + } + } + } + + // returns null if no override + public byte[] getOverrideContents() throws IOException { + if (status == Extracted.NOT) { + return null; + } + byte[] buf = getContents(); + if (this.decompressed_data) { + buf = new BLZCoder(null).BLZ_EncodePub(buf, false, false, "overlay " + overlay_id); + // update our compressed size + this.compressed_size = buf.length; + } + return buf; + } + + private enum Extracted { + NOT, TO_FILE, TO_RAM + } + +} diff --git a/src/com/pkrandom/patches/bwexp/crystal_en_bwxp.ips b/src/com/pkrandom/patches/bwexp/crystal_en_bwxp.ips new file mode 100644 index 0000000..1286524 Binary files /dev/null and b/src/com/pkrandom/patches/bwexp/crystal_en_bwxp.ips differ diff --git a/src/com/pkrandom/patches/bwexp/gs_en_bwxp.ips b/src/com/pkrandom/patches/bwexp/gs_en_bwxp.ips new file mode 100644 index 0000000..e4bf2e3 Binary files /dev/null and b/src/com/pkrandom/patches/bwexp/gs_en_bwxp.ips differ diff --git a/src/com/pkrandom/patches/bwexp/rb_en_bwxp.ips b/src/com/pkrandom/patches/bwexp/rb_en_bwxp.ips new file mode 100644 index 0000000..e7e929a Binary files /dev/null and b/src/com/pkrandom/patches/bwexp/rb_en_bwxp.ips differ diff --git a/src/com/pkrandom/patches/bwexp/yellow_en_bwxp.ips b/src/com/pkrandom/patches/bwexp/yellow_en_bwxp.ips new file mode 100644 index 0000000..021fa97 Binary files /dev/null and b/src/com/pkrandom/patches/bwexp/yellow_en_bwxp.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/em_firstbattle.ips b/src/com/pkrandom/patches/hardcoded_statics/em_firstbattle.ips new file mode 100644 index 0000000..3b6385d Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/em_firstbattle.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/fr_marowak_10.ips b/src/com/pkrandom/patches/hardcoded_statics/fr_marowak_10.ips new file mode 100644 index 0000000..856297e Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/fr_marowak_10.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/fr_marowak_11.ips b/src/com/pkrandom/patches/hardcoded_statics/fr_marowak_11.ips new file mode 100644 index 0000000..797d12b Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/fr_marowak_11.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/lg_marowak_10.ips b/src/com/pkrandom/patches/hardcoded_statics/lg_marowak_10.ips new file mode 100644 index 0000000..eacbc91 Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/lg_marowak_10.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/lg_marowak_11.ips b/src/com/pkrandom/patches/hardcoded_statics/lg_marowak_11.ips new file mode 100644 index 0000000..057ec3a Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/lg_marowak_11.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/roamers/fr_roamers_10.ips b/src/com/pkrandom/patches/hardcoded_statics/roamers/fr_roamers_10.ips new file mode 100644 index 0000000..22c093a Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/roamers/fr_roamers_10.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/roamers/fr_roamers_11.ips b/src/com/pkrandom/patches/hardcoded_statics/roamers/fr_roamers_11.ips new file mode 100644 index 0000000..3ccd60f Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/roamers/fr_roamers_11.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/roamers/hgss_roamers.ips b/src/com/pkrandom/patches/hardcoded_statics/roamers/hgss_roamers.ips new file mode 100644 index 0000000..7667156 Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/roamers/hgss_roamers.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/roamers/lg_roamers_10.ips b/src/com/pkrandom/patches/hardcoded_statics/roamers/lg_roamers_10.ips new file mode 100644 index 0000000..03a8e74 Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/roamers/lg_roamers_10.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/roamers/lg_roamers_11.ips b/src/com/pkrandom/patches/hardcoded_statics/roamers/lg_roamers_11.ips new file mode 100644 index 0000000..c7ed279 Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/roamers/lg_roamers_11.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/roamers/plat_roamers.ips b/src/com/pkrandom/patches/hardcoded_statics/roamers/plat_roamers.ips new file mode 100644 index 0000000..0dd15fe Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/roamers/plat_roamers.ips differ diff --git a/src/com/pkrandom/patches/hardcoded_statics/rs_firstbattle.ips b/src/com/pkrandom/patches/hardcoded_statics/rs_firstbattle.ips new file mode 100644 index 0000000..4d9fd8c Binary files /dev/null and b/src/com/pkrandom/patches/hardcoded_statics/rs_firstbattle.ips differ diff --git a/src/com/pkrandom/patches/hgss_catching_tutorialfix.ips b/src/com/pkrandom/patches/hgss_catching_tutorialfix.ips new file mode 100644 index 0000000..ec5932d Binary files /dev/null and b/src/com/pkrandom/patches/hgss_catching_tutorialfix.ips differ diff --git a/src/com/pkrandom/patches/instant_text/b1_instant_text.ips b/src/com/pkrandom/patches/instant_text/b1_instant_text.ips new file mode 100644 index 0000000..fc140b0 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/b1_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/b2_instant_text.ips b/src/com/pkrandom/patches/instant_text/b2_instant_text.ips new file mode 100644 index 0000000..8c3ad4f Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/b2_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/dp_instant_text.ips b/src/com/pkrandom/patches/instant_text/dp_instant_text.ips new file mode 100644 index 0000000..82dc56c Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/dp_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/em_instant_text.ips b/src/com/pkrandom/patches/instant_text/em_instant_text.ips new file mode 100644 index 0000000..40c11a5 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/em_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/fr_10_instant_text.ips b/src/com/pkrandom/patches/instant_text/fr_10_instant_text.ips new file mode 100644 index 0000000..39a8907 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/fr_10_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/fr_11_instant_text.ips b/src/com/pkrandom/patches/instant_text/fr_11_instant_text.ips new file mode 100644 index 0000000..6f17ea8 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/fr_11_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/hgss_instant_text.ips b/src/com/pkrandom/patches/instant_text/hgss_instant_text.ips new file mode 100644 index 0000000..7352d3e Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/hgss_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/lg_10_instant_text.ips b/src/com/pkrandom/patches/instant_text/lg_10_instant_text.ips new file mode 100644 index 0000000..4236c85 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/lg_10_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/lg_11_instant_text.ips b/src/com/pkrandom/patches/instant_text/lg_11_instant_text.ips new file mode 100644 index 0000000..5138d23 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/lg_11_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/plat_instant_text.ips b/src/com/pkrandom/patches/instant_text/plat_instant_text.ips new file mode 100644 index 0000000..a7c63d0 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/plat_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/ruby_10_instant_text.ips b/src/com/pkrandom/patches/instant_text/ruby_10_instant_text.ips new file mode 100644 index 0000000..43c46a4 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/ruby_10_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/ruby_11_instant_text.ips b/src/com/pkrandom/patches/instant_text/ruby_11_instant_text.ips new file mode 100644 index 0000000..f03925e Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/ruby_11_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/ruby_12_instant_text.ips b/src/com/pkrandom/patches/instant_text/ruby_12_instant_text.ips new file mode 100644 index 0000000..f03925e Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/ruby_12_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/sapphire_10_instant_text.ips b/src/com/pkrandom/patches/instant_text/sapphire_10_instant_text.ips new file mode 100644 index 0000000..e0a33a8 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/sapphire_10_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/sapphire_11_instant_text.ips b/src/com/pkrandom/patches/instant_text/sapphire_11_instant_text.ips new file mode 100644 index 0000000..0fe1ab2 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/sapphire_11_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/sapphire_12_instant_text.ips b/src/com/pkrandom/patches/instant_text/sapphire_12_instant_text.ips new file mode 100644 index 0000000..0fe1ab2 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/sapphire_12_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/w1_instant_text.ips b/src/com/pkrandom/patches/instant_text/w1_instant_text.ips new file mode 100644 index 0000000..f633ba9 Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/w1_instant_text.ips differ diff --git a/src/com/pkrandom/patches/instant_text/w2_instant_text.ips b/src/com/pkrandom/patches/instant_text/w2_instant_text.ips new file mode 100644 index 0000000..1c8180b Binary files /dev/null and b/src/com/pkrandom/patches/instant_text/w2_instant_text.ips differ diff --git a/src/com/pkrandom/patches/musicfix/black2_musicfix.ips b/src/com/pkrandom/patches/musicfix/black2_musicfix.ips new file mode 100644 index 0000000..8e6db21 Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/black2_musicfix.ips differ diff --git a/src/com/pkrandom/patches/musicfix/black2_ovl36_musicfix.ips b/src/com/pkrandom/patches/musicfix/black2_ovl36_musicfix.ips new file mode 100644 index 0000000..8cb959d Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/black2_ovl36_musicfix.ips differ diff --git a/src/com/pkrandom/patches/musicfix/black_musicfix.ips b/src/com/pkrandom/patches/musicfix/black_musicfix.ips new file mode 100644 index 0000000..d08f7da Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/black_musicfix.ips differ diff --git a/src/com/pkrandom/patches/musicfix/black_ovl21_musicfix.ips b/src/com/pkrandom/patches/musicfix/black_ovl21_musicfix.ips new file mode 100644 index 0000000..cb48339 Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/black_ovl21_musicfix.ips differ diff --git a/src/com/pkrandom/patches/musicfix/diamond_musicfix.ips b/src/com/pkrandom/patches/musicfix/diamond_musicfix.ips new file mode 100644 index 0000000..fc374b8 Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/diamond_musicfix.ips differ diff --git a/src/com/pkrandom/patches/musicfix/em_musicfix.ips b/src/com/pkrandom/patches/musicfix/em_musicfix.ips new file mode 100644 index 0000000..58be766 Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/em_musicfix.ips differ diff --git a/src/com/pkrandom/patches/musicfix/fr_musicfix_10.ips b/src/com/pkrandom/patches/musicfix/fr_musicfix_10.ips new file mode 100644 index 0000000..adf01b9 Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/fr_musicfix_10.ips differ diff --git a/src/com/pkrandom/patches/musicfix/fr_musicfix_11.ips b/src/com/pkrandom/patches/musicfix/fr_musicfix_11.ips new file mode 100644 index 0000000..67e026c Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/fr_musicfix_11.ips differ diff --git a/src/com/pkrandom/patches/musicfix/lg_musicfix_10.ips b/src/com/pkrandom/patches/musicfix/lg_musicfix_10.ips new file mode 100644 index 0000000..287f26f Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/lg_musicfix_10.ips differ diff --git a/src/com/pkrandom/patches/musicfix/lg_musicfix_11.ips b/src/com/pkrandom/patches/musicfix/lg_musicfix_11.ips new file mode 100644 index 0000000..49be790 Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/lg_musicfix_11.ips differ diff --git a/src/com/pkrandom/patches/musicfix/plat_musicfix.ips b/src/com/pkrandom/patches/musicfix/plat_musicfix.ips new file mode 100644 index 0000000..61f6857 Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/plat_musicfix.ips differ diff --git a/src/com/pkrandom/patches/musicfix/white2_musicfix.ips b/src/com/pkrandom/patches/musicfix/white2_musicfix.ips new file mode 100644 index 0000000..b422ce5 Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/white2_musicfix.ips differ diff --git a/src/com/pkrandom/patches/musicfix/white2_ovl36_musicfix.ips b/src/com/pkrandom/patches/musicfix/white2_ovl36_musicfix.ips new file mode 100644 index 0000000..93e6f67 Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/white2_ovl36_musicfix.ips differ diff --git a/src/com/pkrandom/patches/musicfix/white_musicfix.ips b/src/com/pkrandom/patches/musicfix/white_musicfix.ips new file mode 100644 index 0000000..8991702 Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/white_musicfix.ips differ diff --git a/src/com/pkrandom/patches/musicfix/white_ovl21_musicfix.ips b/src/com/pkrandom/patches/musicfix/white_ovl21_musicfix.ips new file mode 100644 index 0000000..6207258 Binary files /dev/null and b/src/com/pkrandom/patches/musicfix/white_ovl21_musicfix.ips differ diff --git a/src/com/pkrandom/patches/national_dex/bw1_national_dex.ips b/src/com/pkrandom/patches/national_dex/bw1_national_dex.ips new file mode 100644 index 0000000..13afb31 Binary files /dev/null and b/src/com/pkrandom/patches/national_dex/bw1_national_dex.ips differ diff --git a/src/com/pkrandom/patches/national_dex/bw2_national_dex.ips b/src/com/pkrandom/patches/national_dex/bw2_national_dex.ips new file mode 100644 index 0000000..2981e85 Binary files /dev/null and b/src/com/pkrandom/patches/national_dex/bw2_national_dex.ips differ diff --git a/src/com/pkrandom/patches/national_dex/dp_national_dex.ips b/src/com/pkrandom/patches/national_dex/dp_national_dex.ips new file mode 100644 index 0000000..e9aea86 Binary files /dev/null and b/src/com/pkrandom/patches/national_dex/dp_national_dex.ips differ diff --git a/src/com/pkrandom/patches/national_dex/hgss_national_dex.ips b/src/com/pkrandom/patches/national_dex/hgss_national_dex.ips new file mode 100644 index 0000000..0c7d3eb Binary files /dev/null and b/src/com/pkrandom/patches/national_dex/hgss_national_dex.ips differ diff --git a/src/com/pkrandom/patches/national_dex/plat_national_dex.ips b/src/com/pkrandom/patches/national_dex/plat_national_dex.ips new file mode 100644 index 0000000..612f314 Binary files /dev/null and b/src/com/pkrandom/patches/national_dex/plat_national_dex.ips differ diff --git a/src/com/pkrandom/patches/pt_fast_distortion_world.ips b/src/com/pkrandom/patches/pt_fast_distortion_world.ips new file mode 100644 index 0000000..9279b5b Binary files /dev/null and b/src/com/pkrandom/patches/pt_fast_distortion_world.ips differ diff --git a/src/com/pkrandom/patches/rb_en_critrate.ips b/src/com/pkrandom/patches/rb_en_critrate.ips new file mode 100755 index 0000000..f08ef39 Binary files /dev/null and b/src/com/pkrandom/patches/rb_en_critrate.ips differ diff --git a/src/com/pkrandom/patches/rb_en_xaccnerf.ips b/src/com/pkrandom/patches/rb_en_xaccnerf.ips new file mode 100755 index 0000000..426753a Binary files /dev/null and b/src/com/pkrandom/patches/rb_en_xaccnerf.ips differ diff --git a/src/com/pkrandom/patches/shedinja/black2_ovl284_shedinja.ips b/src/com/pkrandom/patches/shedinja/black2_ovl284_shedinja.ips new file mode 100644 index 0000000..3da8290 Binary files /dev/null and b/src/com/pkrandom/patches/shedinja/black2_ovl284_shedinja.ips differ diff --git a/src/com/pkrandom/patches/shedinja/black2_shedinja.ips b/src/com/pkrandom/patches/shedinja/black2_shedinja.ips new file mode 100644 index 0000000..c6eb474 Binary files /dev/null and b/src/com/pkrandom/patches/shedinja/black2_shedinja.ips differ diff --git a/src/com/pkrandom/patches/shedinja/black_ovl195_shedinja.ips b/src/com/pkrandom/patches/shedinja/black_ovl195_shedinja.ips new file mode 100644 index 0000000..b6fd282 Binary files /dev/null and b/src/com/pkrandom/patches/shedinja/black_ovl195_shedinja.ips differ diff --git a/src/com/pkrandom/patches/shedinja/black_shedinja.ips b/src/com/pkrandom/patches/shedinja/black_shedinja.ips new file mode 100644 index 0000000..08acd5e Binary files /dev/null and b/src/com/pkrandom/patches/shedinja/black_shedinja.ips differ diff --git a/src/com/pkrandom/patches/shedinja/white2_ovl284_shedinja.ips b/src/com/pkrandom/patches/shedinja/white2_ovl284_shedinja.ips new file mode 100644 index 0000000..3da8290 Binary files /dev/null and b/src/com/pkrandom/patches/shedinja/white2_ovl284_shedinja.ips differ diff --git a/src/com/pkrandom/patches/shedinja/white2_shedinja.ips b/src/com/pkrandom/patches/shedinja/white2_shedinja.ips new file mode 100644 index 0000000..2901b63 Binary files /dev/null and b/src/com/pkrandom/patches/shedinja/white2_shedinja.ips differ diff --git a/src/com/pkrandom/patches/shedinja/white_ovl195_shedinja.ips b/src/com/pkrandom/patches/shedinja/white_ovl195_shedinja.ips new file mode 100644 index 0000000..b6fd282 Binary files /dev/null and b/src/com/pkrandom/patches/shedinja/white_ovl195_shedinja.ips differ diff --git a/src/com/pkrandom/patches/shedinja/white_shedinja.ips b/src/com/pkrandom/patches/shedinja/white_shedinja.ips new file mode 100644 index 0000000..a00135b Binary files /dev/null and b/src/com/pkrandom/patches/shedinja/white_shedinja.ips differ diff --git a/src/com/pkrandom/patches/yellow_en_critrate.ips b/src/com/pkrandom/patches/yellow_en_critrate.ips new file mode 100755 index 0000000..a691f04 Binary files /dev/null and b/src/com/pkrandom/patches/yellow_en_critrate.ips differ diff --git a/src/com/pkrandom/patches/yellow_en_xaccnerf.ips b/src/com/pkrandom/patches/yellow_en_xaccnerf.ips new file mode 100755 index 0000000..e968abe Binary files /dev/null and b/src/com/pkrandom/patches/yellow_en_xaccnerf.ips differ diff --git a/src/com/pkrandom/pokemon/Aura.java b/src/com/pkrandom/pokemon/Aura.java new file mode 100644 index 0000000..946e844 --- /dev/null +++ b/src/com/pkrandom/pokemon/Aura.java @@ -0,0 +1,80 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- Aura.java - handles the Aura that Totem Pokemon have --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.RomFunctions; + +import java.util.Random; + +public class Aura { + + private enum AuraStat { + NONE, ATTACK, DEFENSE, SPECIAL_ATTACK, SPECIAL_DEFENSE, SPEED, ALL + } + + public AuraStat stat; + + public int stages; + + public Aura(byte b) { + if (b == 0) { + stat = AuraStat.NONE; + stages = 0; + } else { + stat = AuraStat.values()[((b - 1) / 3) + 1]; + stages = ((b - 1) % 3) + 1; + } + } + + private Aura(AuraStat stat, int stages) { + this.stat = stat; + this.stages = stages; + } + + public byte toByte() { + if (stat == AuraStat.NONE) { + return 0; + } else { + return (byte)(((stat.ordinal() - 1) * 3) + (stages)); + } + } + + public static Aura randomAura(Random random) { + return new Aura((byte)(random.nextInt(18) + 1)); + } + + public static Aura randomAuraSimilarStrength(Random random, Aura old) { + if (old.stat == AuraStat.NONE || old.stat == AuraStat.ALL) { + return old; + } else { + return new Aura(AuraStat.values()[random.nextInt(5) + 1], old.stages); + } + } + + @Override + public String toString() { + String ret = RomFunctions.camelCase(stat.toString()).replace("_"," "); + return stat == AuraStat.NONE ? ret : ret + " +" + stages; + } +} diff --git a/src/com/pkrandom/pokemon/CriticalChance.java b/src/com/pkrandom/pokemon/CriticalChance.java new file mode 100644 index 0000000..7fd02a8 --- /dev/null +++ b/src/com/pkrandom/pokemon/CriticalChance.java @@ -0,0 +1,31 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- CriticalChance.java - represents a move's odds to critical hit. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public enum CriticalChance { + NONE, // Phased out in Gen 5+ + NORMAL, + INCREASED, + GUARANTEED +} diff --git a/src/com/pkrandom/pokemon/Effectiveness.java b/src/com/pkrandom/pokemon/Effectiveness.java new file mode 100644 index 0000000..edfd3f3 --- /dev/null +++ b/src/com/pkrandom/pokemon/Effectiveness.java @@ -0,0 +1,178 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- Effectiveness.java - represents a type's effectiveness and the --*/ +/*-- results of applying super effectiveness and resistance --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public enum Effectiveness { + ZERO, HALF, NEUTRAL, DOUBLE, QUARTER, QUADRUPLE; + + public static Map against(Type primaryType, Type secondaryType, int gen) { + return against(primaryType, secondaryType, gen, false); + } + + // Returns a map where the key is a type and the value is the effectiveness against + // a pokemon with the two types in a given gen. It does not account for abilities. + public static Map against(Type primaryType, Type secondaryType, int gen, boolean effectivenessUpdated) { + if (gen >= 2 && gen <= 5) { + if (effectivenessUpdated) { + return against(primaryType, secondaryType, gen6PlusTable, Type.GEN2THROUGH5); + } else { + return against(primaryType, secondaryType, gen2Through5Table, Type.GEN2THROUGH5); + } + } + if (gen >= 6) { + return against(primaryType, secondaryType, gen6PlusTable, Type.GEN6PLUS); + } + return null; + } + + private static Map against(Type primaryType, Type secondaryType, Effectiveness[][] effectivenesses, List allTypes) { + Map result = new HashMap<>(); + for(Type type : allTypes) { + Effectiveness effect = effectivenesses[type.ordinal()][primaryType.ordinal()]; + if (secondaryType != null) { + effect = effect.combine(effectivenesses[type.ordinal()][secondaryType.ordinal()]); + } + result.put(type, effect); + } + return result; + } + + public static List notVeryEffective(Type attackingType, int generation, boolean effectivenessUpdated) { + Effectiveness[][] effectivenesses; + if (generation == 1) { + effectivenesses = effectivenessUpdated ? gen2Through5Table : gen1Table; + } else if (generation >= 2 && generation <= 5) { + effectivenesses = effectivenessUpdated ? gen6PlusTable : gen2Through5Table; + } else { + effectivenesses = gen6PlusTable; + } + List allTypes = Type.getAllTypes(generation); + + return allTypes + .stream() + .filter(defendingType -> + effectivenesses[attackingType.ordinal()][defendingType.ordinal()] == Effectiveness.HALF || + effectivenesses[attackingType.ordinal()][defendingType.ordinal()] == Effectiveness.ZERO) + .collect(Collectors.toList()); + } + + public static List superEffective(Type attackingType, int generation, boolean effectivenessUpdated) { + Effectiveness[][] effectivenesses; + if (generation == 1) { + effectivenesses = effectivenessUpdated ? gen2Through5Table : gen1Table; + } else if (generation >= 2 && generation <= 5) { + effectivenesses = effectivenessUpdated ? gen6PlusTable : gen2Through5Table; + } else { + effectivenesses = gen6PlusTable; + } + List allTypes = Type.getAllTypes(generation); + + return allTypes + .stream() + .filter(defendingType -> + effectivenesses[attackingType.ordinal()][defendingType.ordinal()] == Effectiveness.DOUBLE) + .collect(Collectors.toList()); + } + + // Attacking type is the row, Defending type is the column. This corresponds to the ordinal of types. + private static final Effectiveness[][] gen1Table = { + /* NORMAL,FIGHTING, FLYING, GRASS , WATER, FIRE , ROCK , GROUND, PSYCHIC, BUG , DRAGON,ELECTRIC, GHOST , POISON, ICE */ + /*NORMAL */ {NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, ZERO, NEUTRAL, NEUTRAL}, + /*FIGHTING*/{ DOUBLE, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, ZERO, HALF, DOUBLE}, + /*FLYING */ {NEUTRAL, DOUBLE, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL}, + /*GRASS */ {NEUTRAL, NEUTRAL, HALF, HALF, DOUBLE, HALF, DOUBLE, DOUBLE, NEUTRAL, HALF, HALF, NEUTRAL, NEUTRAL, HALF, NEUTRAL}, + /*WATER */ {NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF, DOUBLE, DOUBLE, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL}, + /*FIRE */ {NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, HALF, HALF, HALF, NEUTRAL, NEUTRAL, DOUBLE, HALF, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE}, + /*ROCK */ {NEUTRAL, HALF, DOUBLE, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, HALF, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE}, + /*GROUND */ {NEUTRAL, NEUTRAL, ZERO, HALF, NEUTRAL, DOUBLE, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, DOUBLE, NEUTRAL, DOUBLE, NEUTRAL}, + /*PSYCHIC*/ {NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL}, + /*BUG */ {NEUTRAL, HALF, HALF, DOUBLE, NEUTRAL, HALF, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, HALF, DOUBLE, NEUTRAL}, + /*DRAGON */ {NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL}, + /*ELECTRIC*/{NEUTRAL, NEUTRAL, DOUBLE, HALF, DOUBLE, NEUTRAL, NEUTRAL, ZERO, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL, NEUTRAL, NEUTRAL}, + /*GHOST */ { ZERO, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, ZERO, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL}, + /*POISON */ {NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL}, + /*ICE */ {NEUTRAL, NEUTRAL, DOUBLE, DOUBLE, HALF, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, HALF}, + }; + private static final Effectiveness[][] gen2Through5Table = { + /* NORMAL,FIGHTING, FLYING, GRASS , WATER, FIRE , ROCK , GROUND, PSYCHIC, BUG , DRAGON,ELECTRIC, GHOST , POISON, ICE , STEEL , DARK */ + /*NORMAL */ {NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, ZERO, NEUTRAL, NEUTRAL, HALF, NEUTRAL}, + /*FIGHTING*/{ DOUBLE, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, ZERO, HALF, DOUBLE, DOUBLE, DOUBLE}, + /*FLYING */ {NEUTRAL, DOUBLE, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL}, + /*GRASS */ {NEUTRAL, NEUTRAL, HALF, HALF, DOUBLE, HALF, DOUBLE, DOUBLE, NEUTRAL, HALF, HALF, NEUTRAL, NEUTRAL, HALF, NEUTRAL, HALF, NEUTRAL}, + /*WATER */ {NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF, DOUBLE, DOUBLE, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL}, + /*FIRE */ {NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, HALF, HALF, HALF, NEUTRAL, NEUTRAL, DOUBLE, HALF, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, DOUBLE, NEUTRAL}, + /*ROCK */ {NEUTRAL, HALF, DOUBLE, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, HALF, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, HALF, NEUTRAL}, + /*GROUND */ {NEUTRAL, NEUTRAL, ZERO, HALF, NEUTRAL, DOUBLE, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, DOUBLE, NEUTRAL, DOUBLE, NEUTRAL, DOUBLE, NEUTRAL}, + /*PSYCHIC*/ {NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, HALF, ZERO}, + /*BUG */ {NEUTRAL, HALF, HALF, DOUBLE, NEUTRAL, HALF, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL, HALF, DOUBLE}, + /*DRAGON */ {NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL}, + /*ELECTRIC*/{NEUTRAL, NEUTRAL, DOUBLE, HALF, DOUBLE, NEUTRAL, NEUTRAL, ZERO, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL}, + /*GHOST */ { ZERO, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, HALF}, + /*POISON */ {NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL, ZERO, NEUTRAL}, + /*ICE */ {NEUTRAL, NEUTRAL, DOUBLE, DOUBLE, HALF, HALF, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL}, + /*STEEL */ {NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, DOUBLE, HALF, NEUTRAL}, + /*DARK */ {NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, HALF}, + }; + private static final Effectiveness[][] gen6PlusTable = { + /* NORMAL,FIGHTING, FLYING, GRASS , WATER, FIRE , ROCK , GROUND, PSYCHIC, BUG , DRAGON,ELECTRIC, GHOST , POISON, ICE , STEEL , DARK , FAIRY */ + /*NORMAL */ {NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, ZERO, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL}, + /*FIGHTING*/{ DOUBLE, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, ZERO, HALF, DOUBLE, DOUBLE, DOUBLE, HALF}, + /*FLYING */ {NEUTRAL, DOUBLE, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL}, + /*GRASS */ {NEUTRAL, NEUTRAL, HALF, HALF, DOUBLE, HALF, DOUBLE, DOUBLE, NEUTRAL, HALF, HALF, NEUTRAL, NEUTRAL, HALF, NEUTRAL, HALF, NEUTRAL, NEUTRAL}, + /*WATER */ {NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF, DOUBLE, DOUBLE, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL}, + /*FIRE */ {NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, HALF, HALF, HALF, NEUTRAL, NEUTRAL, DOUBLE, HALF, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, DOUBLE, NEUTRAL, NEUTRAL}, + /*ROCK */ {NEUTRAL, HALF, DOUBLE, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, HALF, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, HALF, NEUTRAL, NEUTRAL}, + /*GROUND */ {NEUTRAL, NEUTRAL, ZERO, HALF, NEUTRAL, DOUBLE, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, DOUBLE, NEUTRAL, DOUBLE, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL}, + /*PSYCHIC*/ {NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, HALF, ZERO, NEUTRAL}, + /*BUG */ {NEUTRAL, HALF, HALF, DOUBLE, NEUTRAL, HALF, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL, HALF, DOUBLE, HALF}, + /*DRAGON */ {NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL, ZERO}, + /*ELECTRIC*/{NEUTRAL, NEUTRAL, DOUBLE, HALF, DOUBLE, NEUTRAL, NEUTRAL, ZERO, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL}, + /*GHOST */ { ZERO, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL}, + /*POISON */ {NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL, ZERO, NEUTRAL, DOUBLE}, + /*ICE */ {NEUTRAL, NEUTRAL, DOUBLE, DOUBLE, HALF, HALF, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF, NEUTRAL, NEUTRAL}, + /*STEEL */ {NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, DOUBLE, HALF, NEUTRAL, DOUBLE}, + /*DARK */ {NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, HALF, HALF}, + /*FAIRY */ {NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, NEUTRAL, HALF, NEUTRAL, NEUTRAL, NEUTRAL, NEUTRAL, DOUBLE, NEUTRAL, NEUTRAL, HALF, NEUTRAL, HALF, DOUBLE, NEUTRAL}, + }; + + private Effectiveness combine(Effectiveness other) { + return combineTable[this.ordinal()][other.ordinal()]; + } + + // Allows easier calculation of combining a single type attacking a double typed pokemon. + // The rows and columns are the ordinals of Effectiveness (but only the first 4, as we don't need to + // combine 3 or more type considerations). + private static final Effectiveness[][] combineTable = { + {ZERO, ZERO, ZERO, ZERO}, + {ZERO, QUARTER, HALF, NEUTRAL}, + {ZERO, HALF, NEUTRAL, DOUBLE}, + {ZERO, NEUTRAL, DOUBLE, QUADRUPLE}, + }; +} diff --git a/src/com/pkrandom/pokemon/Encounter.java b/src/com/pkrandom/pokemon/Encounter.java new file mode 100755 index 0000000..9b391ec --- /dev/null +++ b/src/com/pkrandom/pokemon/Encounter.java @@ -0,0 +1,48 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- Encounter.java - contains one wild Pokemon slot --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class Encounter { + + public int level; + public int maxLevel; + public Pokemon pokemon; + public int formeNumber; + + // Used only for Gen 7's SOS mechanic + public boolean isSOS; + public SOSType sosType; + + public String toString() { + if (pokemon == null) { + return "ERROR"; + } + if (maxLevel == 0) { + return pokemon.name + " Lv" + level; + } else { + return pokemon.name + " Lvs " + level + "-" + maxLevel; + } + } + +} diff --git a/src/com/pkrandom/pokemon/EncounterSet.java b/src/com/pkrandom/pokemon/EncounterSet.java new file mode 100755 index 0000000..80001ef --- /dev/null +++ b/src/com/pkrandom/pokemon/EncounterSet.java @@ -0,0 +1,43 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- EncounterSet.java - contains a group of wild Pokemon --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class EncounterSet { + + public int rate; + public List encounters = new ArrayList<>(); + public Set bannedPokemon = new HashSet<>(); + public String displayName; + public int offset; + + public String toString() { + return "Encounter [Rate = " + rate + ", Encounters = " + encounters + "]"; + } + +} diff --git a/src/com/pkrandom/pokemon/Evolution.java b/src/com/pkrandom/pokemon/Evolution.java new file mode 100755 index 0000000..4721fe1 --- /dev/null +++ b/src/com/pkrandom/pokemon/Evolution.java @@ -0,0 +1,83 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- Evolution.java - represents an evolution between 2 Pokemon. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class Evolution implements Comparable { + + public Pokemon from; + public Pokemon to; + public boolean carryStats; + public EvolutionType type; + public int extraInfo; + public int forme; + public String formeSuffix = ""; + public int level = 0; + + public Evolution(Pokemon from, Pokemon to, boolean carryStats, EvolutionType type, int extra) { + this.from = from; + this.to = to; + this.carryStats = carryStats; + this.type = type; + this.extraInfo = extra; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + from.number; + result = prime * result + to.number; + result = prime * result + type.ordinal(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Evolution other = (Evolution) obj; + return from == other.from && to == other.to && type == other.type; + } + + @Override + public int compareTo(Evolution o) { + if (this.from.number < o.from.number) { + return -1; + } else if (this.from.number > o.from.number) { + return 1; + } else if (this.to.number < o.to.number) { + return -1; + } else if (this.to.number > o.to.number) { + return 1; + } else return Integer.compare(this.type.ordinal(), o.type.ordinal()); + } + + public String toFullName() { + return to.name + formeSuffix; + } +} diff --git a/src/com/pkrandom/pokemon/EvolutionType.java b/src/com/pkrandom/pokemon/EvolutionType.java new file mode 100755 index 0000000..1042820 --- /dev/null +++ b/src/com/pkrandom/pokemon/EvolutionType.java @@ -0,0 +1,111 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- EvolutionType.java - describes what process is necessary for an --*/ +/*-- evolution to occur --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public enum EvolutionType { + /* @formatter:off */ + LEVEL(1, 1, 4, 4, 4, 4, 4), + STONE(2, 2, 7, 7, 8, 8, 8), + TRADE(3, 3, 5, 5, 5, 5, 5), + TRADE_ITEM(-1, 3, 6, 6, 6, 6, 6), + HAPPINESS(-1, 4, 1, 1, 1, 1, 1), + HAPPINESS_DAY(-1, 4, 2, 2, 2, 2, 2), + HAPPINESS_NIGHT(-1, 4, 3, 3, 3, 3, 3), + LEVEL_ATTACK_HIGHER(-1, 5, 8, 8, 9, 9, 9), + LEVEL_DEFENSE_HIGHER(-1, 5, 10, 10, 11, 11, 11), + LEVEL_ATK_DEF_SAME(-1, 5, 9, 9, 10, 10, 10), + LEVEL_LOW_PV(-1, -1, 11, 11, 12, 12, 12), + LEVEL_HIGH_PV(-1, -1, 12, 12, 13, 13, 13), + LEVEL_CREATE_EXTRA(-1, -1, 13, 13, 14, 14, 14), + LEVEL_IS_EXTRA(-1, -1, 14, 14, 15, 15, 15), + LEVEL_HIGH_BEAUTY(-1, -1, 15, 15, 16, 16, 16), + STONE_MALE_ONLY(-1, -1, -1, 16, 17, 17, 17), + STONE_FEMALE_ONLY(-1, -1, -1, 17, 18, 18, 18), + LEVEL_ITEM_DAY(-1, -1, -1, 18, 19, 19, 19), + LEVEL_ITEM_NIGHT(-1, -1, -1, 19, 20, 20, 20), + LEVEL_WITH_MOVE(-1, -1, -1, 20, 21, 21, 21), + LEVEL_WITH_OTHER(-1, -1, -1, 21, 22, 22, 22), + LEVEL_MALE_ONLY(-1, -1, -1, 22, 23, 23, 23), + LEVEL_FEMALE_ONLY(-1, -1, -1, 23, 24, 24, 24), + LEVEL_ELECTRIFIED_AREA(-1, -1, -1, 24, 25, 25, 25), + LEVEL_MOSS_ROCK(-1, -1, -1, 25, 26, 26, 26), + LEVEL_ICY_ROCK(-1, -1, -1, 26, 27, 27, 27), + TRADE_SPECIAL(-1, -1, -1, -1, 7, 7, 7), + FAIRY_AFFECTION(-1, -1, -1, -1, -1, 29, 29), + LEVEL_WITH_DARK(-1, -1, -1, -1, -1, 30, 30), + LEVEL_UPSIDE_DOWN(-1, -1, -1, -1, -1, 28, 28), + LEVEL_RAIN(-1, -1, -1, -1, -1, 31, 31), + LEVEL_DAY(-1, -1, -1, -1, -1, 32, 32), + LEVEL_NIGHT(-1, -1, -1, -1, -1, 33, 33), + LEVEL_FEMALE_ESPURR(-1, -1, -1, -1, -1, 34, 34), + LEVEL_GAME(-1, -1, -1, -1, -1, -1, 36), + LEVEL_DAY_GAME(-1, -1, -1, -1, -1, -1, 37), + LEVEL_NIGHT_GAME(-1, -1, -1, -1, -1, -1, 38), + LEVEL_SNOWY(-1, -1, -1, -1, -1, -1, 39), + LEVEL_DUSK(-1, -1, -1, -1, -1, -1, 40), + LEVEL_NIGHT_ULTRA(-1, -1, -1, -1, -1, -1, 41), + STONE_ULTRA(-1, -1, -1, -1, -1, -1, 42), + NONE(-1, -1, -1, -1, -1, -1, -1); + /* @formatter:on */ + + private int[] indexNumbers; + private static EvolutionType[][] reverseIndexes = new EvolutionType[7][50]; + + static { + for (EvolutionType et : EvolutionType.values()) { + for (int i = 0; i < et.indexNumbers.length; i++) { + if (et.indexNumbers[i] > 0 && reverseIndexes[i][et.indexNumbers[i]] == null) { + reverseIndexes[i][et.indexNumbers[i]] = et; + } + } + } + } + + EvolutionType(int... indexes) { + this.indexNumbers = indexes; + } + + public int toIndex(int generation) { + return indexNumbers[generation - 1]; + } + + public static EvolutionType fromIndex(int generation, int index) { + return reverseIndexes[generation - 1][index]; + } + + public boolean usesLevel() { + return (this == LEVEL) || (this == LEVEL_ATTACK_HIGHER) || (this == LEVEL_DEFENSE_HIGHER) + || (this == LEVEL_ATK_DEF_SAME) || (this == LEVEL_LOW_PV) || (this == LEVEL_HIGH_PV) + || (this == LEVEL_CREATE_EXTRA) || (this == LEVEL_IS_EXTRA) || (this == LEVEL_MALE_ONLY) + || (this == LEVEL_FEMALE_ONLY) || (this == LEVEL_WITH_DARK)|| (this == LEVEL_UPSIDE_DOWN) + || (this == LEVEL_RAIN) || (this == LEVEL_DAY)|| (this == LEVEL_NIGHT)|| (this == LEVEL_FEMALE_ESPURR) + || (this == LEVEL_GAME) || (this == LEVEL_DAY_GAME) || (this == LEVEL_NIGHT_GAME) + || (this == LEVEL_SNOWY) || (this == LEVEL_DUSK) || (this == LEVEL_NIGHT_ULTRA); + } + + public boolean skipSplitEvo() { + return (this == LEVEL_HIGH_BEAUTY) || (this == LEVEL_NIGHT_ULTRA) || (this == STONE_ULTRA); + } +} diff --git a/src/com/pkrandom/pokemon/EvolutionUpdate.java b/src/com/pkrandom/pokemon/EvolutionUpdate.java new file mode 100644 index 0000000..5301d82 --- /dev/null +++ b/src/com/pkrandom/pokemon/EvolutionUpdate.java @@ -0,0 +1,72 @@ +package com.pkrandom.pokemon; + +public class EvolutionUpdate implements Comparable { + + private Pokemon from, to; + private String fromName, toName; + private EvolutionType type; + private String extraInfo; + private boolean condensed; + private boolean additional; + + + public EvolutionUpdate(Pokemon from, Pokemon to, EvolutionType type, String extraInfo, boolean condensed, boolean additional) { + this.from = from; + this.to = to; + fromName = from.fullName(); + toName = to.fullName(); + this.type = type; + this.extraInfo = extraInfo; + this.condensed = condensed; + this.additional = additional; + } + + public boolean isCondensed() { + return condensed; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EvolutionUpdate other = (EvolutionUpdate) obj; + return from == other.from && to == other.to && type == other.type; + } + + @Override + public int compareTo(EvolutionUpdate o) { + if (this.from.number < o.from.number) { + return -1; + } else if (this.from.number > o.from.number) { + return 1; + } else return Integer.compare(this.to.number, o.to.number); + } + + @Override + public String toString() { + switch (type) { + case LEVEL: + if (condensed) { + String formatLength = this.additional ? "%-15s" : "%-20s"; + return String.format("%-15s now%s evolves into " + formatLength + " at minimum level %s", + fromName, additional ? " also" : "", toName, extraInfo); + } else { + return String.format("%-15s -> %-15s at level %s", fromName, toName, extraInfo); + } + case STONE: + return String.format("%-15s -> %-15s using a %s", fromName, toName, extraInfo); + case HAPPINESS: + return String.format("%-15s -> %-15s by reaching high happiness", fromName, toName); + case LEVEL_ITEM_DAY: + return String.format("%-15s -> %-15s by leveling up holding %s", fromName, toName, extraInfo); + case LEVEL_WITH_OTHER: + return String.format("%-15s -> %-15s by leveling up with %s in the party", fromName, toName, extraInfo); + default: + return ""; + } + } +} diff --git a/src/com/pkrandom/pokemon/ExpCurve.java b/src/com/pkrandom/pokemon/ExpCurve.java new file mode 100755 index 0000000..3b018b0 --- /dev/null +++ b/src/com/pkrandom/pokemon/ExpCurve.java @@ -0,0 +1,85 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- ExpCurve.java - represents the EXP curves that a Pokemon can have. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public enum ExpCurve { + + SLOW, MEDIUM_SLOW, MEDIUM_FAST, FAST, ERRATIC, FLUCTUATING; + + public static ExpCurve fromByte(byte curve) { + switch (curve) { + case 0: + return MEDIUM_FAST; + case 1: + return ERRATIC; + case 2: + return FLUCTUATING; + case 3: + return MEDIUM_SLOW; + case 4: + return FAST; + case 5: + return SLOW; + } + return null; + } + + public byte toByte() { + switch (this) { + case SLOW: + return 5; + case MEDIUM_SLOW: + return 3; + case MEDIUM_FAST: + return 0; + case FAST: + return 4; + case ERRATIC: + return 1; + case FLUCTUATING: + return 2; + } + return 0; // default + } + + @Override + public String toString() { + switch (this) { + case MEDIUM_FAST: + return "Medium Fast"; + case ERRATIC: + return "Erratic"; + case FLUCTUATING: + return "Fluctuating"; + case MEDIUM_SLOW: + return "Medium Slow"; + case FAST: + return "Fast"; + case SLOW: + return "Slow"; + } + return null; + } + +} diff --git a/src/com/pkrandom/pokemon/FormeInfo.java b/src/com/pkrandom/pokemon/FormeInfo.java new file mode 100644 index 0000000..498f4c0 --- /dev/null +++ b/src/com/pkrandom/pokemon/FormeInfo.java @@ -0,0 +1,36 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- FormeInfo.java - stores information about a Pokemon's alt formes. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class FormeInfo { + public int baseForme; + public int formeNumber; + public int formeSpriteOffset; + + public FormeInfo(int baseForme, int formeNumber, int formeSpriteOffset) { + this.baseForme = baseForme; + this.formeNumber = formeNumber; + this.formeSpriteOffset = formeSpriteOffset; + } +} diff --git a/src/com/pkrandom/pokemon/Gen1Pokemon.java b/src/com/pkrandom/pokemon/Gen1Pokemon.java new file mode 100644 index 0000000..af73feb --- /dev/null +++ b/src/com/pkrandom/pokemon/Gen1Pokemon.java @@ -0,0 +1,149 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- Gen1Pokemon.java - represents an individual Gen 1 Pokemon. Used to --*/ +/*-- handle things related to stats because of the lack --*/ +/*-- of the Special split in Gen 1. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +public class Gen1Pokemon extends Pokemon { + + public Gen1Pokemon() { + shuffledStatsOrder = Arrays.asList(0, 1, 2, 3, 4); + } + + @Override + public void copyShuffledStatsUpEvolution(Pokemon evolvesFrom) { + // If stats were already shuffled once, un-shuffle them + shuffledStatsOrder = Arrays.asList( + shuffledStatsOrder.indexOf(0), + shuffledStatsOrder.indexOf(1), + shuffledStatsOrder.indexOf(2), + shuffledStatsOrder.indexOf(3), + shuffledStatsOrder.indexOf(4)); + applyShuffledOrderToStats(); + shuffledStatsOrder = evolvesFrom.shuffledStatsOrder; + applyShuffledOrderToStats(); + } + + @Override + protected void applyShuffledOrderToStats() { + List stats = Arrays.asList(hp, attack, defense, special, speed); + + // Copy in new stats + hp = stats.get(shuffledStatsOrder.get(0)); + attack = stats.get(shuffledStatsOrder.get(1)); + defense = stats.get(shuffledStatsOrder.get(2)); + special = stats.get(shuffledStatsOrder.get(3)); + speed = stats.get(shuffledStatsOrder.get(4)); + } + + @Override + public void randomizeStatsWithinBST(Random random) { + // Minimum 20 HP, 10 everything else + int bst = bst() - 60; + + // Make weightings + double hpW = random.nextDouble(), atkW = random.nextDouble(), defW = random.nextDouble(); + double specW = random.nextDouble(), speW = random.nextDouble(); + + double totW = hpW + atkW + defW + specW + speW; + + hp = (int) Math.max(1, Math.round(hpW / totW * bst)) + 20; + attack = (int) Math.max(1, Math.round(atkW / totW * bst)) + 10; + defense = (int) Math.max(1, Math.round(defW / totW * bst)) + 10; + special = (int) Math.max(1, Math.round(specW / totW * bst)) + 10; + speed = (int) Math.max(1, Math.round(speW / totW * bst)) + 10; + + // Check for something we can't store + if (hp > 255 || attack > 255 || defense > 255 || special > 255 || speed > 255) { + // re roll + randomizeStatsWithinBST(random); + } + } + + @Override + public void copyRandomizedStatsUpEvolution(Pokemon evolvesFrom) { + double ourBST = bst(); + double theirBST = evolvesFrom.bst(); + + double bstRatio = ourBST / theirBST; + + hp = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.hp * bstRatio))); + attack = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.attack * bstRatio))); + defense = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.defense * bstRatio))); + speed = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.speed * bstRatio))); + special = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.special * bstRatio))); + } + + @Override + public void assignNewStatsForEvolution(Pokemon evolvesFrom, Random random) { + double ourBST = bst(); + double theirBST = evolvesFrom.bst(); + + double bstDiff = ourBST - theirBST; + + // Make weightings + double hpW = random.nextDouble(), atkW = random.nextDouble(), defW = random.nextDouble(); + double specW = random.nextDouble(), speW = random.nextDouble(); + + double totW = hpW + atkW + defW + specW + speW; + + double hpDiff = Math.round((hpW / totW) * bstDiff); + double atkDiff = Math.round((atkW / totW) * bstDiff); + double defDiff = Math.round((defW / totW) * bstDiff); + double specDiff = Math.round((specW / totW) * bstDiff); + double speDiff = Math.round((speW / totW) * bstDiff); + + hp = (int) Math.min(255, Math.max(1, evolvesFrom.hp + hpDiff)); + attack = (int) Math.min(255, Math.max(1, evolvesFrom.attack + atkDiff)); + defense = (int) Math.min(255, Math.max(1, evolvesFrom.defense + defDiff)); + speed = (int) Math.min(255, Math.max(1, evolvesFrom.speed + speDiff)); + special = (int) Math.min(255, Math.max(1, evolvesFrom.special + specDiff)); + } + + @Override + protected int bst() { + return hp + attack + defense + special + speed; + } + + @Override + public int bstForPowerLevels() { + return hp + attack + defense + special + speed; + } + + @Override + public double getAttackSpecialAttackRatio() { + return (double)attack / ((double)attack + (double)special); + } + + @Override + public String toString() { + return "Pokemon [name=" + name + ", number=" + number + ", primaryType=" + primaryType + ", secondaryType=" + + secondaryType + ", hp=" + hp + ", attack=" + attack + ", defense=" + defense + ", special=" + special + + ", speed=" + speed + "]"; + } +} diff --git a/src/com/pkrandom/pokemon/GenRestrictions.java b/src/com/pkrandom/pokemon/GenRestrictions.java new file mode 100755 index 0000000..7fea8c3 --- /dev/null +++ b/src/com/pkrandom/pokemon/GenRestrictions.java @@ -0,0 +1,144 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- GenRestrictions.java - stores what generations the user has limited. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class GenRestrictions { + + public boolean allow_gen1, allow_gen2, allow_gen3, allow_gen4, allow_gen5, allow_gen6, allow_gen7; + public boolean allow_evolutionary_relatives; + + public GenRestrictions() { + } + + public GenRestrictions(int state) { + allow_gen1 = (state & 1) > 0; + allow_gen2 = (state & 2) > 0; + allow_gen3 = (state & 4) > 0; + allow_gen4 = (state & 8) > 0; + allow_gen5 = (state & 16) > 0; + allow_gen6 = (state & 32) > 0; + allow_gen7 = (state & 64) > 0; + allow_evolutionary_relatives = (state & 128) > 0; + } + + public boolean nothingSelected() { + return !allow_gen1 && !allow_gen2 && !allow_gen3 && !allow_gen4 && !allow_gen5 && !allow_gen6 && !allow_gen7; + } + + public int toInt() { + return makeIntSelected(allow_gen1, allow_gen2, allow_gen3, allow_gen4, allow_gen5, allow_gen6, allow_gen7, + allow_evolutionary_relatives); + } + + public void limitToGen(int generation) { + if (generation < 2) { + allow_gen2 = false; + } + if (generation < 3) { + allow_gen3 = false; + } + if (generation < 4) { + allow_gen4 = false; + } + if (generation < 5) { + allow_gen5 = false; + } + if (generation < 6) { + allow_gen6 = false; + } + if (generation < 7) { + allow_gen7 = false; + } + } + + public boolean allowTrainerSwapMegaEvolvables(boolean isXY, boolean isTypeThemedTrainers) { + if (isTypeThemedTrainers) { + return megaEvolutionsOfEveryTypeAreInPool(isXY); + } else { + return megaEvolutionsAreInPool(isXY); + } + } + + public boolean megaEvolutionsOfEveryTypeAreInPool(boolean isXY) { + Set typePool = new HashSet<>(); + if (allow_gen1) { + typePool.addAll(Arrays.asList(Type.GRASS, Type.POISON, Type.FIRE, Type.FLYING, Type.WATER, Type.PSYCHIC, + Type.GHOST, Type.NORMAL, Type.BUG, Type.ROCK)); + } + if (allow_gen2) { + typePool.addAll(Arrays.asList(Type.ELECTRIC, Type.BUG, Type.STEEL, Type.FIGHTING, Type.DARK, + Type.FIRE, Type.ROCK)); + if (!isXY) { + typePool.add(Type.GROUND); + } + } + if (allow_gen3) { + typePool.addAll(Arrays.asList(Type.FIRE, Type.FIGHTING, Type.PSYCHIC, Type.FAIRY, Type.STEEL, Type.ROCK, + Type.ELECTRIC, Type.GHOST, Type.DARK, Type.DRAGON)); + if (!isXY) { + typePool.addAll(Arrays.asList(Type.GRASS, Type.WATER, Type.GROUND, Type.FLYING, Type.ICE)); + } + } + if (allow_gen4) { + typePool.addAll(Arrays.asList(Type.DRAGON, Type.GROUND, Type.FIGHTING, Type.STEEL, Type.GRASS, Type.ICE)); + if (!isXY) { + typePool.addAll(Arrays.asList(Type.NORMAL, Type.PSYCHIC)); + } + } + if (allow_gen5 && !isXY) { + typePool.add(Type.NORMAL); + } + if (allow_gen6 && !isXY) { + typePool.addAll(Arrays.asList(Type.ROCK, Type.FAIRY)); + } + return typePool.size() == 18; + } + + public boolean megaEvolutionsAreInPool(boolean isXY) { + if (isXY) { + return allow_gen1 || allow_gen2 || allow_gen3 || allow_gen4; + } else { + return allow_gen1 || allow_gen2 || allow_gen3 || allow_gen4 || allow_gen5 || allow_gen6; + } + } + + private int makeIntSelected(boolean... switches) { + if (switches.length > 32) { + // No can do + return 0; + } + int initial = 0; + int state = 1; + for (boolean b : switches) { + initial |= b ? state : 0; + state *= 2; + } + return initial; + } + +} diff --git a/src/com/pkrandom/pokemon/IngameTrade.java b/src/com/pkrandom/pokemon/IngameTrade.java new file mode 100755 index 0000000..4dfdc76 --- /dev/null +++ b/src/com/pkrandom/pokemon/IngameTrade.java @@ -0,0 +1,40 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- IngameTrade.java - stores Pokemon trades with in-game NPCs. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class IngameTrade { + + public int id; + + public Pokemon requestedPokemon, givenPokemon; + + public String nickname, otName; + + public int otId; + + public int[] ivs = new int[0]; + + public int item = 0; + +} diff --git a/src/com/pkrandom/pokemon/ItemList.java b/src/com/pkrandom/pokemon/ItemList.java new file mode 100755 index 0000000..ed771d3 --- /dev/null +++ b/src/com/pkrandom/pokemon/ItemList.java @@ -0,0 +1,105 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- ItemList.java - contains the list of all items in the game. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.Random; + +public class ItemList { + + private boolean[] items; + private boolean[] tms; + + public ItemList(int highestIndex) { + items = new boolean[highestIndex + 1]; + tms = new boolean[highestIndex + 1]; + for (int i = 1; i <= highestIndex; i++) { + items[i] = true; + } + } + + public boolean isTM(int index) { + return index >= 0 && index < tms.length && tms[index]; + } + + public boolean isAllowed(int index) { + return index >= 0 && index < tms.length && items[index]; + } + + public void banSingles(int... indexes) { + for (int index : indexes) { + items[index] = false; + } + } + + public void banRange(int startIndex, int length) { + for (int i = 0; i < length; i++) { + items[i + startIndex] = false; + } + } + + public void tmRange(int startIndex, int length) { + for (int i = 0; i < length; i++) { + tms[i + startIndex] = true; + } + } + + public int randomItem(Random random) { + int chosen = 0; + while (!items[chosen]) { + chosen = random.nextInt(items.length); + } + return chosen; + } + + public int randomNonTM(Random random) { + int chosen = 0; + while (!items[chosen] || tms[chosen]) { + chosen = random.nextInt(items.length); + } + return chosen; + } + + public int randomTM(Random random) { + int chosen = 0; + while (!tms[chosen]) { + chosen = random.nextInt(items.length); + } + return chosen; + } + + public ItemList copy() { + ItemList other = new ItemList(items.length - 1); + System.arraycopy(items, 0, other.items, 0, items.length); + System.arraycopy(tms, 0, other.tms, 0, tms.length); + return other; + } + + public ItemList copy(int newMax) { + ItemList other = new ItemList(newMax); + System.arraycopy(items, 0, other.items, 0, items.length); + System.arraycopy(tms, 0, other.tms, 0, tms.length); + return other; + } + +} diff --git a/src/com/pkrandom/pokemon/MegaEvolution.java b/src/com/pkrandom/pokemon/MegaEvolution.java new file mode 100644 index 0000000..fcf6ff3 --- /dev/null +++ b/src/com/pkrandom/pokemon/MegaEvolution.java @@ -0,0 +1,39 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- MegaEvolution.java - represents an mega evolution --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class MegaEvolution { + public Pokemon from; + public Pokemon to; + public int method; + public int argument; + public boolean carryStats = true; + + public MegaEvolution(Pokemon from, Pokemon to, int method, int argument) { + this.from = from; + this.to = to; + this.method = method; + this.argument = argument; + } +} diff --git a/src/com/pkrandom/pokemon/Move.java b/src/com/pkrandom/pokemon/Move.java new file mode 100755 index 0000000..c22a448 --- /dev/null +++ b/src/com/pkrandom/pokemon/Move.java @@ -0,0 +1,103 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- Move.java - represents a move usable by Pokemon. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.constants.GlobalConstants; + +public class Move { + public class StatChange { + public StatChangeType type; + public int stages; + public double percentChance; + + @Override + public boolean equals(Object obj) { + StatChange other = (StatChange)obj; + return this.type == other.type && this.stages == other.stages && this.percentChance == other.percentChance; + } + + } + + public String name; + public int number; + public int internalId; + public int power; + public int pp; + public double hitratio; + public Type type; + public MoveCategory category; + public StatChangeMoveType statChangeMoveType = StatChangeMoveType.NONE_OR_UNKNOWN; + public StatChange[] statChanges = new StatChange[3]; + public StatusMoveType statusMoveType = StatusMoveType.NONE_OR_UNKNOWN; + public StatusType statusType = StatusType.NONE; + public CriticalChance criticalChance = CriticalChance.NORMAL; + public double statusPercentChance; + public double flinchPercentChance; + public int recoilPercent; + public int absorbPercent; + public int priority; + public boolean makesContact; + public boolean isChargeMove; + public boolean isRechargeMove; + public boolean isPunchMove; + public boolean isSoundMove; + public boolean isTrapMove; // True for both binding moves (like Wrap) and trapping moves (like Mean Look) + public int effectIndex; + public int target; + public double hitCount = 1; // not saved, only used in randomized move powers. + + public Move() { + // Initialize all statStageChanges to something sensible so that we don't need to have + // each RomHandler mess with them if they don't need to. + for (int i = 0; i < this.statChanges.length; i++) { + this.statChanges[i] = new StatChange(); + this.statChanges[i].type = StatChangeType.NONE; + } + } + + public boolean hasSpecificStatChange(StatChangeType type, boolean isPositive) { + for (StatChange sc: this.statChanges) { + if (sc.type == type && (isPositive ^ sc.stages < 0)) { + return true; + } + } + return false; + } + + public boolean hasBeneficialStatChange() { + return (statChangeMoveType == StatChangeMoveType.DAMAGE_TARGET && statChanges[0].stages < 0) || + statChangeMoveType == StatChangeMoveType.DAMAGE_USER && statChanges[0].stages > 0; + } + + public boolean isGoodDamaging(int perfectAccuracy) { + return (power * hitCount) >= 2 * GlobalConstants.MIN_DAMAGING_MOVE_POWER + || ((power * hitCount) >= GlobalConstants.MIN_DAMAGING_MOVE_POWER && (hitratio >= 90 || hitratio == perfectAccuracy)); + } + + public String toString() { + return "#" + number + " " + name + " - Power: " + power + ", Base PP: " + pp + ", Type: " + type + ", Hit%: " + + (hitratio) + ", Effect: " + effectIndex + ", Priority: " + priority; + } + +} diff --git a/src/com/pkrandom/pokemon/MoveCategory.java b/src/com/pkrandom/pokemon/MoveCategory.java new file mode 100644 index 0000000..a2b976a --- /dev/null +++ b/src/com/pkrandom/pokemon/MoveCategory.java @@ -0,0 +1,28 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- DamageType.java - represents a move's Physical/Special split value. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public enum MoveCategory { + PHYSICAL, SPECIAL, STATUS +} diff --git a/src/com/pkrandom/pokemon/MoveLearnt.java b/src/com/pkrandom/pokemon/MoveLearnt.java new file mode 100755 index 0000000..d8cd9b4 --- /dev/null +++ b/src/com/pkrandom/pokemon/MoveLearnt.java @@ -0,0 +1,35 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- MoveLearnt.java - represents a move learnt by a Pokemon at a level. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class MoveLearnt { + + public int move; + public int level; + + public String toString() { + return "move " + move + " at level " + level; + } + +} diff --git a/src/com/pkrandom/pokemon/MoveSynergy.java b/src/com/pkrandom/pokemon/MoveSynergy.java new file mode 100644 index 0000000..51b7ce9 --- /dev/null +++ b/src/com/pkrandom/pokemon/MoveSynergy.java @@ -0,0 +1,1204 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- MoveSynergy.java - synergies between moves, or between --*/ +/*-- abilities/stats and moves --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.constants.Abilities; +import com.pkrandom.constants.Moves; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class MoveSynergy { + + public static List getSoftAbilityMoveSynergy(int ability, List moveList, Type pkType1, Type pkType2) { + List synergisticMoves = new ArrayList<>(); + + switch(ability) { + case Abilities.drizzle: + case Abilities.primordialSea: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.WATER && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.drought: + case Abilities.desolateLand: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.FIRE && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.refrigerate: + if (pkType1 == Type.ICE || pkType2 == Type.ICE) { + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.NORMAL && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + } + break; + case Abilities.galeWings: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.FLYING) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.pixilate: + if (pkType1 == Type.FAIRY || pkType2 == Type.FAIRY) { + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.NORMAL && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + } + break; + case Abilities.aerilate: + if (pkType1 == Type.FLYING || pkType2 == Type.FLYING) { + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.NORMAL && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + } + break; + case Abilities.darkAura: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.DARK && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.fairyAura: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.FAIRY && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.steelworker: + case Abilities.steelySpirit: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.STEEL && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.galvanize: + if (pkType1 == Type.ELECTRIC || pkType2 == Type.ELECTRIC) { + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.NORMAL && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + } + break; + case Abilities.electricSurge: + case Abilities.transistor: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.ELECTRIC && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.psychicSurge: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.PSYCHIC && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.grassySurge: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.GRASS && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.dragonsMaw: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.DRAGON && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + } + + return moveList + .stream() + .filter(mv -> synergisticMoves.contains(mv.number)) + .distinct() + .collect(Collectors.toList()); + } + + public static List getSoftAbilityMoveAntiSynergy(int ability, List moveList) { + List antiSynergisticMoves = new ArrayList<>(); + + switch(ability) { + case Abilities.drizzle: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.FIRE && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.drought: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.WATER && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.mistySurge: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.DRAGON && mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + } + + return moveList + .stream() + .filter(mv -> antiSynergisticMoves.contains(mv.number)) + .distinct() + .collect(Collectors.toList()); + } + + public static List getHardAbilityMoveSynergy(int ability, Type pkType1, Type pkType2, List moveList, + int generation, int perfectAccuracy) { + List synergisticMoves = new ArrayList<>(); + + switch(ability) { + case Abilities.drizzle: + case Abilities.primordialSea: + synergisticMoves.add(Moves.thunder); + synergisticMoves.add(Moves.hurricane); + if (pkType1 == Type.WATER || pkType2 == Type.WATER) { + synergisticMoves.add(Moves.weatherBall); + } + break; + case Abilities.speedBoost: + synergisticMoves.add(Moves.batonPass); + synergisticMoves.add(Moves.storedPower); + synergisticMoves.add(Moves.powerTrip); + break; + case Abilities.sturdy: + if (generation >= 5) { + synergisticMoves.add(Moves.endeavor); + synergisticMoves.add(Moves.counter); + synergisticMoves.add(Moves.mirrorCoat); + synergisticMoves.add(Moves.flail); + synergisticMoves.add(Moves.reversal); + } + break; + case Abilities.sandVeil: + case Abilities.sandRush: + synergisticMoves.add(Moves.sandstorm); + break; + case Abilities.staticTheAbilityNotTheKeyword: + synergisticMoves.add(Moves.smellingSalts); + synergisticMoves.add(Moves.hex); + break; + case Abilities.compoundEyes: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.hitratio > 0 && mv.hitratio <= 80) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.ownTempo: + case Abilities.tangledFeet: + synergisticMoves.add(Moves.petalDance); + synergisticMoves.add(Moves.thrash); + synergisticMoves.add(Moves.outrage); + break; + case Abilities.shadowTag: + case Abilities.arenaTrap: + synergisticMoves.add(Moves.perishSong); + break; + case Abilities.poisonPoint: + synergisticMoves.add(Moves.venoshock); + // fallthrough + case Abilities.effectSpore: + case Abilities.flameBody: + synergisticMoves.add(Moves.hex); + break; + case Abilities.sereneGrace: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> ((mv.statChangeMoveType == StatChangeMoveType.DAMAGE_TARGET || + mv.statChangeMoveType == StatChangeMoveType.DAMAGE_USER) && + mv.statChanges[0].percentChance < 100) || + (mv.statusMoveType == StatusMoveType.DAMAGE && mv.statusPercentChance < 100) || + mv.flinchPercentChance > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.swiftSwim: + case Abilities.rainDish: + case Abilities.drySkin: + case Abilities.hydration: + synergisticMoves.add(Moves.rainDance); + break; + case Abilities.chlorophyll: + case Abilities.harvest: + case Abilities.leafGuard: + synergisticMoves.add(Moves.sunnyDay); + break; + case Abilities.soundproof: + synergisticMoves.add(Moves.perishSong); + break; + case Abilities.sandStream: + if (pkType1 == Type.ROCK || pkType2 == Type.ROCK) { + synergisticMoves.add(Moves.weatherBall); + } + break; + case Abilities.earlyBird: + case Abilities.shedSkin: + synergisticMoves.add(Moves.rest); + break; + case Abilities.truant: + synergisticMoves.add(Moves.transform); + break; + case Abilities.hustle: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.statChangeMoveType == StatChangeMoveType.DAMAGE_USER || + mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_USER) && + mv.hasSpecificStatChange(StatChangeType.ACCURACY, true)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.category == MoveCategory.PHYSICAL && mv.hitratio == perfectAccuracy) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.guts: + synergisticMoves.add(Moves.facade); + break; + case Abilities.rockHead: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.recoilPercent > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.drought: + case Abilities.desolateLand: + synergisticMoves.add(Moves.solarBeam); + synergisticMoves.add(Moves.solarBlade); + synergisticMoves.add(Moves.morningSun); + synergisticMoves.add(Moves.synthesis); + synergisticMoves.add(Moves.moonlight); + if (generation >= 5) { + synergisticMoves.add(Moves.growth); + } + if (pkType1 == Type.FIRE || pkType2 == Type.FIRE) { + synergisticMoves.add(Moves.weatherBall); + } + break; + case Abilities.ironFist: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.isPunchMove) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.snowCloak: + case Abilities.iceBody: + case Abilities.slushRush: + synergisticMoves.add(Moves.hail); + break; + case Abilities.unburden: + synergisticMoves.add(Moves.fling); + synergisticMoves.add(Moves.acrobatics); + break; + case Abilities.simple: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.statChangeMoveType == StatChangeMoveType.DAMAGE_USER || + mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_USER) && + mv.statChanges[0].stages > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + synergisticMoves.add(Moves.acupressure); + break; + case Abilities.adaptability: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.category != MoveCategory.STATUS && (mv.type == pkType1 || mv.type == pkType2)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.skillLink: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.hitCount >= 3) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.sniper: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.criticalChance == CriticalChance.INCREASED || + mv.criticalChance == CriticalChance.GUARANTEED) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.magicGuard: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.recoilPercent > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + synergisticMoves.add(Moves.mindBlown); + break; + case Abilities.stall: + synergisticMoves.add(Moves.metalBurst); + synergisticMoves.add(Moves.payback); + break; + case Abilities.superLuck: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.criticalChance == CriticalChance.INCREASED) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.analytic: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.power > 0 && mv.priority < 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.noGuard: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.hitratio > 0 && mv.hitratio <= 70) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.technician: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.power >= 40 && mv.power <= 60) || mv.hitCount > 1) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.slowStart: + synergisticMoves.add(Moves.transform); + synergisticMoves.add(Moves.protect); + synergisticMoves.add(Moves.detect); + synergisticMoves.add(Moves.kingsShield); + synergisticMoves.add(Moves.banefulBunker); + synergisticMoves.add(Moves.fly); + synergisticMoves.add(Moves.dig); + synergisticMoves.add(Moves.bounce); + synergisticMoves.add(Moves.dive); + break; + case Abilities.snowWarning: + synergisticMoves.add(Moves.auroraVeil); + synergisticMoves.add(Moves.blizzard); + if (pkType1 == Type.ICE || pkType2 == Type.ICE) { + synergisticMoves.add(Moves.weatherBall); + } + break; + case Abilities.reckless: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.recoilPercent > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + synergisticMoves.add(Moves.jumpKick); + synergisticMoves.add(Moves.highJumpKick); + break; + case Abilities.badDreams: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.statusType == StatusType.SLEEP) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.sheerForce: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.statChangeMoveType == StatChangeMoveType.DAMAGE_TARGET || + (mv.statChangeMoveType == StatChangeMoveType.DAMAGE_USER && + mv.statChanges[0].stages > 0) || + mv.statusMoveType == StatusMoveType.DAMAGE || + mv.flinchPercentChance > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.contrary: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.statChangeMoveType == StatChangeMoveType.DAMAGE_USER && + mv.statChanges[0].stages < 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.heavyMetal: + synergisticMoves.add(Moves.heatCrash); + synergisticMoves.add(Moves.heavySlam); + break; + case Abilities.moody: + synergisticMoves.add(Moves.storedPower); + synergisticMoves.add(Moves.powerTrip); + break; + case Abilities.poisonTouch: + case Abilities.toughClaws: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.makesContact) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.regenerator: + synergisticMoves.add(Moves.uTurn); + synergisticMoves.add(Moves.voltSwitch); + synergisticMoves.add(Moves.partingShot); + break; + case Abilities.prankster: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.statusMoveType == StatusMoveType.NO_DAMAGE) + .map(mv -> mv.number) + .collect(Collectors.toList())); + synergisticMoves.add(Moves.destinyBond); + synergisticMoves.add(Moves.encore); + synergisticMoves.add(Moves.reflect); + synergisticMoves.add(Moves.lightScreen); + synergisticMoves.add(Moves.grudge); + synergisticMoves.add(Moves.painSplit); + synergisticMoves.add(Moves.substitute); + break; + case Abilities.strongJaw: + synergisticMoves.add(Moves.bite); + synergisticMoves.add(Moves.crunch); + synergisticMoves.add(Moves.fireFang); + synergisticMoves.add(Moves.fishiousRend); + synergisticMoves.add(Moves.hyperFang); + synergisticMoves.add(Moves.iceFang); + synergisticMoves.add(Moves.jawLock); + synergisticMoves.add(Moves.poisonFang); + synergisticMoves.add(Moves.psychicFangs); + synergisticMoves.add(Moves.thunderFang); + break; + case Abilities.megaLauncher: + synergisticMoves.add(Moves.auraSphere); + synergisticMoves.add(Moves.darkPulse); + synergisticMoves.add(Moves.dragonPulse); + synergisticMoves.add(Moves.originPulse); + synergisticMoves.add(Moves.terrainPulse); + synergisticMoves.add(Moves.waterPulse); + break; + case Abilities.wimpOut: + case Abilities.emergencyExit: + synergisticMoves.add(Moves.fakeOut); + synergisticMoves.add(Moves.firstImpression); + break; + case Abilities.merciless: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.statusType == StatusType.POISON || mv.statusType == StatusType.TOXIC_POISON) + .map(mv -> mv.number) + .collect(Collectors.toList())); + synergisticMoves.add(Moves.banefulBunker); + break; + case Abilities.liquidVoice: + if (pkType1 == Type.WATER || pkType2 == Type.WATER) { + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.isSoundMove && mv.power > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + } + break; + case Abilities.triage: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.absorbPercent > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.surgeSurfer: + synergisticMoves.add(Moves.electricTerrain); + break; + case Abilities.corrosion: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.category == MoveCategory.STATUS && + (mv.statusType == StatusType.POISON || mv.statusType == StatusType.TOXIC_POISON)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + } + + return moveList + .stream() + .filter(mv -> synergisticMoves.contains(mv.number)) + .distinct() + .collect(Collectors.toList()); + } + + public static List getHardAbilityMoveAntiSynergy(int ability, List moveList) { + List antiSynergisticMoves = new ArrayList<>(); + + switch(ability) { + case Abilities.primordialSea: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.FIRE && + mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + // fallthrough + case Abilities.drizzle: + case Abilities.sandStream: + case Abilities.snowWarning: + antiSynergisticMoves.add(Moves.solarBeam); + antiSynergisticMoves.add(Moves.solarBlade); + antiSynergisticMoves.add(Moves.morningSun); + antiSynergisticMoves.add(Moves.synthesis); + antiSynergisticMoves.add(Moves.moonlight); + antiSynergisticMoves.add(Moves.rainDance); + antiSynergisticMoves.add(Moves.sunnyDay); + antiSynergisticMoves.add(Moves.hail); + antiSynergisticMoves.add(Moves.sandstorm); + break; + case Abilities.speedBoost: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_USER) && + mv.statChanges[0].type == StatChangeType.SPEED && + mv.statChanges[0].stages > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + antiSynergisticMoves.add(Moves.psychUp); + antiSynergisticMoves.add(Moves.haze); + break; + case Abilities.desolateLand: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.type == Type.WATER && + mv.category != MoveCategory.STATUS) + .map(mv -> mv.number) + .collect(Collectors.toList())); + // fallthrough + case Abilities.drought: + antiSynergisticMoves.add(Moves.thunder); + antiSynergisticMoves.add(Moves.hurricane); + antiSynergisticMoves.add(Moves.rainDance); + antiSynergisticMoves.add(Moves.sunnyDay); + antiSynergisticMoves.add(Moves.hail); + antiSynergisticMoves.add(Moves.sandstorm); + break; + case Abilities.noGuard: + antiSynergisticMoves.add(Moves.lockOn); + antiSynergisticMoves.add(Moves.mindReader); + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.hasSpecificStatChange(StatChangeType.ACCURACY, true) || + mv.hasSpecificStatChange(StatChangeType.EVASION, true) || + mv.hasSpecificStatChange(StatChangeType.EVASION, false)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.damp: + antiSynergisticMoves.add(Moves.selfDestruct); + antiSynergisticMoves.add(Moves.explosion); + antiSynergisticMoves.add(Moves.mindBlown); + antiSynergisticMoves.add(Moves.mistyExplosion); + break; + case Abilities.insomnia: + case Abilities.vitalSpirit: + case Abilities.comatose: + case Abilities.sweetVeil: + antiSynergisticMoves.add(Moves.rest); + break; + case Abilities.airLock: + case Abilities.cloudNine: + case Abilities.deltaStream: + antiSynergisticMoves.add(Moves.rainDance); + antiSynergisticMoves.add(Moves.sunnyDay); + antiSynergisticMoves.add(Moves.sandstorm); + antiSynergisticMoves.add(Moves.hail); + break; + case Abilities.simple: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.statChangeMoveType == StatChangeMoveType.DAMAGE_USER || + mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_USER) && + mv.statChanges[0].stages < 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.contrary: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.statChangeMoveType == StatChangeMoveType.DAMAGE_USER || + mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_USER) && + mv.statChanges[0].stages > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + antiSynergisticMoves.add(Moves.shellSmash); + break; + case Abilities.lightMetal: + antiSynergisticMoves.add(Moves.heatCrash); + antiSynergisticMoves.add(Moves.heavySlam); + break; + case Abilities.electricSurge: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.category == MoveCategory.STATUS && mv.statusType == StatusType.SLEEP)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + antiSynergisticMoves.add(Moves.rest); + break; + case Abilities.psychicSurge: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.priority > 0)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Abilities.mistySurge: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.category == MoveCategory.STATUS && + (mv.statusType == StatusType.BURN || + mv.statusType == StatusType.FREEZE || + mv.statusType == StatusType.PARALYZE || + mv.statusType == StatusType.SLEEP || + mv.statusType == StatusType.POISON || + mv.statusType == StatusType.TOXIC_POISON))) + .map(mv -> mv.number) + .collect(Collectors.toList())); + antiSynergisticMoves.add(Moves.rest); + break; + case Abilities.grassySurge: + antiSynergisticMoves.add(Moves.earthquake); + antiSynergisticMoves.add(Moves.magnitude); + antiSynergisticMoves.add(Moves.bulldoze); + break; + + + } + + return moveList + .stream() + .filter(mv -> antiSynergisticMoves.contains(mv.number)) + .distinct() + .collect(Collectors.toList()); + } + + public static List getStatMoveSynergy(Pokemon pk, List moveList) { + List synergisticMoves = new ArrayList<>(); + + if ((double)pk.hp / (double)pk.bst() < 1.0/8) { + synergisticMoves.add(Moves.painSplit); + synergisticMoves.add(Moves.endeavor); + } + + if ((double)pk.hp / (double)pk.bst() >= 1.0/4) { + synergisticMoves.add(Moves.waterSpout); + synergisticMoves.add(Moves.eruption); + synergisticMoves.add(Moves.counter); + synergisticMoves.add(Moves.mirrorCoat); + } + + if (pk.attack * 2 < pk.defense) { + synergisticMoves.add(Moves.powerTrick); + } + + if ((double)(pk.attack + pk.spatk) / (double)pk.bst() < 1.0/4) { + synergisticMoves.add(Moves.powerSplit); + } + + if ((double)(pk.defense + pk.spdef) / (double)pk.bst() < 1.0/4) { + synergisticMoves.add(Moves.guardSplit); + } + + if ((double)pk.speed / (double)pk.bst() < 1.0/8) { + synergisticMoves.add(Moves.gyroBall); + } + + if ((double)pk.speed / (double)pk.bst() >= 1.0/4) { + synergisticMoves.add(Moves.electroBall); + } + + return moveList + .stream() + .filter(mv -> synergisticMoves.contains(mv.number)) + .distinct() + .collect(Collectors.toList()); + } + + public static List getStatMoveAntiSynergy(Pokemon pk, List moveList) { + List antiSynergisticMoves = new ArrayList<>(); + + if ((double)pk.hp / (double)pk.bst() >= 1.0/4) { + antiSynergisticMoves.add(Moves.painSplit); + antiSynergisticMoves.add(Moves.endeavor); + } + + if (pk.defense * 2 < pk.attack) { + antiSynergisticMoves.add(Moves.powerTrick); + } + + if ((double)(pk.attack + pk.spatk) / (double)pk.bst() >= 1.0/3) { + antiSynergisticMoves.add(Moves.powerSplit); + } + + if ((double)(pk.defense + pk.spdef) / (double)pk.bst() >= 1.0/3) { + antiSynergisticMoves.add(Moves.guardSplit); + } + + if ((double)pk.speed / (double)pk.bst() >= 1.0/4) { + antiSynergisticMoves.add(Moves.gyroBall); + } + + if ((double)pk.speed / (double)pk.bst() < 1.0/8) { + antiSynergisticMoves.add(Moves.electroBall); + } + + return moveList + .stream() + .filter(mv -> antiSynergisticMoves.contains(mv.number)) + .distinct() + .collect(Collectors.toList()); + } + + public static List getMoveSynergy(Move mv1, List moveList, int generation) { + List synergisticMoves = new ArrayList<>(); + + if ((mv1.statChangeMoveType == StatChangeMoveType.DAMAGE_TARGET && + mv1.hasSpecificStatChange(StatChangeType.SPEED, false)) || + ((mv1.statChangeMoveType == StatChangeMoveType.DAMAGE_USER || + mv1.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_USER) && + mv1.hasSpecificStatChange(StatChangeType.SPEED, true))) { + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.flinchPercentChance > 0 && mv.priority == 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + } + + if (mv1.flinchPercentChance > 0 && mv1.priority == 0) { + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.statChangeMoveType == StatChangeMoveType.DAMAGE_TARGET && + mv.hasSpecificStatChange(StatChangeType.SPEED, false)) || + ((mv.statChangeMoveType == StatChangeMoveType.DAMAGE_USER || + mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_USER) && + mv.hasSpecificStatChange(StatChangeType.SPEED, true))) + .map(mv -> mv.number) + .collect(Collectors.toList())); + } + + if (mv1.statChanges[0].stages >= 2 || mv1.statChanges[1].type != StatChangeType.NONE) { + synergisticMoves.add(Moves.batonPass); + synergisticMoves.add(Moves.storedPower); + synergisticMoves.add(Moves.powerTrip); + } + + if (mv1.statusType == StatusType.SLEEP) { + synergisticMoves.add(Moves.dreamEater); + synergisticMoves.add(Moves.nightmare); + synergisticMoves.add(Moves.hex); + } + + switch(mv1.number) { + case Moves.toxic: + synergisticMoves.add(Moves.protect); + synergisticMoves.add(Moves.detect); + synergisticMoves.add(Moves.kingsShield); + synergisticMoves.add(Moves.dig); + synergisticMoves.add(Moves.fly); + synergisticMoves.add(Moves.bounce); + synergisticMoves.add(Moves.dive); + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.isTrapMove)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + // fallthrough + case Moves.poisonPowder: + case Moves.poisonGas: + case Moves.banefulBunker: + case Moves.toxicThread: + synergisticMoves.add(Moves.venoshock); + synergisticMoves.add(Moves.hex); + break; + case Moves.venoshock: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.category == MoveCategory.STATUS && + (mv.statusType == StatusType.POISON || mv.statusType == StatusType.TOXIC_POISON)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Moves.protect: + case Moves.detect: + case Moves.kingsShield: + synergisticMoves.add(Moves.toxic); + synergisticMoves.add(Moves.leechSeed); + synergisticMoves.add(Moves.willOWisp); + break; + case Moves.batonPass: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.statChanges[0].stages >= 2 || mv.statChanges[1].type != StatChangeType.NONE) + .map(mv -> mv.number) + .collect(Collectors.toList())); + synergisticMoves.add(Moves.shellSmash); + break; + case Moves.willOWisp: + synergisticMoves.add(Moves.hex); + break; + case Moves.lockOn: + case Moves.mindReader: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.hitratio <= 50)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Moves.sunnyDay: + synergisticMoves.add(Moves.solarBlade); + synergisticMoves.add(Moves.solarBeam); + break; + case Moves.rainDance: + synergisticMoves.add(Moves.thunder); + synergisticMoves.add(Moves.hurricane); + break; + case Moves.powerSwap: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.statChangeMoveType == StatChangeMoveType.DAMAGE_USER && + (mv.hasSpecificStatChange(StatChangeType.ATTACK, false) || + mv.hasSpecificStatChange(StatChangeType.SPECIAL_ATTACK, false))) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Moves.endure: + synergisticMoves.add(Moves.reversal); + synergisticMoves.add(Moves.flail); + synergisticMoves.add(Moves.endeavor); + break; + case Moves.endeavor: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.category != MoveCategory.STATUS && mv.priority > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Moves.thunderWave: + case Moves.glare: + case Moves.stunSpore: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.flinchPercentChance > 0) + .map(mv -> mv.number) + .collect(Collectors.toList())); + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.category == MoveCategory.STATUS && mv.statusType == StatusType.CONFUSION) + .map(mv -> mv.number) + .collect(Collectors.toList())); + synergisticMoves.add(Moves.hex); + break; + case Moves.hail: + synergisticMoves.add(Moves.blizzard); + synergisticMoves.add(Moves.auroraVeil); + break; + case Moves.stockpile: + synergisticMoves.add(Moves.spitUp); + synergisticMoves.add(Moves.swallow); + break; + case Moves.spitUp: + case Moves.swallow: + synergisticMoves.add(Moves.stockpile); + break; + case Moves.leechSeed: + case Moves.perishSong: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.isTrapMove)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Moves.spikes: + case Moves.stealthRock: + case Moves.toxicSpikes: + synergisticMoves.add(Moves.roar); + synergisticMoves.add(Moves.whirlwind); + synergisticMoves.add(Moves.dragonTail); + synergisticMoves.add(Moves.circleThrow); + break; + case Moves.rest: + synergisticMoves.add(Moves.sleepTalk); + break; + case Moves.focusEnergy: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.criticalChance == CriticalChance.INCREASED) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Moves.focusPunch: + case Moves.dreamEater: + case Moves.nightmare: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.statusMoveType == StatusMoveType.NO_DAMAGE && + mv.statusType == StatusType.SLEEP) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Moves.torment: + synergisticMoves.add(Moves.encore); + break; + case Moves.encore: + synergisticMoves.add(Moves.torment); + break; + case Moves.hex: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.statusMoveType == StatusMoveType.NO_DAMAGE && + mv.statusType != StatusType.CONFUSION) + .map(mv -> mv.number) + .collect(Collectors.toList())); + synergisticMoves.add(Moves.banefulBunker); + break; + case Moves.storedPower: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.statChanges[0].stages > 1 || mv.statChanges[1].type != StatChangeType.NONE) + .map(mv -> mv.number) + .collect(Collectors.toList())); + synergisticMoves.add(Moves.acupressure); + synergisticMoves.add(Moves.shellSmash); + break; + case Moves.swagger: + synergisticMoves.add(Moves.punishment); + break; + case Moves.punishment: + synergisticMoves.add(Moves.swagger); + break; + case Moves.shellSmash: + synergisticMoves.add(Moves.storedPower); + break; + } + + return moveList + .stream() + .filter(mv -> synergisticMoves.contains(mv.number)) + .distinct() + .collect(Collectors.toList()); + } + + public static List getSoftMoveSynergy(Move mv1, List moveList, int generation, + boolean effectivenessUpdated) { + List synergisticMoves = new ArrayList<>(); + + if (mv1.category != MoveCategory.STATUS) { + List notVeryEffective = Effectiveness.notVeryEffective(mv1.type, generation, effectivenessUpdated); + for (Type nveType: notVeryEffective) { + List superEffectiveAgainstNVE = + Effectiveness.against(nveType, null, generation, effectivenessUpdated) + .entrySet() + .stream() + .filter(entry -> entry.getValue() == Effectiveness.DOUBLE) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.category != MoveCategory.STATUS && + superEffectiveAgainstNVE.contains(mv.type)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + } + } + + switch(mv1.number) { + case Moves.swordsDance: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.category == MoveCategory.PHYSICAL) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Moves.nastyPlot: + case Moves.tailGlow: + synergisticMoves.addAll(moveList + .stream() + .filter(mv -> mv.category == MoveCategory.SPECIAL) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + } + + return moveList + .stream() + .filter(mv -> synergisticMoves.contains(mv.number)) + .distinct() + .collect(Collectors.toList()); + } + + public static List getHardMoveAntiSynergy(Move mv1, List moveList) { + List antiSynergisticMoves = new ArrayList<>(); + + + if (mv1.category == MoveCategory.STATUS && mv1.statusType != StatusType.NONE) { + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.category == MoveCategory.STATUS && mv.statusType != StatusType.NONE && + (mv.statusType == mv1.statusType || + (mv1.statusType != StatusType.CONFUSION && mv.statusType != StatusType.CONFUSION)))) + .map(mv -> mv.number) + .collect(Collectors.toList())); + } + + switch(mv1.number) { + case Moves.protect: + antiSynergisticMoves.add(Moves.detect); + antiSynergisticMoves.add(Moves.banefulBunker); + antiSynergisticMoves.add(Moves.kingsShield); + break; + case Moves.detect: + antiSynergisticMoves.add(Moves.protect); + antiSynergisticMoves.add(Moves.banefulBunker); + antiSynergisticMoves.add(Moves.kingsShield); + break; + case Moves.kingsShield: + antiSynergisticMoves.add(Moves.protect); + antiSynergisticMoves.add(Moves.detect); + antiSynergisticMoves.add(Moves.banefulBunker); + break; + case Moves.banefulBunker: + antiSynergisticMoves.add(Moves.protect); + antiSynergisticMoves.add(Moves.detect); + antiSynergisticMoves.add(Moves.kingsShield); + break; + case Moves.returnTheMoveNotTheKeyword: + antiSynergisticMoves.add(Moves.frustration); + break; + case Moves.frustration: + antiSynergisticMoves.add(Moves.returnTheMoveNotTheKeyword); + break; + case Moves.leechSeed: + case Moves.perishSong: + antiSynergisticMoves.add(Moves.whirlwind); + antiSynergisticMoves.add(Moves.roar); + antiSynergisticMoves.add(Moves.circleThrow); + antiSynergisticMoves.add(Moves.dragonTail); + break; + } + + if (mv1.type != null) { + switch (mv1.type) { + case FIRE: + if (mv1.category != MoveCategory.STATUS) { + antiSynergisticMoves.add(Moves.waterSport); + } + break; + case ELECTRIC: + if (mv1.category != MoveCategory.STATUS) { + antiSynergisticMoves.add(Moves.mudSport); + } + break; + } + } + + return moveList + .stream() + .filter(mv -> antiSynergisticMoves.contains(mv.number)) + .distinct() + .collect(Collectors.toList()); + } + + public static List getSoftMoveAntiSynergy(Move mv1, List moveList) { + List antiSynergisticMoves = new ArrayList<>(); + + + if (mv1.category != MoveCategory.STATUS) { + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.category != MoveCategory.STATUS && mv.type == mv1.type)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + } + + switch (mv1.number) { + case Moves.waterSport: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.category != MoveCategory.STATUS && mv.type == Type.FIRE)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + case Moves.mudSport: + antiSynergisticMoves.addAll(moveList + .stream() + .filter(mv -> (mv.category != MoveCategory.STATUS && mv.type == Type.ELECTRIC)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + } + + return moveList + .stream() + .filter(mv -> antiSynergisticMoves.contains(mv.number)) + .distinct() + .collect(Collectors.toList()); + } + + public static List requiresOtherMove(Move mv1, List moveList) { + List requiresMove = new ArrayList<>(); + switch(mv1.number) { + case Moves.spitUp: + case Moves.swallow: + requiresMove.add(Moves.stockpile); + break; + case Moves.dreamEater: + case Moves.nightmare: + requiresMove.addAll(moveList + .stream() + .filter(mv -> (mv.category == MoveCategory.STATUS && mv.statusType == StatusType.SLEEP)) + .map(mv -> mv.number) + .collect(Collectors.toList())); + break; + } + return moveList.stream().filter(mv -> requiresMove.contains(mv.number)).distinct().collect(Collectors.toList()); + } +} diff --git a/src/com/pkrandom/pokemon/PickupItem.java b/src/com/pkrandom/pokemon/PickupItem.java new file mode 100644 index 0000000..9b90683 --- /dev/null +++ b/src/com/pkrandom/pokemon/PickupItem.java @@ -0,0 +1,11 @@ +package com.pkrandom.pokemon; + +public class PickupItem { + public int item; + public int[] probabilities; + + public PickupItem(int item) { + this.item = item; + this.probabilities = new int[10]; + } +} diff --git a/src/com/pkrandom/pokemon/Pokemon.java b/src/com/pkrandom/pokemon/Pokemon.java new file mode 100755 index 0000000..c868b9d --- /dev/null +++ b/src/com/pkrandom/pokemon/Pokemon.java @@ -0,0 +1,324 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- Pokemon.java - represents an individual Pokemon, and contains --*/ +/*-- common Pokemon-related functions. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.constants.Species; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +public class Pokemon implements Comparable { + + public String name; + public int number; + + public String formeSuffix = ""; + public Pokemon baseForme = null; + public int formeNumber = 0; + public int cosmeticForms = 0; + public int formeSpriteIndex = 0; + public boolean actuallyCosmetic = false; + public List realCosmeticFormNumbers = new ArrayList<>(); + + public Type primaryType, secondaryType; + + public int hp, attack, defense, spatk, spdef, speed, special; + + public int ability1, ability2, ability3; + + public int catchRate, expYield; + + public int guaranteedHeldItem, commonHeldItem, rareHeldItem, darkGrassHeldItem; + + public int genderRatio; + + public int frontSpritePointer, picDimensions; + + public int callRate; + + public ExpCurve growthCurve; + + public List evolutionsFrom = new ArrayList<>(); + public List evolutionsTo = new ArrayList<>(); + + public List megaEvolutionsFrom = new ArrayList<>(); + public List megaEvolutionsTo = new ArrayList<>(); + + protected List shuffledStatsOrder; + + // A flag to use for things like recursive stats copying. + // Must not rely on the state of this flag being preserved between calls. + public boolean temporaryFlag; + + public Pokemon() { + shuffledStatsOrder = Arrays.asList(0, 1, 2, 3, 4, 5); + } + + public void shuffleStats(Random random) { + Collections.shuffle(shuffledStatsOrder, random); + applyShuffledOrderToStats(); + } + + public void copyShuffledStatsUpEvolution(Pokemon evolvesFrom) { + // If stats were already shuffled once, un-shuffle them + shuffledStatsOrder = Arrays.asList( + shuffledStatsOrder.indexOf(0), + shuffledStatsOrder.indexOf(1), + shuffledStatsOrder.indexOf(2), + shuffledStatsOrder.indexOf(3), + shuffledStatsOrder.indexOf(4), + shuffledStatsOrder.indexOf(5)); + applyShuffledOrderToStats(); + shuffledStatsOrder = evolvesFrom.shuffledStatsOrder; + applyShuffledOrderToStats(); + } + + protected void applyShuffledOrderToStats() { + List stats = Arrays.asList(hp, attack, defense, spatk, spdef, speed); + + // Copy in new stats + hp = stats.get(shuffledStatsOrder.get(0)); + attack = stats.get(shuffledStatsOrder.get(1)); + defense = stats.get(shuffledStatsOrder.get(2)); + spatk = stats.get(shuffledStatsOrder.get(3)); + spdef = stats.get(shuffledStatsOrder.get(4)); + speed = stats.get(shuffledStatsOrder.get(5)); + } + + public void randomizeStatsWithinBST(Random random) { + if (number == Species.shedinja) { + // Shedinja is horribly broken unless we restrict him to 1HP. + int bst = bst() - 51; + + // Make weightings + double atkW = random.nextDouble(), defW = random.nextDouble(); + double spaW = random.nextDouble(), spdW = random.nextDouble(), speW = random.nextDouble(); + + double totW = atkW + defW + spaW + spdW + speW; + + hp = 1; + attack = (int) Math.max(1, Math.round(atkW / totW * bst)) + 10; + defense = (int) Math.max(1, Math.round(defW / totW * bst)) + 10; + spatk = (int) Math.max(1, Math.round(spaW / totW * bst)) + 10; + spdef = (int) Math.max(1, Math.round(spdW / totW * bst)) + 10; + speed = (int) Math.max(1, Math.round(speW / totW * bst)) + 10; + } else { + // Minimum 20 HP, 10 everything else + int bst = bst() - 70; + + // Make weightings + double hpW = random.nextDouble(), atkW = random.nextDouble(), defW = random.nextDouble(); + double spaW = random.nextDouble(), spdW = random.nextDouble(), speW = random.nextDouble(); + + double totW = hpW + atkW + defW + spaW + spdW + speW; + + hp = (int) Math.max(1, Math.round(hpW / totW * bst)) + 20; + attack = (int) Math.max(1, Math.round(atkW / totW * bst)) + 10; + defense = (int) Math.max(1, Math.round(defW / totW * bst)) + 10; + spatk = (int) Math.max(1, Math.round(spaW / totW * bst)) + 10; + spdef = (int) Math.max(1, Math.round(spdW / totW * bst)) + 10; + speed = (int) Math.max(1, Math.round(speW / totW * bst)) + 10; + } + + // Check for something we can't store + if (hp > 255 || attack > 255 || defense > 255 || spatk > 255 || spdef > 255 || speed > 255) { + // re roll + randomizeStatsWithinBST(random); + } + + } + + public void copyRandomizedStatsUpEvolution(Pokemon evolvesFrom) { + double ourBST = bst(); + double theirBST = evolvesFrom.bst(); + + double bstRatio = ourBST / theirBST; + + hp = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.hp * bstRatio))); + attack = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.attack * bstRatio))); + defense = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.defense * bstRatio))); + speed = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.speed * bstRatio))); + spatk = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.spatk * bstRatio))); + spdef = (int) Math.min(255, Math.max(1, Math.round(evolvesFrom.spdef * bstRatio))); + } + + public void assignNewStatsForEvolution(Pokemon evolvesFrom, Random random) { + + double ourBST = bst(); + double theirBST = evolvesFrom.bst(); + + double bstDiff = ourBST - theirBST; + + // Make weightings + double hpW = random.nextDouble(), atkW = random.nextDouble(), defW = random.nextDouble(); + double spaW = random.nextDouble(), spdW = random.nextDouble(), speW = random.nextDouble(); + + double totW = hpW + atkW + defW + spaW + spdW + speW; + + double hpDiff = Math.round((hpW / totW) * bstDiff); + double atkDiff = Math.round((atkW / totW) * bstDiff); + double defDiff = Math.round((defW / totW) * bstDiff); + double spaDiff = Math.round((spaW / totW) * bstDiff); + double spdDiff = Math.round((spdW / totW) * bstDiff); + double speDiff = Math.round((speW / totW) * bstDiff); + + hp = (int) Math.min(255, Math.max(1, evolvesFrom.hp + hpDiff)); + attack = (int) Math.min(255, Math.max(1, evolvesFrom.attack + atkDiff)); + defense = (int) Math.min(255, Math.max(1, evolvesFrom.defense + defDiff)); + speed = (int) Math.min(255, Math.max(1, evolvesFrom.speed + speDiff)); + spatk = (int) Math.min(255, Math.max(1, evolvesFrom.spatk + spaDiff)); + spdef = (int) Math.min(255, Math.max(1, evolvesFrom.spdef + spdDiff)); + } + + protected int bst() { + return hp + attack + defense + spatk + spdef + speed; + } + + public int bstForPowerLevels() { + // Take into account Shedinja's purposefully nerfed HP + if (number == Species.shedinja) { + return (attack + defense + spatk + spdef + speed) * 6 / 5; + } else { + return hp + attack + defense + spatk + spdef + speed; + } + } + + public double getAttackSpecialAttackRatio() { + return (double)attack / ((double)attack + (double)spatk); + } + + public int getBaseNumber() { + Pokemon base = this; + while (base.baseForme != null) { + base = base.baseForme; + } + return base.number; + } + + public void copyBaseFormeBaseStats(Pokemon baseForme) { + hp = baseForme.hp; + attack = baseForme.attack; + defense = baseForme.defense; + speed = baseForme.speed; + spatk = baseForme.spatk; + spdef = baseForme.spdef; + } + + public void copyBaseFormeAbilities(Pokemon baseForme) { + ability1 = baseForme.ability1; + ability2 = baseForme.ability2; + ability3 = baseForme.ability3; + } + + public void copyBaseFormeEvolutions(Pokemon baseForme) { + evolutionsFrom = baseForme.evolutionsFrom; + } + + public int getSpriteIndex() { + return formeNumber == 0 ? number : formeSpriteIndex + formeNumber - 1; + } + + public String fullName() { + return name + formeSuffix; + } + + @Override + public String toString() { + return "Pokemon [name=" + name + formeSuffix + ", number=" + number + ", primaryType=" + primaryType + + ", secondaryType=" + secondaryType + ", hp=" + hp + ", attack=" + attack + ", defense=" + defense + + ", spatk=" + spatk + ", spdef=" + spdef + ", speed=" + speed + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + number; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Pokemon other = (Pokemon) obj; + return number == other.number; + } + + @Override + public int compareTo(Pokemon o) { + return number - o.number; + } + + private static final List legendaries = Arrays.asList(Species.articuno, Species.zapdos, Species.moltres, + Species.mewtwo, Species.mew, Species.raikou, Species.entei, Species.suicune, Species.lugia, Species.hoOh, + Species.celebi, Species.regirock, Species.regice, Species.registeel, Species.latias, Species.latios, + Species.kyogre, Species.groudon, Species.rayquaza, Species.jirachi, Species.deoxys, Species.uxie, + Species.mesprit, Species.azelf, Species.dialga, Species.palkia, Species.heatran, Species.regigigas, + Species.giratina, Species.cresselia, Species.phione, Species.manaphy, Species.darkrai, Species.shaymin, + Species.arceus, Species.victini, Species.cobalion, Species.terrakion, Species.virizion, Species.tornadus, + Species.thundurus, Species.reshiram, Species.zekrom, Species.landorus, Species.kyurem, Species.keldeo, + Species.meloetta, Species.genesect, Species.xerneas, Species.yveltal, Species.zygarde, Species.diancie, + Species.hoopa, Species.volcanion, Species.typeNull, Species.silvally, Species.tapuKoko, Species.tapuLele, + Species.tapuBulu, Species.tapuFini, Species.cosmog, Species.cosmoem, Species.solgaleo, Species.lunala, + Species.necrozma, Species.magearna, Species.marshadow, Species.zeraora); + + private static final List strongLegendaries = Arrays.asList(Species.mewtwo, Species.lugia, Species.hoOh, + Species.kyogre, Species.groudon, Species.rayquaza, Species.dialga, Species.palkia, Species.regigigas, + Species.giratina, Species.arceus, Species.reshiram, Species.zekrom, Species.kyurem, Species.xerneas, + Species.yveltal, Species.cosmog, Species.cosmoem, Species.solgaleo, Species.lunala); + + private static final List ultraBeasts = Arrays.asList(Species.nihilego, Species.buzzwole, Species.pheromosa, + Species.xurkitree, Species.celesteela, Species.kartana, Species.guzzlord, Species.poipole, Species.naganadel, + Species.stakataka, Species.blacephalon); + + public boolean isLegendary() { + return formeNumber == 0 ? legendaries.contains(this.number) : legendaries.contains(this.baseForme.number); + } + + public boolean isStrongLegendary() { + return formeNumber == 0 ? strongLegendaries.contains(this.number) : strongLegendaries.contains(this.baseForme.number); + } + + // This method can only be used in contexts where alt formes are NOT involved; otherwise, some alt formes + // will be considered as Ultra Beasts in SM. + // In contexts where formes are involved, use "if (ultraBeastList.contains(...))" instead, + // assuming "checkPokemonRestrictions" has been used at some point beforehand. + public boolean isUltraBeast() { + return ultraBeasts.contains(this.number); + } + + public int getCosmeticFormNumber(int num) { + return realCosmeticFormNumbers.isEmpty() ? num : realCosmeticFormNumbers.get(num); + } + +} diff --git a/src/com/pkrandom/pokemon/SOSType.java b/src/com/pkrandom/pokemon/SOSType.java new file mode 100644 index 0000000..95c8639 --- /dev/null +++ b/src/com/pkrandom/pokemon/SOSType.java @@ -0,0 +1,28 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- SOSType.java - represents a Gen 7 SOS Encounter's type --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public enum SOSType { + GENERIC, RAIN, HAIL, SAND +} diff --git a/src/com/pkrandom/pokemon/Shop.java b/src/com/pkrandom/pokemon/Shop.java new file mode 100644 index 0000000..34a9f1d --- /dev/null +++ b/src/com/pkrandom/pokemon/Shop.java @@ -0,0 +1,42 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- Shop.java - represents a shop with a list of purchasable items. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.List; + +public class Shop { + public List items; + public String name; + public boolean isMainGame; + + public Shop() { + this.isMainGame = false; + } + + public Shop(Shop otherShop) { + this.items = otherShop.items; + this.name = otherShop.name; + this.isMainGame = otherShop.isMainGame; + } +} diff --git a/src/com/pkrandom/pokemon/Stat.java b/src/com/pkrandom/pokemon/Stat.java new file mode 100644 index 0000000..4826e93 --- /dev/null +++ b/src/com/pkrandom/pokemon/Stat.java @@ -0,0 +1,45 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- Stat.java - represents a Pokemon's stat --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public enum Stat { + HP(1), + ATK(1 << 1), + DEF(1 << 2), + SPATK(1 << 3), + SPDEF(1 << 4), + SPEED(1 << 5), + SPECIAL(1 << 6), + POWER(1 << 7), + ACCURACY(1 << 8), + PP(1 << 9), + TYPE(1 << 10), + CATEGORY(1 << 11); + + public final int val; + + Stat(int val) { + this.val = val; + } +} \ No newline at end of file diff --git a/src/com/pkrandom/pokemon/StatChange.java b/src/com/pkrandom/pokemon/StatChange.java new file mode 100644 index 0000000..7d55e9e --- /dev/null +++ b/src/com/pkrandom/pokemon/StatChange.java @@ -0,0 +1,35 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- StatChange.java - stores updated stat information for a Pokemon --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class StatChange { + + public int stat; + public int[] values; + + public StatChange(int stat, int... values) { + this.stat = stat; + this.values = values; + } +} diff --git a/src/com/pkrandom/pokemon/StatChangeMoveType.java b/src/com/pkrandom/pokemon/StatChangeMoveType.java new file mode 100644 index 0000000..18a0623 --- /dev/null +++ b/src/com/pkrandom/pokemon/StatChangeMoveType.java @@ -0,0 +1,35 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- StatChangeType.java - represents the different types of moves --*/ +/*-- that can change a battler's stats. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public enum StatChangeMoveType { + NONE_OR_UNKNOWN, + NO_DAMAGE_TARGET, + NO_DAMAGE_USER, + NO_DAMAGE_ALL, + NO_DAMAGE_ALLY, + DAMAGE_TARGET, + DAMAGE_USER +} diff --git a/src/com/pkrandom/pokemon/StatChangeType.java b/src/com/pkrandom/pokemon/StatChangeType.java new file mode 100644 index 0000000..e79046c --- /dev/null +++ b/src/com/pkrandom/pokemon/StatChangeType.java @@ -0,0 +1,38 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- StatChangeType.java - represents the types of stat buffs that a move --*/ +/*-- can apply. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public enum StatChangeType { + NONE, + ATTACK, + DEFENSE, + SPECIAL_ATTACK, + SPECIAL_DEFENSE, + SPEED, + ACCURACY, + EVASION, + ALL, + SPECIAL +} \ No newline at end of file diff --git a/src/com/pkrandom/pokemon/StaticEncounter.java b/src/com/pkrandom/pokemon/StaticEncounter.java new file mode 100644 index 0000000..285a27b --- /dev/null +++ b/src/com/pkrandom/pokemon/StaticEncounter.java @@ -0,0 +1,98 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- StaticEncounter.java - stores a static encounter in Gen 6+ --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.ArrayList; +import java.util.List; + +public class StaticEncounter { + public Pokemon pkmn; + public int forme = 0; + public int level; + public int maxLevel = 0; + public int heldItem; + public boolean isEgg = false; + public boolean resetMoves = false; + public boolean restrictedPool = false; + public List restrictedList = new ArrayList<>(); + + // In the games, sometimes what is logically an encounter or set of encounters with one specific Pokemon + // can actually consist of multiple encounters internally. This can happen because: + // - The same Pokemon appears in multiple locations (e.g., Reshiram/Zekrom in BW1, Giratina in Pt) + // - The same Pokemon appears at different levels depending on game progression (e.g., Volcarona in BW2) + // - Rebattling a Pokemon actually is different encounter entirely (e.g., Xerneas/Yveltal in XY) + // This list tracks encounters that should logically have the same species and forme, but *may* have + // differences in other properties like level. + public List linkedEncounters; + + public StaticEncounter() { + this.linkedEncounters = new ArrayList<>(); + } + + public StaticEncounter(Pokemon pkmn) { + this.pkmn = pkmn; + this.linkedEncounters = new ArrayList<>(); + } + + @Override + public String toString() { + return this.toString(true); + } + + public String toString(boolean printLevel) { + if (isEgg) { + return pkmn.fullName() + " (egg)"; + } + else if (!printLevel) { + return pkmn.fullName(); + } + StringBuilder levelStringBuilder = new StringBuilder("Lv" + level); + if (maxLevel > 0) { + levelStringBuilder.append("-").append(maxLevel); + } + boolean needToDisplayLinkedLevels = false; + for (int i = 0; i < linkedEncounters.size(); i++) { + if (level != linkedEncounters.get(i).level) { + needToDisplayLinkedLevels = true; + } + } + if (needToDisplayLinkedLevels) { + for (int i = 0; i < linkedEncounters.size(); i++) { + levelStringBuilder.append(" / ").append("Lv").append(linkedEncounters.get(i).level); + } + } + return pkmn.fullName() + ", " + levelStringBuilder.toString(); + } + + public boolean canMegaEvolve() { + if (heldItem != 0) { + for (MegaEvolution mega: pkmn.megaEvolutionsFrom) { + if (mega.argument == heldItem) { + return true; + } + } + } + return false; + } +} diff --git a/src/com/pkrandom/pokemon/StatusMoveType.java b/src/com/pkrandom/pokemon/StatusMoveType.java new file mode 100644 index 0000000..a2c0bc7 --- /dev/null +++ b/src/com/pkrandom/pokemon/StatusMoveType.java @@ -0,0 +1,31 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- StatusMoveType.java - represents the different types of moves --*/ +/*-- that can inflict a status effect. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public enum StatusMoveType { + NONE_OR_UNKNOWN, + NO_DAMAGE, + DAMAGE +} diff --git a/src/com/pkrandom/pokemon/StatusType.java b/src/com/pkrandom/pokemon/StatusType.java new file mode 100644 index 0000000..bf1e63b --- /dev/null +++ b/src/com/pkrandom/pokemon/StatusType.java @@ -0,0 +1,37 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- StatusType.java - represents the different types of status effects --*/ +/*-- that can be inflicted on a Pokemon. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +// This does not include statuses that are only inflicted by a single move, like Attract, Nightmare, etc. +public enum StatusType { + NONE, + PARALYZE, + SLEEP, + FREEZE, + BURN, + POISON, + CONFUSION, + TOXIC_POISON +} diff --git a/src/com/pkrandom/pokemon/TotemPokemon.java b/src/com/pkrandom/pokemon/TotemPokemon.java new file mode 100644 index 0000000..5134044 --- /dev/null +++ b/src/com/pkrandom/pokemon/TotemPokemon.java @@ -0,0 +1,56 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- TotemPokemon.java - represents a Totem Pokemon encounter --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.Map; +import java.util.TreeMap; + +public class TotemPokemon extends StaticEncounter { + public Aura aura; + public int ally1Offset; + public int ally2Offset; + public Map allies = new TreeMap<>(); + + public boolean unused = false; + + public TotemPokemon() { + + } + + public TotemPokemon(Pokemon pkmn) { + this.pkmn = pkmn; + } + + @Override + public String toString() { + // The %s will be formatted to include the held item. + String ret = pkmn.fullName() + "@%s Lv" + level + "\n Aura: " + aura.toString() + "\n"; + int i = 1; + for (StaticEncounter ally: allies.values()) { + ret = ret.concat(" Ally " + i + ": " + ally.toString() + "\n"); + i++; + } + return ret.concat("\n"); + } +} diff --git a/src/com/pkrandom/pokemon/Trainer.java b/src/com/pkrandom/pokemon/Trainer.java new file mode 100755 index 0000000..ee822fc --- /dev/null +++ b/src/com/pkrandom/pokemon/Trainer.java @@ -0,0 +1,156 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- Trainer.java - represents a Trainer's pokemon set/other details. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.ArrayList; +import java.util.List; + +public class Trainer implements Comparable { + public int offset; + public int index; + public List pokemon = new ArrayList<>(); + public String tag; + public boolean importantTrainer; + // This value has some flags about the trainer's pokemon (e.g. if they have items or custom moves) + public int poketype; + public String name; + public int trainerclass; + public String fullDisplayName; + public MultiBattleStatus multiBattleStatus = MultiBattleStatus.NEVER; + public int forceStarterPosition = -1; + // Certain trainers (e.g., trainers in the PWT in BW2) require unique held items for all of their Pokemon to prevent a game crash. + public boolean requiresUniqueHeldItems; + + public String toString() { + StringBuilder sb = new StringBuilder("["); + if (fullDisplayName != null) { + sb.append(fullDisplayName).append(" "); + } else if (name != null) { + sb.append(name).append(" "); + } + if (trainerclass != 0) { + sb.append("(").append(trainerclass).append(") - "); + } + if (offset > 0) { + sb.append(String.format("%x", offset)); + } + sb.append(" => "); + boolean first = true; + for (TrainerPokemon p : pokemon) { + if (!first) { + sb.append(','); + } + sb.append(p.pokemon.name).append(" Lv").append(p.level); + first = false; + } + sb.append(']'); + if (tag != null) { + sb.append(" (").append(tag).append(")"); + } + return sb.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + index; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Trainer other = (Trainer) obj; + return index == other.index; + } + + @Override + public int compareTo(Trainer o) { + return index - o.index; + } + + public boolean isBoss() { + return tag != null && (tag.startsWith("ELITE") || tag.startsWith("CHAMPION") + || tag.startsWith("UBER") || tag.endsWith("LEADER")); + } + + public boolean isImportant() { + return tag != null && (tag.startsWith("RIVAL") || tag.startsWith("FRIEND") || tag.endsWith("STRONG")); + } + + public boolean skipImportant() { + return ((tag != null) && (tag.startsWith("RIVAL1-") || tag.startsWith("FRIEND1-") || tag.endsWith("NOTSTRONG"))); + } + + public void setPokemonHaveItems(boolean haveItems) { + if (haveItems) { + this.poketype |= 2; + } else { + // https://stackoverflow.com/a/1073328 + this.poketype = poketype & ~2; + } + } + + public boolean pokemonHaveItems() { + // This flag seems consistent for all gens + return (this.poketype & 2) == 2; + } + + public void setPokemonHaveCustomMoves(boolean haveCustomMoves) { + if (haveCustomMoves) { + this.poketype |= 1; + } else { + this.poketype = poketype & ~1; + } + } + + public boolean pokemonHaveCustomMoves() { + // This flag seems consistent for all gens + return (this.poketype & 1) == 1; + } + + public boolean pokemonHaveUniqueHeldItems() { + List heldItemsForThisTrainer = new ArrayList<>(); + for (TrainerPokemon poke : this.pokemon) { + if (poke.heldItem > 0) { + if (heldItemsForThisTrainer.contains(poke.heldItem)) { + return false; + } else { + heldItemsForThisTrainer.add(poke.heldItem); + } + } + } + return true; + } + + public enum MultiBattleStatus { + NEVER, POTENTIAL, ALWAYS + } +} diff --git a/src/com/pkrandom/pokemon/TrainerPokemon.java b/src/com/pkrandom/pokemon/TrainerPokemon.java new file mode 100755 index 0000000..9323f12 --- /dev/null +++ b/src/com/pkrandom/pokemon/TrainerPokemon.java @@ -0,0 +1,109 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- TrainerPokemon.java - represents a Pokemon owned by a trainer. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class TrainerPokemon { + + public Pokemon pokemon; + public int level; + + public int[] moves = {0, 0, 0, 0}; + + public int heldItem = 0; + public boolean hasMegaStone; + public boolean hasZCrystal; + public int abilitySlot; + public int forme; + public String formeSuffix = ""; + + public int forcedGenderFlag; + public byte nature; + public byte hpEVs; + public byte atkEVs; + public byte defEVs; + public byte spatkEVs; + public byte spdefEVs; + public byte speedEVs; + public int IVs; + // In gens 3-5, there is a byte or word that corresponds + // to the IVs a trainer's pokemon has. In X/Y, this byte + // also encodes some other information, possibly related + // to EV spread. Because of the unknown part in X/Y, + // we store the whole "strength byte" so we can + // write it unchanged when randomizing trainer pokemon. + public int strength; + + public boolean resetMoves = false; + + public String toString() { + String s = pokemon.name + formeSuffix; + if (heldItem != 0) { + // This can be filled in with the actual name when written to the log. + s += "@%s"; + } + s+= " Lv" + level; + return s; + } + + public boolean canMegaEvolve() { + if (heldItem != 0) { + for (MegaEvolution mega: pokemon.megaEvolutionsFrom) { + if (mega.argument == heldItem) { + return true; + } + } + } + return false; + } + + public TrainerPokemon copy() { + TrainerPokemon tpk = new TrainerPokemon(); + tpk.pokemon = pokemon; + tpk.level = level; + + tpk.moves[0] = moves[0]; + tpk.moves[1] = moves[1]; + tpk.moves[2] = moves[2]; + tpk.moves[3] = moves[3]; + + tpk.forcedGenderFlag = forcedGenderFlag; + tpk.nature = nature; + tpk.IVs = IVs; + tpk.hpEVs = hpEVs; + tpk.atkEVs = atkEVs; + tpk.defEVs = defEVs; + tpk.spatkEVs = spatkEVs; + tpk.spdefEVs = spdefEVs; + tpk.speedEVs = speedEVs; + tpk.strength = strength; + tpk.heldItem = heldItem; + tpk.abilitySlot = abilitySlot; + tpk.forme = forme; + tpk.formeSuffix = formeSuffix; + + tpk.resetMoves = resetMoves; + + return tpk; + } +} diff --git a/src/com/pkrandom/pokemon/Type.java b/src/com/pkrandom/pokemon/Type.java new file mode 100755 index 0000000..8dfff51 --- /dev/null +++ b/src/com/pkrandom/pokemon/Type.java @@ -0,0 +1,77 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- Type.java - represents a Pokemon or move type. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import com.pkrandom.RomFunctions; + +public enum Type { + + NORMAL, FIGHTING, FLYING, GRASS, WATER, FIRE, ROCK, GROUND, PSYCHIC, BUG, DRAGON, ELECTRIC, GHOST, POISON, ICE, STEEL, DARK, FAIRY, + GAS(true), WOOD(true), ABNORMAL(true), WIND(true), SOUND(true), LIGHT(true), TRI(true); + + public boolean isHackOnly; + + Type() { + this.isHackOnly = false; + } + + Type(boolean isHackOnly) { + this.isHackOnly = isHackOnly; + } + + private static final List VALUES = Collections.unmodifiableList(Arrays.asList(values())); + private static final int SIZE = VALUES.size(); + + public static final List GEN1 = Collections.unmodifiableList(Arrays.asList(values()).subList(0, ICE.ordinal()+1)); + public static final List GEN2THROUGH5 = Collections.unmodifiableList(Arrays.asList(values()).subList(0, DARK.ordinal()+1)); + public static final List GEN6PLUS = Collections.unmodifiableList(Arrays.asList(values()).subList(0, FAIRY.ordinal()+1)); + + public static List getAllTypes(int generation) { + switch (generation) { + case 1: + return GEN1; + case 2: + case 3: + case 4: + case 5: + return GEN2THROUGH5; + default: + return GEN6PLUS; + } + } + + public static Type randomType(Random random) { + return VALUES.get(random.nextInt(SIZE)); + } + + public String camelCase() { + return RomFunctions.camelCase(this.toString()); + } + +} diff --git a/src/com/pkrandom/pokemon/TypeRelationship.java b/src/com/pkrandom/pokemon/TypeRelationship.java new file mode 100644 index 0000000..b63dc73 --- /dev/null +++ b/src/com/pkrandom/pokemon/TypeRelationship.java @@ -0,0 +1,37 @@ +package com.pkrandom.pokemon; + +/*----------------------------------------------------------------------------*/ +/*-- TypeRelationship.java - represents the relationship between an --*/ +/*-- attacking and defending Type. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +public class TypeRelationship { + public Type attacker; + public Type defender; + public Effectiveness effectiveness; + + public TypeRelationship(Type attacker, Type defender, Effectiveness effectiveness) { + this.attacker = attacker; + this.defender = defender; + this.effectiveness = effectiveness; + } +} diff --git a/src/com/pkrandom/romhandlers/Abstract3DSRomHandler.java b/src/com/pkrandom/romhandlers/Abstract3DSRomHandler.java new file mode 100644 index 0000000..94b7111 --- /dev/null +++ b/src/com/pkrandom/romhandlers/Abstract3DSRomHandler.java @@ -0,0 +1,350 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- Abstract3DSRomHandler.java - a base class for 3DS rom handlers --*/ +/*-- which standardises common 3DS functions. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.FileFunctions; +import com.pkrandom.ctr.GARCArchive; +import com.pkrandom.ctr.NCCH; +import com.pkrandom.exceptions.CannotWriteToLocationException; +import com.pkrandom.exceptions.EncryptedROMException; +import com.pkrandom.exceptions.RandomizerIOException; +import com.pkrandom.pokemon.Type; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Random; + +public abstract class Abstract3DSRomHandler extends AbstractRomHandler { + + private NCCH baseRom; + private NCCH gameUpdate; + private String loadedFN; + + public Abstract3DSRomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + @Override + public boolean loadRom(String filename) { + String productCode = getProductCodeFromFile(filename); + String titleId = getTitleIdFromFile(filename); + if (!this.detect3DSRom(productCode, titleId)) { + return false; + } + // Load inner rom + try { + baseRom = new NCCH(filename, productCode, titleId); + if (!baseRom.isDecrypted()) { + throw new EncryptedROMException(filename); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + loadedFN = filename; + this.loadedROM(productCode, titleId); + return true; + } + + protected abstract boolean detect3DSRom(String productCode, String titleId); + + @Override + public String loadedFilename() { + return loadedFN; + } + + protected abstract void loadedROM(String productCode, String titleId); + + protected abstract void savingROM() throws IOException; + + protected abstract String getGameAcronym(); + + @Override + public boolean saveRomFile(String filename, long seed) { + try { + savingROM(); + baseRom.saveAsNCCH(filename, getGameAcronym(), seed); + } catch (IOException | NoSuchAlgorithmException e) { + if (e.getMessage().contains("Access is denied")) { + throw new CannotWriteToLocationException("The randomizer cannot write to this location: " + filename); + } else { + throw new RandomizerIOException(e); + } + } + return true; + } + + @Override + public boolean saveRomDirectory(String filename) { + try { + savingROM(); + baseRom.saveAsLayeredFS(filename); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return true; + } + + protected abstract boolean isGameUpdateSupported(int version); + + @Override + public boolean hasGameUpdateLoaded() { + return gameUpdate != null; + } + + @Override + public boolean loadGameUpdate(String filename) { + String productCode = getProductCodeFromFile(filename); + String titleId = getTitleIdFromFile(filename); + try { + gameUpdate = new NCCH(filename, productCode, titleId); + if (!gameUpdate.isDecrypted()) { + throw new EncryptedROMException(filename); + } + int version = gameUpdate.getVersion(); + if (!this.isGameUpdateSupported(version)) { + System.out.println("Game Update: Supplied unexpected version " + version); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + this.loadedROM(baseRom.getProductCode(), baseRom.getTitleId()); + return true; + } + + @Override + public void removeGameUpdate() { + gameUpdate = null; + this.loadedROM(baseRom.getProductCode(), baseRom.getTitleId()); + } + + protected abstract String getGameVersion(); + + @Override + public String getGameUpdateVersion() { + return getGameVersion(); + } + + @Override + public void printRomDiagnostics(PrintStream logStream) { + baseRom.printRomDiagnostics(logStream, gameUpdate); + } + + public void closeInnerRom() throws IOException { + baseRom.closeROM(); + } + + @Override + public boolean hasPhysicalSpecialSplit() { + // Default value for Gen4+. + // Handlers can override again in case of ROM hacks etc. + return true; + } + + protected byte[] readCode() throws IOException { + if (gameUpdate != null) { + return gameUpdate.getCode(); + } + return baseRom.getCode(); + } + + protected void writeCode(byte[] data) throws IOException { + baseRom.writeCode(data); + } + + protected GARCArchive readGARC(String subpath, boolean skipDecompression) throws IOException { + return new GARCArchive(readFile(subpath),skipDecompression); + } + + protected GARCArchive readGARC(String subpath, List compressThese) throws IOException { + return new GARCArchive(readFile(subpath),compressThese); + } + + protected void writeGARC(String subpath, GARCArchive garc) throws IOException { + this.writeFile(subpath,garc.getBytes()); + } + + protected byte[] readFile(String location) throws IOException { + if (gameUpdate != null && gameUpdate.hasFile(location)) { + return gameUpdate.getFile(location); + } + return baseRom.getFile(location); + } + + protected void writeFile(String location, byte[] data) throws IOException { + writeFile(location, data, 0, data.length); + } + + protected void readByteIntoFlags(byte[] data, boolean[] flags, int offsetIntoFlags, int offsetIntoData) { + int thisByte = data[offsetIntoData] & 0xFF; + for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { + flags[offsetIntoFlags + i] = ((thisByte >> i) & 0x01) == 0x01; + } + } + + protected byte getByteFromFlags(boolean[] flags, int offsetIntoFlags) { + int thisByte = 0; + for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { + thisByte |= (flags[offsetIntoFlags + i] ? 1 : 0) << i; + } + return (byte) thisByte; + } + + protected int readWord(byte[] data, int offset) { + return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8); + } + + protected void writeWord(byte[] data, int offset, int value) { + data[offset] = (byte) (value & 0xFF); + data[offset + 1] = (byte) ((value >> 8) & 0xFF); + } + + protected int readLong(byte[] data, int offset) { + return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) | ((data[offset + 2] & 0xFF) << 16) + | ((data[offset + 3] & 0xFF) << 24); + } + + protected void writeLong(byte[] data, int offset, int value) { + data[offset] = (byte) (value & 0xFF); + data[offset + 1] = (byte) ((value >> 8) & 0xFF); + data[offset + 2] = (byte) ((value >> 16) & 0xFF); + data[offset + 3] = (byte) ((value >> 24) & 0xFF); + } + + protected void writeFile(String location, byte[] data, int offset, int length) throws IOException { + if (offset != 0 || length != data.length) { + byte[] newData = new byte[length]; + System.arraycopy(data, offset, newData, 0, length); + data = newData; + } + baseRom.writeFile(location, data); + if (gameUpdate != null && gameUpdate.hasFile(location)) { + gameUpdate.writeFile(location, data); + } + } + + public String getTitleIdFromLoadedROM() { + return baseRom.getTitleId(); + } + + protected static String getProductCodeFromFile(String filename) { + try { + long ncchStartingOffset = NCCH.getCXIOffsetInFile(filename); + if (ncchStartingOffset == -1) { + return null; + } + FileInputStream fis = new FileInputStream(filename); + fis.skip(ncchStartingOffset + 0x150); + byte[] productCode = FileFunctions.readFullyIntoBuffer(fis, 0x10); + fis.close(); + return new String(productCode, "UTF-8").trim(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + public static String getTitleIdFromFile(String filename) { + try { + long ncchStartingOffset = NCCH.getCXIOffsetInFile(filename); + if (ncchStartingOffset == -1) { + return null; + } + FileInputStream fis = new FileInputStream(filename); + fis.skip(ncchStartingOffset + 0x118); + byte[] programId = FileFunctions.readFullyIntoBuffer(fis, 0x8); + fis.close(); + reverseArray(programId); + return bytesToHex(programId); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private static void reverseArray(byte[] bytes) { + for (int i = 0; i < bytes.length / 2; i++) { + byte temp = bytes[i]; + bytes[i] = bytes[bytes.length - i - 1]; + bytes[bytes.length - i - 1] = temp; + } + } + + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + private static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int i = 0; i < bytes.length; i++) { + int unsignedByte = bytes[i] & 0xFF; + hexChars[i * 2] = HEX_ARRAY[unsignedByte >>> 4]; + hexChars[i * 2 + 1] = HEX_ARRAY[unsignedByte & 0x0F]; + } + return new String(hexChars); + } + + protected int typeTMPaletteNumber(Type t, boolean isGen7) { + if (t == null) { + return 322; // CURSE + } + switch (t) { + case DARK: + return 309; + case DRAGON: + return 310; + case PSYCHIC: + return 311; + case NORMAL: + return 312; + case POISON: + return 313; + case ICE: + return 314; + case FIGHTING: + return 315; + case FIRE: + return 316; + case WATER: + return 317; + case FLYING: + return 323; + case GRASS: + return 318; + case ROCK: + return 319; + case ELECTRIC: + return 320; + case GROUND: + return 321; + case GHOST: + default: + return 322; // for CURSE + case STEEL: + return 324; + case BUG: + return 325; + case FAIRY: + return isGen7 ? 555 : 546; + } + } +} diff --git a/src/com/pkrandom/romhandlers/AbstractDSRomHandler.java b/src/com/pkrandom/romhandlers/AbstractDSRomHandler.java new file mode 100755 index 0000000..3736758 --- /dev/null +++ b/src/com/pkrandom/romhandlers/AbstractDSRomHandler.java @@ -0,0 +1,390 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- AbstractDSRomHandler.java - a base class for DS rom handlers --*/ +/*-- which standardises common DS functions. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import com.pkrandom.FileFunctions; +import com.pkrandom.RomFunctions; +import com.pkrandom.exceptions.CannotWriteToLocationException; +import com.pkrandom.exceptions.RandomizerIOException; +import com.pkrandom.newnds.NARCArchive; +import com.pkrandom.newnds.NDSRom; +import com.pkrandom.pokemon.Type; + +public abstract class AbstractDSRomHandler extends AbstractRomHandler { + + protected String dataFolder; + private NDSRom baseRom; + private String loadedFN; + private boolean arm9Extended = false; + + public AbstractDSRomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + protected abstract boolean detectNDSRom(String ndsCode, byte version); + + @Override + public boolean loadRom(String filename) { + if (!this.detectNDSRom(getROMCodeFromFile(filename), getVersionFromFile(filename))) { + return false; + } + // Load inner rom + try { + baseRom = new NDSRom(filename); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + loadedFN = filename; + loadedROM(baseRom.getCode(), baseRom.getVersion()); + return true; + } + + @Override + public String loadedFilename() { + return loadedFN; + } + + protected byte[] get3byte(int amount) { + byte[] ret = new byte[3]; + ret[0] = (byte) (amount & 0xFF); + ret[1] = (byte) ((amount >> 8) & 0xFF); + ret[2] = (byte) ((amount >> 16) & 0xFF); + return ret; + } + + protected abstract void loadedROM(String romCode, byte version); + + protected abstract void savingROM(); + + @Override + public boolean saveRomFile(String filename, long seed) { + savingROM(); + try { + baseRom.saveTo(filename); + } catch (IOException e) { + if (e.getMessage().contains("Access is denied")) { + throw new CannotWriteToLocationException("The randomizer cannot write to this location: " + filename); + } else { + throw new RandomizerIOException(e); + } + } + return true; + } + + @Override + public boolean saveRomDirectory(String filename) { + // do nothing. DS games do have the concept of a filesystem, but it's way more + // convenient for users to use ROM files instead. + return true; + } + + @Override + public boolean hasGameUpdateLoaded() { + return false; + } + + @Override + public boolean loadGameUpdate(String filename) { + // do nothing, as DS games don't have external game updates + return true; + } + + @Override + public void removeGameUpdate() { + // do nothing, as DS games don't have external game updates + } + + @Override + public String getGameUpdateVersion() { + // do nothing, as DS games don't have external game updates + return null; + } + + @Override + public void printRomDiagnostics(PrintStream logStream) { + baseRom.printRomDiagnostics(logStream); + } + + public void closeInnerRom() throws IOException { + baseRom.closeROM(); + } + + @Override + public boolean canChangeStaticPokemon() { + return false; + } + + @Override + public boolean hasPhysicalSpecialSplit() { + // Default value for Gen4+. + // Handlers can override again in case of ROM hacks etc. + return true; + } + + public NARCArchive readNARC(String subpath) throws IOException { + return new NARCArchive(readFile(subpath)); + } + + public void writeNARC(String subpath, NARCArchive narc) throws IOException { + this.writeFile(subpath, narc.getBytes()); + } + + protected static String getROMCodeFromFile(String filename) { + try { + FileInputStream fis = new FileInputStream(filename); + fis.skip(0x0C); + byte[] sig = FileFunctions.readFullyIntoBuffer(fis, 4); + fis.close(); + return new String(sig, "US-ASCII"); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + protected static byte getVersionFromFile(String filename) { + try { + FileInputStream fis = new FileInputStream(filename); + fis.skip(0x1E); + byte[] version = FileFunctions.readFullyIntoBuffer(fis, 1); + fis.close(); + return version[0]; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + protected int readByte(byte[] data, int offset) { return data[offset] & 0xFF; } + + protected int readWord(byte[] data, int offset) { + return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8); + } + + protected int readLong(byte[] data, int offset) { + return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) | ((data[offset + 2] & 0xFF) << 16) + | ((data[offset + 3] & 0xFF) << 24); + } + + protected int readRelativePointer(byte[] data, int offset) { + return readLong(data, offset) + offset + 4; + } + + protected void writeWord(byte[] data, int offset, int value) { + data[offset] = (byte) (value & 0xFF); + data[offset + 1] = (byte) ((value >> 8) & 0xFF); + } + + protected void writeLong(byte[] data, int offset, int value) { + data[offset] = (byte) (value & 0xFF); + data[offset + 1] = (byte) ((value >> 8) & 0xFF); + data[offset + 2] = (byte) ((value >> 16) & 0xFF); + data[offset + 3] = (byte) ((value >> 24) & 0xFF); + } + + protected void writeRelativePointer(byte[] data, int offset, int pointer) { + int relPointer = pointer - (offset + 4); + writeLong(data, offset, relPointer); + } + + protected byte[] readFile(String location) throws IOException { + return baseRom.getFile(location); + } + + protected void writeFile(String location, byte[] data) throws IOException { + writeFile(location, data, 0, data.length); + } + + protected void writeFile(String location, byte[] data, int offset, int length) throws IOException { + if (offset != 0 || length != data.length) { + byte[] newData = new byte[length]; + System.arraycopy(data, offset, newData, 0, length); + data = newData; + } + baseRom.writeFile(location, data); + } + + protected byte[] readARM9() throws IOException { + return baseRom.getARM9(); + } + + protected void writeARM9(byte[] data) throws IOException { + baseRom.writeARM9(data); + } + + protected byte[] readOverlay(int number) throws IOException { + return baseRom.getOverlay(number); + } + + protected void writeOverlay(int number, byte[] data) throws IOException { + baseRom.writeOverlay(number, data); + } + + protected void readByteIntoFlags(byte[] data, boolean[] flags, int offsetIntoFlags, int offsetIntoData) { + int thisByte = data[offsetIntoData] & 0xFF; + for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { + flags[offsetIntoFlags + i] = ((thisByte >> i) & 0x01) == 0x01; + } + } + + protected byte getByteFromFlags(boolean[] flags, int offsetIntoFlags) { + int thisByte = 0; + for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { + thisByte |= (flags[offsetIntoFlags + i] ? 1 : 0) << i; + } + return (byte) thisByte; + } + + protected int typeTMPaletteNumber(Type t) { + if (t == null) { + return 411; // CURSE + } + switch (t) { + case FIGHTING: + return 398; + case DRAGON: + return 399; + case WATER: + return 400; + case PSYCHIC: + return 401; + case NORMAL: + return 402; + case POISON: + return 403; + case ICE: + return 404; + case GRASS: + return 405; + case FIRE: + return 406; + case DARK: + return 407; + case STEEL: + return 408; + case ELECTRIC: + return 409; + case GROUND: + return 410; + case GHOST: + default: + return 411; // for CURSE + case ROCK: + return 412; + case FLYING: + return 413; + case BUG: + return 610; + } + } + + private int find(byte[] data, String hexString) { + if (hexString.length() % 2 != 0) { + return -3; // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + List found = RomFunctions.search(data, searchFor); + if (found.size() == 0) { + return -1; // not found + } else if (found.size() > 1) { + return -2; // not unique + } else { + return found.get(0); + } + } + + protected byte[] extendARM9(byte[] arm9, int extendBy, String prefix, int arm9Offset) { + /* + Simply extending the ARM9 at the end doesn't work. Towards the end of the ARM9, the following sections exist: + 1. A section that is copied to ITCM (Instruction Tightly Coupled Memory) + 2. A section that is copied to DTCM (Data Tightly Coupled Memory) + 3. Pointers specifying to where these sections should be copied as well as their sizes + + All of these sections are later overwritten(!) and the area is used more or less like a regular RAM area. + This means that if any new code is put after these sections, it will also be overwritten. + Changing which area is overwritten is not viable. There are very many pointers to this area that would need to + be re-indexed. + + Our solution is to extend the section that is to be copied to ITCM, so that any new code gets copied to + ITCM and can be executed from there. This means we have to shift all the data that is after this in order to + make space. Additionally, elsewhere in the ARM9, pointers are stored specifying from where the ITCM + section should be copied, as well as some other data. They are supposedly part of some sort of NDS library + functions and should work the same across games; look for "[SDK+NINTENDO:" in the ARM9 and these pointers should + be slightly before that. They are as follows (each pointer = 4 bytes): + 1. Pointer specifying from where the destination pointers/sizes should be read (see point 3 above) + 2. Pointer specifying the end address of the ARM9. + 3. Pointer specifying from where data copying should start (since ITCM is first, this corresponds to the start + of the section that should be copied to ITCM). + 4. Pointer specifying where data should start being overwritten. (should be identical to #3) + 5. Pointer specifying where data should stop being overwritten (should correspond to start of ovl table). + 6. ??? + + Out of these, we want to change #1 (it will be moved because we have to shift the end of the ARM9 to make space + for enlarging the "copy to ITCM" area) and #2 (since the ARM9 will be made larger). We also want to change the + specified size for the ITCM area since we're enlarging it. + */ + + if (arm9Extended) return arm9; // Don't try to extend the ARM9 more than once + + int tcmCopyingPointersOffset = find(arm9, prefix); + tcmCopyingPointersOffset += prefix.length() / 2; // because it was a prefix + + int oldDestPointersOffset = FileFunctions.readFullInt(arm9, tcmCopyingPointersOffset) - arm9Offset; + int itcmSrcOffset = + FileFunctions.readFullInt(arm9, tcmCopyingPointersOffset + 8) - arm9Offset; + int itcmSizeOffset = oldDestPointersOffset + 4; + int oldITCMSize = FileFunctions.readFullInt(arm9, itcmSizeOffset); + + int oldDTCMOffset = itcmSrcOffset + oldITCMSize; + + byte[] newARM9 = Arrays.copyOf(arm9, arm9.length + extendBy); + + // Change: + // 1. Pointer to destination pointers/sizes + // 2. ARM9 size + // 3. Size of the area copied to ITCM + FileFunctions.writeFullInt(newARM9, tcmCopyingPointersOffset, + oldDestPointersOffset + extendBy + arm9Offset); + FileFunctions.writeFullInt(newARM9, tcmCopyingPointersOffset + 4, + newARM9.length + arm9Offset); + FileFunctions.writeFullInt(newARM9, itcmSizeOffset, oldITCMSize + extendBy); + + // Finally, shift everything + System.arraycopy(newARM9, oldDTCMOffset, newARM9, oldDTCMOffset + extendBy, + arm9.length - oldDTCMOffset); + + arm9Extended = true; + + return newARM9; + } + +} diff --git a/src/com/pkrandom/romhandlers/AbstractGBCRomHandler.java b/src/com/pkrandom/romhandlers/AbstractGBCRomHandler.java new file mode 100644 index 0000000..897b6cd --- /dev/null +++ b/src/com/pkrandom/romhandlers/AbstractGBCRomHandler.java @@ -0,0 +1,224 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- AbstractGBCRomHandler.java - an extension of AbstractGBRomHandler --*/ +/*-- used for Gen 1 and Gen 2. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.Scanner; + +import com.pkrandom.FileFunctions; +import com.pkrandom.constants.GBConstants; + +public abstract class AbstractGBCRomHandler extends AbstractGBRomHandler { + + private String[] tb; + private Map d; + private int longestTableToken; + + public AbstractGBCRomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + protected void clearTextTables() { + tb = new String[256]; + if (d != null) { + d.clear(); + } else { + d = new HashMap(); + } + longestTableToken = 0; + } + + protected void readTextTable(String name) { + try { + Scanner sc = new Scanner(FileFunctions.openConfig(name + ".tbl"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine(); + if (!q.trim().isEmpty()) { + String[] r = q.split("=", 2); + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + int hexcode = Integer.parseInt(r[0], 16); + if (tb[hexcode] != null) { + String oldMatch = tb[hexcode]; + tb[hexcode] = null; + if (d.get(oldMatch) == hexcode) { + d.remove(oldMatch); + } + } + tb[hexcode] = r[1]; + longestTableToken = Math.max(longestTableToken, r[1].length()); + d.put(r[1], (byte) hexcode); + } + } + sc.close(); + } catch (FileNotFoundException e) { + } + + } + + protected String readString(int offset, int maxLength, boolean textEngineMode) { + StringBuilder string = new StringBuilder(); + for (int c = 0; c < maxLength; c++) { + int currChar = rom[offset + c] & 0xFF; + if (tb[currChar] != null) { + string.append(tb[currChar]); + if (textEngineMode && (tb[currChar].equals("\\r") || tb[currChar].equals("\\e"))) { + break; + } + } else { + if (currChar == GBConstants.stringTerminator) { + break; + } else { + string.append("\\x" + String.format("%02X", currChar)); + } + } + } + return string.toString(); + } + + protected int lengthOfStringAt(int offset, boolean textEngineMode) { + int len = 0; + while (rom[offset + len] != GBConstants.stringTerminator + && (!textEngineMode || (rom[offset + len] != GBConstants.stringPrintedTextEnd && rom[offset + len] != GBConstants.stringPrintedTextPromptEnd))) { + len++; + } + + if (textEngineMode + && (rom[offset + len] == GBConstants.stringPrintedTextEnd || rom[offset + len] == GBConstants.stringPrintedTextPromptEnd)) { + len++; + } + return len; + } + + protected byte[] translateString(String text) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while (text.length() != 0) { + int i = Math.max(0, longestTableToken - text.length()); + if (text.charAt(0) == '\\' && text.charAt(1) == 'x') { + baos.write(Integer.parseInt(text.substring(2, 4), 16)); + text = text.substring(4); + } else { + while (!(d.containsKey(text.substring(0, longestTableToken - i)) || (i == longestTableToken))) { + i++; + } + if (i == longestTableToken) { + text = text.substring(1); + } else { + baos.write(d.get(text.substring(0, longestTableToken - i)) & 0xFF); + text = text.substring(longestTableToken - i); + } + } + } + return baos.toByteArray(); + } + + protected String readFixedLengthString(int offset, int length) { + return readString(offset, length, false); + } + + // pads the length with terminators, so length should be at least str's len + // + 1 + protected void writeFixedLengthString(String str, int offset, int length) { + byte[] translated = translateString(str); + int len = Math.min(translated.length, length); + System.arraycopy(translated, 0, rom, offset, len); + while (len < length) { + rom[offset + len] = GBConstants.stringTerminator; + len++; + } + } + + protected void writeVariableLengthString(String str, int offset, boolean alreadyTerminated) { + byte[] translated = translateString(str); + System.arraycopy(translated, 0, rom, offset, translated.length); + if (!alreadyTerminated) { + rom[offset + translated.length] = GBConstants.stringTerminator; + } + } + + protected int makeGBPointer(int offset) { + if (offset < GBConstants.bankSize) { + return offset; + } else { + return (offset % GBConstants.bankSize) + GBConstants.bankSize; + } + } + + protected int bankOf(int offset) { + return (offset / GBConstants.bankSize); + } + + protected int calculateOffset(int bank, int pointer) { + if (pointer < GBConstants.bankSize) { + return pointer; + } else { + return (pointer % GBConstants.bankSize) + bank * GBConstants.bankSize; + } + } + + protected String readVariableLengthString(int offset, boolean textEngineMode) { + return readString(offset, Integer.MAX_VALUE, textEngineMode); + } + + protected static boolean romSig(byte[] rom, String sig) { + try { + int sigOffset = GBConstants.romSigOffset; + byte[] sigBytes = sig.getBytes("US-ASCII"); + for (int i = 0; i < sigBytes.length; i++) { + if (rom[sigOffset + i] != sigBytes[i]) { + return false; + } + } + return true; + } catch (UnsupportedEncodingException ex) { + return false; + } + + } + + protected static boolean romCode(byte[] rom, String code) { + try { + int sigOffset = GBConstants.romCodeOffset; + byte[] sigBytes = code.getBytes("US-ASCII"); + for (int i = 0; i < sigBytes.length; i++) { + if (rom[sigOffset + i] != sigBytes[i]) { + return false; + } + } + return true; + } catch (UnsupportedEncodingException ex) { + return false; + } + + } + +} diff --git a/src/com/pkrandom/romhandlers/AbstractGBRomHandler.java b/src/com/pkrandom/romhandlers/AbstractGBRomHandler.java new file mode 100755 index 0000000..a581f3d --- /dev/null +++ b/src/com/pkrandom/romhandlers/AbstractGBRomHandler.java @@ -0,0 +1,210 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- AbstractGBRomHandler.java - a base class for GB/GBA rom handlers --*/ +/*-- which standardises common GB(A) functions.--*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Random; + +import com.pkrandom.FileFunctions; +import com.pkrandom.exceptions.CannotWriteToLocationException; +import com.pkrandom.exceptions.RandomizerIOException; + +public abstract class AbstractGBRomHandler extends AbstractRomHandler { + + protected byte[] rom; + protected byte[] originalRom; + private String loadedFN; + + public AbstractGBRomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + @Override + public boolean loadRom(String filename) { + byte[] loaded = loadFile(filename); + if (!detectRom(loaded)) { + return false; + } + this.rom = loaded; + this.originalRom = new byte[rom.length]; + System.arraycopy(rom, 0, originalRom, 0, rom.length); + loadedFN = filename; + loadedRom(); + return true; + } + + @Override + public String loadedFilename() { + return loadedFN; + } + + @Override + public boolean saveRomFile(String filename, long seed) { + savingRom(); + try { + FileOutputStream fos = new FileOutputStream(filename); + fos.write(rom); + fos.close(); + return true; + } catch (IOException ex) { + if (ex.getMessage().contains("Access is denied")) { + throw new CannotWriteToLocationException("The randomizer cannot write to this location: " + filename); + } + return false; + } + } + + @Override + public boolean saveRomDirectory(String filename) { + // do nothing, because GB games don't really have a concept of a filesystem + return true; + } + + @Override + public boolean hasGameUpdateLoaded() { + return false; + } + + @Override + public boolean loadGameUpdate(String filename) { + // do nothing, as GB games don't have external game updates + return true; + } + + @Override + public void removeGameUpdate() { + // do nothing, as GB games don't have external game updates + } + + @Override + public String getGameUpdateVersion() { + // do nothing, as DS games don't have external game updates + return null; + } + + @Override + public void printRomDiagnostics(PrintStream logStream) { + Path p = Paths.get(loadedFN); + logStream.println("File name: " + p.getFileName().toString()); + long crc = FileFunctions.getCRC32(originalRom); + logStream.println("Original ROM CRC32: " + String.format("%08X", crc)); + } + + @Override + public boolean canChangeStaticPokemon() { + return true; + } + + @Override + public boolean hasPhysicalSpecialSplit() { + // Default value for Gen1-Gen3. + // Handlers can override again in case of ROM hacks etc. + return false; + } + + public abstract boolean detectRom(byte[] rom); + + public abstract void loadedRom(); + + public abstract void savingRom(); + + protected static byte[] loadFile(String filename) { + try { + return FileFunctions.readFileFullyIntoBuffer(filename); + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + } + + protected static byte[] loadFilePartial(String filename, int maxBytes) { + try { + File fh = new File(filename); + if (!fh.exists() || !fh.isFile() || !fh.canRead()) { + return new byte[0]; + } + long fileSize = fh.length(); + if (fileSize > Integer.MAX_VALUE) { + return new byte[0]; + } + FileInputStream fis = new FileInputStream(filename); + byte[] file = FileFunctions.readFullyIntoBuffer(fis, Math.min((int) fileSize, maxBytes)); + fis.close(); + return file; + } catch (IOException ex) { + return new byte[0]; + } + } + + protected void readByteIntoFlags(boolean[] flags, int offsetIntoFlags, int offsetIntoROM) { + int thisByte = rom[offsetIntoROM] & 0xFF; + for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { + flags[offsetIntoFlags + i] = ((thisByte >> i) & 0x01) == 0x01; + } + } + + protected byte getByteFromFlags(boolean[] flags, int offsetIntoFlags) { + int thisByte = 0; + for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { + thisByte |= (flags[offsetIntoFlags + i] ? 1 : 0) << i; + } + return (byte) thisByte; + } + + protected int readWord(int offset) { + return readWord(rom, offset); + } + + protected int readWord(byte[] data, int offset) { + return (data[offset] & 0xFF) + ((data[offset + 1] & 0xFF) << 8); + } + + protected void writeWord(int offset, int value) { + writeWord(rom, offset, value); + } + + protected void writeWord(byte[] data, int offset, int value) { + data[offset] = (byte) (value % 0x100); + data[offset + 1] = (byte) ((value / 0x100) % 0x100); + } + + protected boolean matches(byte[] data, int offset, byte[] needle) { + for (int i = 0; i < needle.length; i++) { + if (offset + i >= data.length) { + return false; + } + if (data[offset + i] != needle[i]) { + return false; + } + } + return true; + } + +} diff --git a/src/com/pkrandom/romhandlers/AbstractRomHandler.java b/src/com/pkrandom/romhandlers/AbstractRomHandler.java new file mode 100755 index 0000000..d0c185c --- /dev/null +++ b/src/com/pkrandom/romhandlers/AbstractRomHandler.java @@ -0,0 +1,7558 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- AbstractRomHandler.java - a base class for all rom handlers which --*/ +/*-- implements the majority of the actual --*/ +/*-- randomizer logic by building on the base --*/ +/*-- getters & setters provided by each concrete --*/ +/*-- handler. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.io.PrintStream; +import java.util.*; +import java.util.stream.Collectors; + +import com.pkrandom.*; +import com.pkrandom.constants.*; +import com.pkrandom.exceptions.RandomizationException; +import com.pkrandom.pokemon.*; + +public abstract class AbstractRomHandler implements RomHandler { + + private boolean restrictionsSet; + protected List mainPokemonList; + protected List mainPokemonListInclFormes; + private List altFormesList; + private List megaEvolutionsList; + private List noLegendaryList, onlyLegendaryList, ultraBeastList; + private List noLegendaryListInclFormes, onlyLegendaryListInclFormes; + private List noLegendaryAltsList, onlyLegendaryAltsList; + private List pickedStarters; + protected final Random random; + private final Random cosmeticRandom; + protected PrintStream logStream; + private List alreadyPicked = new ArrayList<>(); + private Map placementHistory = new HashMap<>(); + private Map itemPlacementHistory = new HashMap<>(); + private int fullyEvolvedRandomSeed; + boolean isORAS = false; + boolean isSM = false; + int perfectAccuracy = 100; + + /* Constructor */ + + public AbstractRomHandler(Random random, PrintStream logStream) { + this.random = random; + this.cosmeticRandom = RandomSource.cosmeticInstance(); + this.fullyEvolvedRandomSeed = -1; + this.logStream = logStream; + } + + /* + * Public Methods, implemented here for all gens. Unlikely to be overridden. + */ + + public void setLog(PrintStream logStream) { + this.logStream = logStream; + } + + public void setPokemonPool(Settings settings) { + GenRestrictions restrictions = null; + if (settings != null) { + restrictions = settings.getCurrentRestrictions(); + + // restrictions should already be null if "Limit Pokemon" is disabled, but this is a safeguard + if (!settings.isLimitPokemon()) { + restrictions = null; + } + } + + restrictionsSet = true; + mainPokemonList = this.allPokemonWithoutNull(); + mainPokemonListInclFormes = this.allPokemonInclFormesWithoutNull(); + altFormesList = this.getAltFormes(); + megaEvolutionsList = this.getMegaEvolutions(); + if (restrictions != null) { + mainPokemonList = new ArrayList<>(); + mainPokemonListInclFormes = new ArrayList<>(); + megaEvolutionsList = new ArrayList<>(); + List allPokemon = this.getPokemon(); + + if (restrictions.allow_gen1) { + addPokesFromRange(mainPokemonList, allPokemon, Species.bulbasaur, Species.mew); + } + + if (restrictions.allow_gen2 && allPokemon.size() > Gen2Constants.pokemonCount) { + addPokesFromRange(mainPokemonList, allPokemon, Species.chikorita, Species.celebi); + } + + if (restrictions.allow_gen3 && allPokemon.size() > Gen3Constants.pokemonCount) { + addPokesFromRange(mainPokemonList, allPokemon, Species.treecko, Species.deoxys); + } + + if (restrictions.allow_gen4 && allPokemon.size() > Gen4Constants.pokemonCount) { + addPokesFromRange(mainPokemonList, allPokemon, Species.turtwig, Species.arceus); + } + + if (restrictions.allow_gen5 && allPokemon.size() > Gen5Constants.pokemonCount) { + addPokesFromRange(mainPokemonList, allPokemon, Species.victini, Species.genesect); + } + + if (restrictions.allow_gen6 && allPokemon.size() > Gen6Constants.pokemonCount) { + addPokesFromRange(mainPokemonList, allPokemon, Species.chespin, Species.volcanion); + } + + int maxGen7SpeciesID = isSM ? Species.marshadow : Species.zeraora; + if (restrictions.allow_gen7 && allPokemon.size() > maxGen7SpeciesID) { + addPokesFromRange(mainPokemonList, allPokemon, Species.rowlet, maxGen7SpeciesID); + } + + // If the user specified it, add all the evolutionary relatives for everything in the mainPokemonList + if (restrictions.allow_evolutionary_relatives) { + addEvolutionaryRelatives(mainPokemonList); + } + + // Now that mainPokemonList has all the selected Pokemon, update mainPokemonListInclFormes too + addAllPokesInclFormes(mainPokemonList, mainPokemonListInclFormes); + + // Populate megaEvolutionsList with all of the mega evolutions that exist in the pool + List allMegaEvolutions = this.getMegaEvolutions(); + for (MegaEvolution megaEvo : allMegaEvolutions) { + if (mainPokemonListInclFormes.contains(megaEvo.to)) { + megaEvolutionsList.add(megaEvo); + } + } + } + + noLegendaryList = new ArrayList<>(); + noLegendaryListInclFormes = new ArrayList<>(); + onlyLegendaryList = new ArrayList<>(); + onlyLegendaryListInclFormes = new ArrayList<>(); + noLegendaryAltsList = new ArrayList<>(); + onlyLegendaryAltsList = new ArrayList<>(); + ultraBeastList = new ArrayList<>(); + + for (Pokemon p : mainPokemonList) { + if (p.isLegendary()) { + onlyLegendaryList.add(p); + } else if (p.isUltraBeast()) { + ultraBeastList.add(p); + } else { + noLegendaryList.add(p); + } + } + for (Pokemon p : mainPokemonListInclFormes) { + if (p.isLegendary()) { + onlyLegendaryListInclFormes.add(p); + } else if (!ultraBeastList.contains(p)) { + noLegendaryListInclFormes.add(p); + } + } + for (Pokemon f : altFormesList) { + if (f.isLegendary()) { + onlyLegendaryAltsList.add(f); + } else { + noLegendaryAltsList.add(f); + } + } + } + + private void addPokesFromRange(List pokemonPool, List allPokemon, int range_min, int range_max) { + for (int i = range_min; i <= range_max; i++) { + if (!pokemonPool.contains(allPokemon.get(i))) { + pokemonPool.add(allPokemon.get(i)); + } + } + } + + private void addEvolutionaryRelatives(List pokemonPool) { + Set newPokemon = new TreeSet<>(); + for (Pokemon pk : pokemonPool) { + List evolutionaryRelatives = getEvolutionaryRelatives(pk); + for (Pokemon relative : evolutionaryRelatives) { + if (!pokemonPool.contains(relative) && !newPokemon.contains(relative)) { + newPokemon.add(relative); + } + } + } + + pokemonPool.addAll(newPokemon); + } + + private void addAllPokesInclFormes(List pokemonPool, List pokemonPoolInclFormes) { + List altFormes = this.getAltFormes(); + for (int i = 0; i < pokemonPool.size(); i++) { + Pokemon currentPokemon = pokemonPool.get(i); + if (!pokemonPoolInclFormes.contains(currentPokemon)) { + pokemonPoolInclFormes.add(currentPokemon); + } + for (int j = 0; j < altFormes.size(); j++) { + Pokemon potentialAltForme = altFormes.get(j); + if (potentialAltForme.baseForme != null && potentialAltForme.baseForme.number == currentPokemon.number) { + pokemonPoolInclFormes.add(potentialAltForme); + } + } + } + } + + @Override + public void shufflePokemonStats(Settings settings) { + boolean evolutionSanity = settings.isBaseStatsFollowEvolutions(); + boolean megaEvolutionSanity = settings.isBaseStatsFollowMegaEvolutions(); + + if (evolutionSanity) { + copyUpEvolutionsHelper(pk -> pk.shuffleStats(AbstractRomHandler.this.random), + (evFrom, evTo, toMonIsFinalEvo) -> evTo.copyShuffledStatsUpEvolution(evFrom) + ); + } else { + List allPokes = this.getPokemonInclFormes(); + for (Pokemon pk : allPokes) { + if (pk != null) { + pk.shuffleStats(this.random); + } + } + } + + List allPokes = this.getPokemonInclFormes(); + for (Pokemon pk : allPokes) { + if (pk != null && pk.actuallyCosmetic) { + pk.copyBaseFormeBaseStats(pk.baseForme); + } + } + + if (megaEvolutionSanity) { + List allMegaEvos = getMegaEvolutions(); + for (MegaEvolution megaEvo: allMegaEvos) { + if (megaEvo.from.megaEvolutionsFrom.size() > 1) continue; + megaEvo.to.copyShuffledStatsUpEvolution(megaEvo.from); + } + } + } + + @Override + public void randomizePokemonStats(Settings settings) { + boolean evolutionSanity = settings.isBaseStatsFollowEvolutions(); + boolean megaEvolutionSanity = settings.isBaseStatsFollowMegaEvolutions(); + boolean assignEvoStatsRandomly = settings.isAssignEvoStatsRandomly(); + + if (evolutionSanity) { + if (assignEvoStatsRandomly) { + copyUpEvolutionsHelper(pk -> pk.randomizeStatsWithinBST(AbstractRomHandler.this.random), + (evFrom, evTo, toMonIsFinalEvo) -> evTo.assignNewStatsForEvolution(evFrom, this.random), + (evFrom, evTo, toMonIsFinalEvo) -> evTo.assignNewStatsForEvolution(evFrom, this.random), + true + ); + } else { + copyUpEvolutionsHelper(pk -> pk.randomizeStatsWithinBST(AbstractRomHandler.this.random), + (evFrom, evTo, toMonIsFinalEvo) -> evTo.copyRandomizedStatsUpEvolution(evFrom), + (evFrom, evTo, toMonIsFinalEvo) -> evTo.assignNewStatsForEvolution(evFrom, this.random), + true + ); + } + } else { + List allPokes = this.getPokemonInclFormes(); + for (Pokemon pk : allPokes) { + if (pk != null) { + pk.randomizeStatsWithinBST(this.random); + } + } + } + + List allPokes = this.getPokemonInclFormes(); + for (Pokemon pk : allPokes) { + if (pk != null && pk.actuallyCosmetic) { + pk.copyBaseFormeBaseStats(pk.baseForme); + } + } + + if (megaEvolutionSanity) { + List allMegaEvos = getMegaEvolutions(); + for (MegaEvolution megaEvo: allMegaEvos) { + if (megaEvo.from.megaEvolutionsFrom.size() > 1 || assignEvoStatsRandomly) { + megaEvo.to.assignNewStatsForEvolution(megaEvo.from, this.random); + } else { + megaEvo.to.copyRandomizedStatsUpEvolution(megaEvo.from); + } + } + } + } + + @Override + public void updatePokemonStats(Settings settings) { + int generation = settings.getUpdateBaseStatsToGeneration(); + + List pokes = getPokemonInclFormes(); + + for (int gen = 6; gen <= generation; gen++) { + Map statChanges = getUpdatedPokemonStats(gen); + + for (int i = 1; i < pokes.size(); i++) { + StatChange changedStats = statChanges.get(i); + if (changedStats != null) { + int statNum = 0; + if ((changedStats.stat & Stat.HP.val) != 0) { + pokes.get(i).hp = changedStats.values[statNum]; + statNum++; + } + if ((changedStats.stat & Stat.ATK.val) != 0) { + pokes.get(i).attack = changedStats.values[statNum]; + statNum++; + } + if ((changedStats.stat & Stat.DEF.val) != 0) { + pokes.get(i).defense = changedStats.values[statNum]; + statNum++; + } + if ((changedStats.stat & Stat.SPATK.val) != 0) { + if (generationOfPokemon() != 1) { + pokes.get(i).spatk = changedStats.values[statNum]; + } + statNum++; + } + if ((changedStats.stat & Stat.SPDEF.val) != 0) { + if (generationOfPokemon() != 1) { + pokes.get(i).spdef = changedStats.values[statNum]; + } + statNum++; + } + if ((changedStats.stat & Stat.SPEED.val) != 0) { + pokes.get(i).speed = changedStats.values[statNum]; + statNum++; + } + if ((changedStats.stat & Stat.SPECIAL.val) != 0) { + pokes.get(i).special = changedStats.values[statNum]; + } + } + } + } + } + + public Pokemon randomPokemon() { + checkPokemonRestrictions(); + return mainPokemonList.get(this.random.nextInt(mainPokemonList.size())); + } + + @Override + public Pokemon randomPokemonInclFormes() { + checkPokemonRestrictions(); + return mainPokemonListInclFormes.get(this.random.nextInt(mainPokemonListInclFormes.size())); + } + + @Override + public Pokemon randomNonLegendaryPokemon() { + checkPokemonRestrictions(); + return noLegendaryList.get(this.random.nextInt(noLegendaryList.size())); + } + + private Pokemon randomNonLegendaryPokemonInclFormes() { + checkPokemonRestrictions(); + return noLegendaryListInclFormes.get(this.random.nextInt(noLegendaryListInclFormes.size())); + } + + @Override + public Pokemon randomLegendaryPokemon() { + checkPokemonRestrictions(); + return onlyLegendaryList.get(this.random.nextInt(onlyLegendaryList.size())); + } + + private List twoEvoPokes; + + @Override + public Pokemon random2EvosPokemon(boolean allowAltFormes) { + if (twoEvoPokes == null) { + // Prepare the list + twoEvoPokes = new ArrayList<>(); + List allPokes = + allowAltFormes ? + this.getPokemonInclFormes() + .stream() + .filter(pk -> pk == null || !pk.actuallyCosmetic) + .collect(Collectors.toList()) : + this.getPokemon(); + for (Pokemon pk : allPokes) { + if (pk != null && pk.evolutionsTo.size() == 0 && pk.evolutionsFrom.size() > 0) { + // Potential candidate + for (Evolution ev : pk.evolutionsFrom) { + // If any of the targets here evolve, the original + // Pokemon has 2+ stages. + if (ev.to.evolutionsFrom.size() > 0) { + twoEvoPokes.add(pk); + break; + } + } + } + } + } + return twoEvoPokes.get(this.random.nextInt(twoEvoPokes.size())); + } + + @Override + public Type randomType() { + Type t = Type.randomType(this.random); + while (!typeInGame(t)) { + t = Type.randomType(this.random); + } + return t; + } + + @Override + public void randomizePokemonTypes(Settings settings) { + boolean evolutionSanity = settings.getTypesMod() == Settings.TypesMod.RANDOM_FOLLOW_EVOLUTIONS; + boolean megaEvolutionSanity = settings.isTypesFollowMegaEvolutions(); + boolean dualTypeOnly = settings.isDualTypeOnly(); + + List allPokes = this.getPokemonInclFormes(); + if (evolutionSanity) { + // Type randomization with evolution sanity + copyUpEvolutionsHelper(pk -> { + // Step 1: Basic or Excluded From Copying Pokemon + // A Basic/EFC pokemon has a 35% chance of a second type if + // it has an evolution that copies type/stats, a 50% chance + // otherwise + pk.primaryType = randomType(); + pk.secondaryType = null; + if (pk.evolutionsFrom.size() == 1 && pk.evolutionsFrom.get(0).carryStats) { + if (AbstractRomHandler.this.random.nextDouble() < 0.35 || dualTypeOnly) { + pk.secondaryType = randomType(); + while (pk.secondaryType == pk.primaryType) { + pk.secondaryType = randomType(); + } + } + } else { + if (AbstractRomHandler.this.random.nextDouble() < 0.5 || dualTypeOnly) { + pk.secondaryType = randomType(); + while (pk.secondaryType == pk.primaryType) { + pk.secondaryType = randomType(); + } + } + } + }, (evFrom, evTo, toMonIsFinalEvo) -> { + evTo.primaryType = evFrom.primaryType; + evTo.secondaryType = evFrom.secondaryType; + + if (evTo.secondaryType == null) { + double chance = toMonIsFinalEvo ? 0.25 : 0.15; + if (AbstractRomHandler.this.random.nextDouble() < chance || dualTypeOnly) { + evTo.secondaryType = randomType(); + while (evTo.secondaryType == evTo.primaryType) { + evTo.secondaryType = randomType(); + } + } + } + }); + } else { + // Entirely random types + for (Pokemon pkmn : allPokes) { + if (pkmn != null) { + pkmn.primaryType = randomType(); + pkmn.secondaryType = null; + if (this.random.nextDouble() < 0.5||settings.isDualTypeOnly()) { + pkmn.secondaryType = randomType(); + while (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = randomType(); + } + } + } + } + } + + for (Pokemon pk : allPokes) { + if (pk != null && pk.actuallyCosmetic) { + pk.primaryType = pk.baseForme.primaryType; + pk.secondaryType = pk.baseForme.secondaryType; + } + } + + if (megaEvolutionSanity) { + List allMegaEvos = getMegaEvolutions(); + for (MegaEvolution megaEvo: allMegaEvos) { + if (megaEvo.from.megaEvolutionsFrom.size() > 1) continue; + megaEvo.to.primaryType = megaEvo.from.primaryType; + megaEvo.to.secondaryType = megaEvo.from.secondaryType; + + if (megaEvo.to.secondaryType == null) { + if (this.random.nextDouble() < 0.25) { + megaEvo.to.secondaryType = randomType(); + while (megaEvo.to.secondaryType == megaEvo.to.primaryType) { + megaEvo.to.secondaryType = randomType(); + } + } + } + } + } + } + + @Override + public void randomizeAbilities(Settings settings) { + boolean evolutionSanity = settings.isAbilitiesFollowEvolutions(); + boolean allowWonderGuard = settings.isAllowWonderGuard(); + boolean banTrappingAbilities = settings.isBanTrappingAbilities(); + boolean banNegativeAbilities = settings.isBanNegativeAbilities(); + boolean banBadAbilities = settings.isBanBadAbilities(); + boolean megaEvolutionSanity = settings.isAbilitiesFollowMegaEvolutions(); + boolean weighDuplicatesTogether = settings.isWeighDuplicateAbilitiesTogether(); + boolean ensureTwoAbilities = settings.isEnsureTwoAbilities(); + boolean doubleBattleMode = settings.isDoubleBattleMode(); + + // Abilities don't exist in some games... + if (this.abilitiesPerPokemon() == 0) { + return; + } + + final boolean hasDWAbilities = (this.abilitiesPerPokemon() == 3); + + final List bannedAbilities = this.getUselessAbilities(); + + if (!allowWonderGuard) { + bannedAbilities.add(Abilities.wonderGuard); + } + + if (banTrappingAbilities) { + bannedAbilities.addAll(GlobalConstants.battleTrappingAbilities); + } + + if (banNegativeAbilities) { + bannedAbilities.addAll(GlobalConstants.negativeAbilities); + } + + if (banBadAbilities) { + bannedAbilities.addAll(GlobalConstants.badAbilities); + if (!doubleBattleMode) { + bannedAbilities.addAll(GlobalConstants.doubleBattleAbilities); + } + } + + if (weighDuplicatesTogether) { + bannedAbilities.addAll(GlobalConstants.duplicateAbilities); + if (generationOfPokemon() == 3) { + bannedAbilities.add(Gen3Constants.airLockIndex); // Special case for Air Lock in Gen 3 + } + } + + final int maxAbility = this.highestAbilityIndex(); + + if (evolutionSanity) { + // copy abilities straight up evolution lines + // still keep WG as an exception, though + + copyUpEvolutionsHelper(pk -> { + if (pk.ability1 != Abilities.wonderGuard + && pk.ability2 != Abilities.wonderGuard + && pk.ability3 != Abilities.wonderGuard) { + // Pick first ability + pk.ability1 = pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether); + + // Second ability? + if (ensureTwoAbilities || AbstractRomHandler.this.random.nextDouble() < 0.5) { + // Yes, second ability + pk.ability2 = pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether, + pk.ability1); + } else { + // Nope + pk.ability2 = 0; + } + + // Third ability? + if (hasDWAbilities) { + pk.ability3 = pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether, + pk.ability1, pk.ability2); + } + } + }, (evFrom, evTo, toMonIsFinalEvo) -> { + if (evTo.ability1 != Abilities.wonderGuard + && evTo.ability2 != Abilities.wonderGuard + && evTo.ability3 != Abilities.wonderGuard) { + evTo.ability1 = evFrom.ability1; + evTo.ability2 = evFrom.ability2; + evTo.ability3 = evFrom.ability3; + } + }); + } else { + List allPokes = this.getPokemonInclFormes(); + for (Pokemon pk : allPokes) { + if (pk == null) { + continue; + } + + // Don't remove WG if already in place. + if (pk.ability1 != Abilities.wonderGuard + && pk.ability2 != Abilities.wonderGuard + && pk.ability3 != Abilities.wonderGuard) { + // Pick first ability + pk.ability1 = this.pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether); + + // Second ability? + if (ensureTwoAbilities || this.random.nextDouble() < 0.5) { + // Yes, second ability + pk.ability2 = this.pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether, + pk.ability1); + } else { + // Nope + pk.ability2 = 0; + } + + // Third ability? + if (hasDWAbilities) { + pk.ability3 = pickRandomAbility(maxAbility, bannedAbilities, weighDuplicatesTogether, + pk.ability1, pk.ability2); + } + } + } + } + + List allPokes = this.getPokemonInclFormes(); + for (Pokemon pk : allPokes) { + if (pk != null && pk.actuallyCosmetic) { + pk.copyBaseFormeAbilities(pk.baseForme); + } + } + + if (megaEvolutionSanity) { + List allMegaEvos = this.getMegaEvolutions(); + for (MegaEvolution megaEvo: allMegaEvos) { + if (megaEvo.from.megaEvolutionsFrom.size() > 1) continue; + megaEvo.to.ability1 = megaEvo.from.ability1; + megaEvo.to.ability2 = megaEvo.from.ability2; + megaEvo.to.ability3 = megaEvo.from.ability3; + } + } + } + + private int pickRandomAbilityVariation(int selectedAbility, int... alreadySetAbilities) { + int newAbility = selectedAbility; + + while (true) { + Map> abilityVariations = getAbilityVariations(); + for (int baseAbility: abilityVariations.keySet()) { + if (selectedAbility == baseAbility) { + List variationsForThisAbility = abilityVariations.get(selectedAbility); + newAbility = variationsForThisAbility.get(this.random.nextInt(variationsForThisAbility.size())); + break; + } + } + + boolean repeat = false; + for (int alreadySetAbility : alreadySetAbilities) { + if (alreadySetAbility == newAbility) { + repeat = true; + break; + } + } + + if (!repeat) { + break; + } + + + } + + return newAbility; + } + + private int pickRandomAbility(int maxAbility, List bannedAbilities, boolean useVariations, + int... alreadySetAbilities) { + int newAbility; + + while (true) { + newAbility = this.random.nextInt(maxAbility) + 1; + + if (bannedAbilities.contains(newAbility)) { + continue; + } + + boolean repeat = false; + for (int alreadySetAbility : alreadySetAbilities) { + if (alreadySetAbility == newAbility) { + repeat = true; + break; + } + } + + if (!repeat) { + if (useVariations) { + newAbility = pickRandomAbilityVariation(newAbility, alreadySetAbilities); + } + break; + } + } + + return newAbility; + } + + @Override + public void randomEncounters(Settings settings) { + boolean useTimeOfDay = settings.isUseTimeBasedEncounters(); + boolean catchEmAll = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.CATCH_EM_ALL; + boolean typeThemed = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.TYPE_THEME_AREAS; + boolean usePowerLevels = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.SIMILAR_STRENGTH; + boolean noLegendaries = settings.isBlockWildLegendaries(); + boolean balanceShakingGrass = settings.isBalanceShakingGrass(); + int levelModifier = settings.isWildLevelsModified() ? settings.getWildLevelModifier() : 0; + boolean allowAltFormes = settings.isAllowWildAltFormes(); + boolean banIrregularAltFormes = settings.isBanIrregularAltFormes(); + boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE; + + List currentEncounters = this.getEncounters(useTimeOfDay); + + if (isORAS) { + List collapsedEncounters = collapseAreasORAS(currentEncounters); + area1to1EncountersImpl(collapsedEncounters, settings); + enhanceRandomEncountersORAS(collapsedEncounters, settings); + setEncounters(useTimeOfDay, currentEncounters); + return; + } + + checkPokemonRestrictions(); + + // New: randomize the order encounter sets are randomized in. + // Leads to less predictable results for various modifiers. + // Need to keep the original ordering around for saving though. + List scrambledEncounters = new ArrayList<>(currentEncounters); + Collections.shuffle(scrambledEncounters, this.random); + + List banned = this.bannedForWildEncounters(); + banned.addAll(this.getBannedFormesForPlayerPokemon()); + if (!abilitiesAreRandomized) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + banned.addAll(getIrregularFormes()); + } + // Assume EITHER catch em all OR type themed OR match strength for now + if (catchEmAll) { + List allPokes; + if (allowAltFormes) { + allPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) : new ArrayList<>( + mainPokemonListInclFormes); + allPokes.removeIf(o -> ((Pokemon) o).actuallyCosmetic); + } else { + allPokes = noLegendaries ? new ArrayList<>(noLegendaryList) : new ArrayList<>( + mainPokemonList); + } + allPokes.removeAll(banned); + + for (EncounterSet area : scrambledEncounters) { + List pickablePokemon = allPokes; + if (area.bannedPokemon.size() > 0) { + pickablePokemon = new ArrayList<>(allPokes); + pickablePokemon.removeAll(area.bannedPokemon); + } + for (Encounter enc : area.encounters) { + // In Catch 'Em All mode, don't randomize encounters for Pokemon that are banned for + // wild encounters. Otherwise, it may be impossible to obtain this Pokemon unless it + // randomly appears as a static or unless it becomes a random evolution. + if (banned.contains(enc.pokemon)) { + continue; + } + + // Pick a random pokemon + if (pickablePokemon.size() == 0) { + // Only banned pokes are left, ignore them and pick + // something else for now. + List tempPickable; + if (allowAltFormes) { + tempPickable = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) : new ArrayList<>( + mainPokemonListInclFormes); + tempPickable.removeIf(o -> ((Pokemon) o).actuallyCosmetic); + } else { + tempPickable = noLegendaries ? new ArrayList<>(noLegendaryList) : new ArrayList<>( + mainPokemonList); + } + tempPickable.removeAll(banned); + tempPickable.removeAll(area.bannedPokemon); + if (tempPickable.size() == 0) { + throw new RandomizationException("ERROR: Couldn't replace a wild Pokemon!"); + } + int picked = this.random.nextInt(tempPickable.size()); + enc.pokemon = tempPickable.get(picked); + setFormeForEncounter(enc, enc.pokemon); + } else { + // Picked this Pokemon, remove it + int picked = this.random.nextInt(pickablePokemon.size()); + enc.pokemon = pickablePokemon.get(picked); + pickablePokemon.remove(picked); + if (allPokes != pickablePokemon) { + allPokes.remove(enc.pokemon); + } + setFormeForEncounter(enc, enc.pokemon); + if (allPokes.size() == 0) { + // Start again + if (allowAltFormes) { + allPokes.addAll(noLegendaries ? noLegendaryListInclFormes : mainPokemonListInclFormes); + allPokes.removeIf(o -> ((Pokemon) o).actuallyCosmetic); + } else { + allPokes.addAll(noLegendaries ? noLegendaryList : mainPokemonList); + } + allPokes.removeAll(banned); + if (pickablePokemon != allPokes) { + pickablePokemon.addAll(allPokes); + pickablePokemon.removeAll(area.bannedPokemon); + } + } + } + } + } + } else if (typeThemed) { + Map> cachedPokeLists = new TreeMap<>(); + for (EncounterSet area : scrambledEncounters) { + List possiblePokemon = null; + int iterLoops = 0; + while (possiblePokemon == null && iterLoops < 10000) { + Type areaTheme = randomType(); + if (!cachedPokeLists.containsKey(areaTheme)) { + List pType = allowAltFormes ? pokemonOfTypeInclFormes(areaTheme, noLegendaries) : + pokemonOfType(areaTheme, noLegendaries); + pType.removeAll(banned); + cachedPokeLists.put(areaTheme, pType); + } + possiblePokemon = cachedPokeLists.get(areaTheme); + if (area.bannedPokemon.size() > 0) { + possiblePokemon = new ArrayList<>(possiblePokemon); + possiblePokemon.removeAll(area.bannedPokemon); + } + if (possiblePokemon.size() == 0) { + // Can't use this type for this area + possiblePokemon = null; + } + iterLoops++; + } + if (possiblePokemon == null) { + throw new RandomizationException("Could not randomize an area in a reasonable amount of attempts."); + } + for (Encounter enc : area.encounters) { + // Pick a random themed pokemon + enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size())); + while (enc.pokemon.actuallyCosmetic) { + enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size())); + } + setFormeForEncounter(enc, enc.pokemon); + } + } + } else if (usePowerLevels) { + List allowedPokes; + if (allowAltFormes) { + allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) + : new ArrayList<>(mainPokemonListInclFormes); + } else { + allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryList) + : new ArrayList<>(mainPokemonList); + } + allowedPokes.removeAll(banned); + for (EncounterSet area : scrambledEncounters) { + List localAllowed = allowedPokes; + if (area.bannedPokemon.size() > 0) { + localAllowed = new ArrayList<>(allowedPokes); + localAllowed.removeAll(area.bannedPokemon); + } + for (Encounter enc : area.encounters) { + if (balanceShakingGrass) { + if (area.displayName.contains("Shaking")) { + enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, (enc.level + enc.maxLevel) / 2); + while (enc.pokemon.actuallyCosmetic) { + enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, (enc.level + enc.maxLevel) / 2); + } + setFormeForEncounter(enc, enc.pokemon); + } else { + enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100); + while (enc.pokemon.actuallyCosmetic) { + enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100); + } + setFormeForEncounter(enc, enc.pokemon); + } + } else { + enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100); + while (enc.pokemon.actuallyCosmetic) { + enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100); + } + setFormeForEncounter(enc, enc.pokemon); + } + } + } + } else { + // Entirely random + for (EncounterSet area : scrambledEncounters) { + for (Encounter enc : area.encounters) { + enc.pokemon = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned); + setFormeForEncounter(enc, enc.pokemon); + } + } + } + if (levelModifier != 0) { + for (EncounterSet area : currentEncounters) { + for (Encounter enc : area.encounters) { + enc.level = Math.min(100, (int) Math.round(enc.level * (1 + levelModifier / 100.0))); + enc.maxLevel = Math.min(100, (int) Math.round(enc.maxLevel * (1 + levelModifier / 100.0))); + } + } + } + + setEncounters(useTimeOfDay, currentEncounters); + } + + @Override + public void area1to1Encounters(Settings settings) { + boolean useTimeOfDay = settings.isUseTimeBasedEncounters(); + + List currentEncounters = this.getEncounters(useTimeOfDay); + if (isORAS) { + List collapsedEncounters = collapseAreasORAS(currentEncounters); + area1to1EncountersImpl(collapsedEncounters, settings); + setEncounters(useTimeOfDay, currentEncounters); + return; + } else { + area1to1EncountersImpl(currentEncounters, settings); + setEncounters(useTimeOfDay, currentEncounters); + } + } + + private void area1to1EncountersImpl(List currentEncounters, Settings settings) { + boolean catchEmAll = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.CATCH_EM_ALL; + boolean typeThemed = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.TYPE_THEME_AREAS; + boolean usePowerLevels = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.SIMILAR_STRENGTH; + boolean noLegendaries = settings.isBlockWildLegendaries(); + int levelModifier = settings.isWildLevelsModified() ? settings.getWildLevelModifier() : 0; + boolean allowAltFormes = settings.isAllowWildAltFormes(); + boolean banIrregularAltFormes = settings.isBanIrregularAltFormes(); + boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE; + + checkPokemonRestrictions(); + List banned = this.bannedForWildEncounters(); + banned.addAll(this.getBannedFormesForPlayerPokemon()); + if (!abilitiesAreRandomized) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + banned.addAll(getIrregularFormes()); + } + + // New: randomize the order encounter sets are randomized in. + // Leads to less predictable results for various modifiers. + // Need to keep the original ordering around for saving though. + List scrambledEncounters = new ArrayList<>(currentEncounters); + Collections.shuffle(scrambledEncounters, this.random); + + // Assume EITHER catch em all OR type themed for now + if (catchEmAll) { + List allPokes; + if (allowAltFormes) { + allPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) : new ArrayList<>( + mainPokemonListInclFormes); + allPokes.removeIf(o -> ((Pokemon) o).actuallyCosmetic); + } else { + allPokes = noLegendaries ? new ArrayList<>(noLegendaryList) : new ArrayList<>( + mainPokemonList); + } + allPokes.removeAll(banned); + for (EncounterSet area : scrambledEncounters) { + Set inArea = pokemonInArea(area); + // Build area map using catch em all + Map areaMap = new TreeMap<>(); + List pickablePokemon = allPokes; + if (area.bannedPokemon.size() > 0) { + pickablePokemon = new ArrayList<>(allPokes); + pickablePokemon.removeAll(area.bannedPokemon); + } + for (Pokemon areaPk : inArea) { + if (pickablePokemon.size() == 0) { + // No more pickable pokes left, take a random one + List tempPickable; + if (allowAltFormes) { + tempPickable = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) : new ArrayList<>( + mainPokemonListInclFormes); + tempPickable.removeIf(o -> ((Pokemon) o).actuallyCosmetic); + } else { + tempPickable = noLegendaries ? new ArrayList<>(noLegendaryList) : new ArrayList<>( + mainPokemonList); + } + tempPickable.removeAll(banned); + tempPickable.removeAll(area.bannedPokemon); + if (tempPickable.size() == 0) { + throw new RandomizationException("ERROR: Couldn't replace a wild Pokemon!"); + } + int picked = this.random.nextInt(tempPickable.size()); + Pokemon pickedMN = tempPickable.get(picked); + areaMap.put(areaPk, pickedMN); + } else { + int picked = this.random.nextInt(allPokes.size()); + Pokemon pickedMN = allPokes.get(picked); + areaMap.put(areaPk, pickedMN); + pickablePokemon.remove(pickedMN); + if (allPokes != pickablePokemon) { + allPokes.remove(pickedMN); + } + if (allPokes.size() == 0) { + // Start again + if (allowAltFormes) { + allPokes.addAll(noLegendaries ? noLegendaryListInclFormes : mainPokemonListInclFormes); + allPokes.removeIf(o -> ((Pokemon) o).actuallyCosmetic); + } else { + allPokes.addAll(noLegendaries ? noLegendaryList : mainPokemonList); + } + allPokes.removeAll(banned); + if (pickablePokemon != allPokes) { + pickablePokemon.addAll(allPokes); + pickablePokemon.removeAll(area.bannedPokemon); + } + } + } + } + for (Encounter enc : area.encounters) { + // In Catch 'Em All mode, don't randomize encounters for Pokemon that are banned for + // wild encounters. Otherwise, it may be impossible to obtain this Pokemon unless it + // randomly appears as a static or unless it becomes a random evolution. + if (banned.contains(enc.pokemon)) { + continue; + } + // Apply the map + enc.pokemon = areaMap.get(enc.pokemon); + setFormeForEncounter(enc, enc.pokemon); + } + } + } else if (typeThemed) { + Map> cachedPokeLists = new TreeMap<>(); + for (EncounterSet area : scrambledEncounters) { + // Poke-set + Set inArea = pokemonInArea(area); + List possiblePokemon = null; + int iterLoops = 0; + while (possiblePokemon == null && iterLoops < 10000) { + Type areaTheme = randomType(); + if (!cachedPokeLists.containsKey(areaTheme)) { + List pType = allowAltFormes ? pokemonOfTypeInclFormes(areaTheme, noLegendaries) : + pokemonOfType(areaTheme, noLegendaries); + pType.removeAll(banned); + cachedPokeLists.put(areaTheme, pType); + } + possiblePokemon = new ArrayList<>(cachedPokeLists.get(areaTheme)); + if (area.bannedPokemon.size() > 0) { + possiblePokemon.removeAll(area.bannedPokemon); + } + if (possiblePokemon.size() < inArea.size()) { + // Can't use this type for this area + possiblePokemon = null; + } + iterLoops++; + } + if (possiblePokemon == null) { + throw new RandomizationException("Could not randomize an area in a reasonable amount of attempts."); + } + + // Build area map using type theme. + Map areaMap = new TreeMap<>(); + for (Pokemon areaPk : inArea) { + int picked = this.random.nextInt(possiblePokemon.size()); + Pokemon pickedMN = possiblePokemon.get(picked); + while (pickedMN.actuallyCosmetic) { + picked = this.random.nextInt(possiblePokemon.size()); + pickedMN = possiblePokemon.get(picked); + } + areaMap.put(areaPk, pickedMN); + possiblePokemon.remove(picked); + } + for (Encounter enc : area.encounters) { + // Apply the map + enc.pokemon = areaMap.get(enc.pokemon); + setFormeForEncounter(enc, enc.pokemon); + } + } + } else if (usePowerLevels) { + List allowedPokes; + if (allowAltFormes) { + allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) + : new ArrayList<>(mainPokemonListInclFormes); + } else { + allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryList) + : new ArrayList<>(mainPokemonList); + } + allowedPokes.removeAll(banned); + for (EncounterSet area : scrambledEncounters) { + // Poke-set + Set inArea = pokemonInArea(area); + // Build area map using randoms + Map areaMap = new TreeMap<>(); + List usedPks = new ArrayList<>(); + List localAllowed = allowedPokes; + if (area.bannedPokemon.size() > 0) { + localAllowed = new ArrayList<>(allowedPokes); + localAllowed.removeAll(area.bannedPokemon); + } + for (Pokemon areaPk : inArea) { + Pokemon picked = pickWildPowerLvlReplacement(localAllowed, areaPk, false, usedPks, 100); + while (picked.actuallyCosmetic) { + picked = pickWildPowerLvlReplacement(localAllowed, areaPk, false, usedPks, 100); + } + areaMap.put(areaPk, picked); + usedPks.add(picked); + } + for (Encounter enc : area.encounters) { + // Apply the map + enc.pokemon = areaMap.get(enc.pokemon); + setFormeForEncounter(enc, enc.pokemon); + } + } + } else { + // Entirely random + for (EncounterSet area : scrambledEncounters) { + // Poke-set + Set inArea = pokemonInArea(area); + // Build area map using randoms + Map areaMap = new TreeMap<>(); + for (Pokemon areaPk : inArea) { + Pokemon picked = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned); + while (areaMap.containsValue(picked)) { + picked = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned); + } + areaMap.put(areaPk, picked); + } + for (Encounter enc : area.encounters) { + // Apply the map + enc.pokemon = areaMap.get(enc.pokemon); + setFormeForEncounter(enc, enc.pokemon); + } + } + } + + if (levelModifier != 0) { + for (EncounterSet area : currentEncounters) { + for (Encounter enc : area.encounters) { + enc.level = Math.min(100, (int) Math.round(enc.level * (1 + levelModifier / 100.0))); + enc.maxLevel = Math.min(100, (int) Math.round(enc.maxLevel * (1 + levelModifier / 100.0))); + } + } + } + } + + @Override + public void game1to1Encounters(Settings settings) { + boolean useTimeOfDay = settings.isUseTimeBasedEncounters(); + boolean usePowerLevels = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.SIMILAR_STRENGTH; + boolean noLegendaries = settings.isBlockWildLegendaries(); + int levelModifier = settings.isWildLevelsModified() ? settings.getWildLevelModifier() : 0; + boolean allowAltFormes = settings.isAllowWildAltFormes(); + boolean banIrregularAltFormes = settings.isBanIrregularAltFormes(); + boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE; + + checkPokemonRestrictions(); + // Build the full 1-to-1 map + Map translateMap = new TreeMap<>(); + List remainingLeft = allPokemonInclFormesWithoutNull(); + remainingLeft.removeIf(o -> ((Pokemon) o).actuallyCosmetic); + List remainingRight; + if (allowAltFormes) { + remainingRight = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) + : new ArrayList<>(mainPokemonListInclFormes); + remainingRight.removeIf(o -> ((Pokemon) o).actuallyCosmetic); + } else { + remainingRight = noLegendaries ? new ArrayList<>(noLegendaryList) + : new ArrayList<>(mainPokemonList); + } + List banned = this.bannedForWildEncounters(); + banned.addAll(this.getBannedFormesForPlayerPokemon()); + if (!abilitiesAreRandomized) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + banned.addAll(getIrregularFormes()); + } + // Banned pokemon should be mapped to themselves + for (Pokemon bannedPK : banned) { + translateMap.put(bannedPK, bannedPK); + remainingLeft.remove(bannedPK); + remainingRight.remove(bannedPK); + } + while (!remainingLeft.isEmpty()) { + if (usePowerLevels) { + int pickedLeft = this.random.nextInt(remainingLeft.size()); + Pokemon pickedLeftP = remainingLeft.remove(pickedLeft); + Pokemon pickedRightP; + if (remainingRight.size() == 1) { + // pick this (it may or may not be the same poke) + pickedRightP = remainingRight.get(0); + } else { + // pick on power level with the current one blocked + pickedRightP = pickWildPowerLvlReplacement(remainingRight, pickedLeftP, true, null, 100); + } + remainingRight.remove(pickedRightP); + translateMap.put(pickedLeftP, pickedRightP); + } else { + int pickedLeft = this.random.nextInt(remainingLeft.size()); + int pickedRight = this.random.nextInt(remainingRight.size()); + Pokemon pickedLeftP = remainingLeft.remove(pickedLeft); + Pokemon pickedRightP = remainingRight.get(pickedRight); + while (pickedLeftP.number == pickedRightP.number && remainingRight.size() != 1) { + // Reroll for a different pokemon if at all possible + pickedRight = this.random.nextInt(remainingRight.size()); + pickedRightP = remainingRight.get(pickedRight); + } + remainingRight.remove(pickedRight); + translateMap.put(pickedLeftP, pickedRightP); + } + if (remainingRight.size() == 0) { + // restart + if (allowAltFormes) { + remainingRight.addAll(noLegendaries ? noLegendaryListInclFormes : mainPokemonListInclFormes); + remainingRight.removeIf(o -> ((Pokemon) o).actuallyCosmetic); + } else { + remainingRight.addAll(noLegendaries ? noLegendaryList : mainPokemonList); + } + remainingRight.removeAll(banned); + } + } + + // Map remaining to themselves just in case + List allPokes = allPokemonInclFormesWithoutNull(); + for (Pokemon poke : allPokes) { + if (!translateMap.containsKey(poke)) { + translateMap.put(poke, poke); + } + } + + List currentEncounters = this.getEncounters(useTimeOfDay); + + for (EncounterSet area : currentEncounters) { + for (Encounter enc : area.encounters) { + // Apply the map + enc.pokemon = translateMap.get(enc.pokemon); + if (area.bannedPokemon.contains(enc.pokemon)) { + // Ignore the map and put a random non-banned poke + List tempPickable; + if (allowAltFormes) { + tempPickable = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) + : new ArrayList<>(mainPokemonListInclFormes); + tempPickable.removeIf(o -> ((Pokemon) o).actuallyCosmetic); + } else { + tempPickable = noLegendaries ? new ArrayList<>(noLegendaryList) + : new ArrayList<>(mainPokemonList); + } + tempPickable.removeAll(banned); + tempPickable.removeAll(area.bannedPokemon); + if (tempPickable.size() == 0) { + throw new RandomizationException("ERROR: Couldn't replace a wild Pokemon!"); + } + if (usePowerLevels) { + enc.pokemon = pickWildPowerLvlReplacement(tempPickable, enc.pokemon, false, null, 100); + } else { + int picked = this.random.nextInt(tempPickable.size()); + enc.pokemon = tempPickable.get(picked); + } + } + setFormeForEncounter(enc, enc.pokemon); + } + } + if (levelModifier != 0) { + for (EncounterSet area : currentEncounters) { + for (Encounter enc : area.encounters) { + enc.level = Math.min(100, (int) Math.round(enc.level * (1 + levelModifier / 100.0))); + enc.maxLevel = Math.min(100, (int) Math.round(enc.maxLevel * (1 + levelModifier / 100.0))); + } + } + } + + setEncounters(useTimeOfDay, currentEncounters); + + } + + @Override + public void onlyChangeWildLevels(Settings settings) { + int levelModifier = settings.getWildLevelModifier(); + + List currentEncounters = this.getEncounters(true); + + if (levelModifier != 0) { + for (EncounterSet area : currentEncounters) { + for (Encounter enc : area.encounters) { + enc.level = Math.min(100, (int) Math.round(enc.level * (1 + levelModifier / 100.0))); + enc.maxLevel = Math.min(100, (int) Math.round(enc.maxLevel * (1 + levelModifier / 100.0))); + } + } + setEncounters(true, currentEncounters); + } + } + + private void enhanceRandomEncountersORAS(List collapsedEncounters, Settings settings) { + boolean catchEmAll = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.CATCH_EM_ALL; + boolean typeThemed = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.TYPE_THEME_AREAS; + boolean usePowerLevels = settings.getWildPokemonRestrictionMod() == Settings.WildPokemonRestrictionMod.SIMILAR_STRENGTH; + boolean noLegendaries = settings.isBlockWildLegendaries(); + boolean allowAltFormes = settings.isAllowWildAltFormes(); + boolean banIrregularAltFormes = settings.isBanIrregularAltFormes(); + boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE; + + List banned = this.bannedForWildEncounters(); + if (!abilitiesAreRandomized) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + banned.addAll(getIrregularFormes()); + } + Map> zonesToEncounters = mapZonesToEncounters(collapsedEncounters); + Map> cachedPokeLists = new TreeMap<>(); + for (List encountersInZone : zonesToEncounters.values()) { + int currentAreaIndex = -1; + List nonRockSmashAreas = new ArrayList<>(); + Map> areasAndEncountersToRandomize = new TreeMap<>(); + // Since Rock Smash Pokemon do not show up on DexNav, they can be fully randomized + for (EncounterSet area : encountersInZone) { + if (area.displayName.contains("Rock Smash")) { + // Assume EITHER catch em all OR type themed OR match strength for now + if (catchEmAll) { + for (Encounter enc : area.encounters) { + boolean shouldRandomize = doesAnotherEncounterWithSamePokemonExistInArea(enc, area); + if (shouldRandomize) { + enc.pokemon = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned); + setFormeForEncounter(enc, enc.pokemon); + } + } + } else if (typeThemed) { + List possiblePokemon = null; + int iterLoops = 0; + while (possiblePokemon == null && iterLoops < 10000) { + Type areaTheme = randomType(); + if (!cachedPokeLists.containsKey(areaTheme)) { + List pType = allowAltFormes ? pokemonOfTypeInclFormes(areaTheme, noLegendaries) : + pokemonOfType(areaTheme, noLegendaries); + pType.removeAll(banned); + cachedPokeLists.put(areaTheme, pType); + } + possiblePokemon = cachedPokeLists.get(areaTheme); + if (area.bannedPokemon.size() > 0) { + possiblePokemon = new ArrayList<>(possiblePokemon); + possiblePokemon.removeAll(area.bannedPokemon); + } + if (possiblePokemon.size() == 0) { + // Can't use this type for this area + possiblePokemon = null; + } + iterLoops++; + } + if (possiblePokemon == null) { + throw new RandomizationException("Could not randomize an area in a reasonable amount of attempts."); + } + for (Encounter enc : area.encounters) { + // Pick a random themed pokemon + enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size())); + while (enc.pokemon.actuallyCosmetic) { + enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size())); + } + setFormeForEncounter(enc, enc.pokemon); + } + } else if (usePowerLevels) { + List allowedPokes; + if (allowAltFormes) { + allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) + : new ArrayList<>(mainPokemonListInclFormes); + } else { + allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryList) + : new ArrayList<>(mainPokemonList); + } + allowedPokes.removeAll(banned); + List localAllowed = allowedPokes; + if (area.bannedPokemon.size() > 0) { + localAllowed = new ArrayList<>(allowedPokes); + localAllowed.removeAll(area.bannedPokemon); + } + for (Encounter enc : area.encounters) { + enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100); + while (enc.pokemon.actuallyCosmetic) { + enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100); + } + setFormeForEncounter(enc, enc.pokemon); + } + } else { + // Entirely random + for (Encounter enc : area.encounters) { + enc.pokemon = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned); + setFormeForEncounter(enc, enc.pokemon); + } + } + } else { + currentAreaIndex++; + nonRockSmashAreas.add(area); + List encounterIndices = new ArrayList<>(); + for (int i = 0; i < area.encounters.size(); i++) { + encounterIndices.add(i); + } + areasAndEncountersToRandomize.put(currentAreaIndex, encounterIndices); + } + } + + // Now, randomize non-Rock Smash Pokemon until we hit the threshold for DexNav + int crashThreshold = computeDexNavCrashThreshold(encountersInZone); + while (crashThreshold < 18 && areasAndEncountersToRandomize.size() > 0) { + Set areaIndices = areasAndEncountersToRandomize.keySet(); + int areaIndex = areaIndices.stream().skip(this.random.nextInt(areaIndices.size())).findFirst().orElse(-1); + List encounterIndices = areasAndEncountersToRandomize.get(areaIndex); + int indexInListOfEncounterIndices = this.random.nextInt(encounterIndices.size()); + int randomEncounterIndex = encounterIndices.get(indexInListOfEncounterIndices); + EncounterSet area = nonRockSmashAreas.get(areaIndex); + Encounter enc = area.encounters.get(randomEncounterIndex); + // Assume EITHER catch em all OR type themed OR match strength for now + if (catchEmAll) { + boolean shouldRandomize = doesAnotherEncounterWithSamePokemonExistInArea(enc, area); + if (shouldRandomize) { + enc.pokemon = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned); + setFormeForEncounter(enc, enc.pokemon); + } + } else if (typeThemed) { + List possiblePokemon = null; + Type areaTheme = getTypeForArea(area); + if (!cachedPokeLists.containsKey(areaTheme)) { + List pType = allowAltFormes ? pokemonOfTypeInclFormes(areaTheme, noLegendaries) : + pokemonOfType(areaTheme, noLegendaries); + pType.removeAll(banned); + cachedPokeLists.put(areaTheme, pType); + } + possiblePokemon = cachedPokeLists.get(areaTheme); + if (area.bannedPokemon.size() > 0) { + possiblePokemon = new ArrayList<>(possiblePokemon); + possiblePokemon.removeAll(area.bannedPokemon); + } + if (possiblePokemon.size() == 0) { + // Can't use this type for this area + throw new RandomizationException("Could not find a possible Pokemon of the correct type."); + } + // Pick a random themed pokemon + enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size())); + while (enc.pokemon.actuallyCosmetic) { + enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size())); + } + setFormeForEncounter(enc, enc.pokemon); + } else if (usePowerLevels) { + List allowedPokes; + if (allowAltFormes) { + allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryListInclFormes) + : new ArrayList<>(mainPokemonListInclFormes); + } else { + allowedPokes = noLegendaries ? new ArrayList<>(noLegendaryList) + : new ArrayList<>(mainPokemonList); + } + allowedPokes.removeAll(banned); + List localAllowed = allowedPokes; + if (area.bannedPokemon.size() > 0) { + localAllowed = new ArrayList<>(allowedPokes); + localAllowed.removeAll(area.bannedPokemon); + } + enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100); + while (enc.pokemon.actuallyCosmetic) { + enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null, 100); + } + setFormeForEncounter(enc, enc.pokemon); + } else { + // Entirely random + enc.pokemon = pickEntirelyRandomPokemon(allowAltFormes, noLegendaries, area, banned); + setFormeForEncounter(enc, enc.pokemon); + } + crashThreshold = computeDexNavCrashThreshold(encountersInZone); + encounterIndices.remove(indexInListOfEncounterIndices); + if (encounterIndices.size() == 0) { + areasAndEncountersToRandomize.remove(areaIndex); + } + } + } + } + + private Type getTypeForArea(EncounterSet area) { + Pokemon firstPokemon = area.encounters.get(0).pokemon; + if (area.encounters.get(0).formeNumber != 0) { + firstPokemon = getAltFormeOfPokemon(firstPokemon, area.encounters.get(0).formeNumber); + } + Type primaryType = firstPokemon.primaryType; + int primaryCount = 1; + Type secondaryType = null; + int secondaryCount = 0; + if (firstPokemon.secondaryType != null) { + secondaryType = firstPokemon.secondaryType; + secondaryCount = 1; + } + for (int i = 1; i < area.encounters.size(); i++) { + Pokemon pokemon = area.encounters.get(i).pokemon; + if (area.encounters.get(i).formeNumber != 0) { + pokemon = getAltFormeOfPokemon(pokemon, area.encounters.get(i).formeNumber); + } + if (pokemon.primaryType == primaryType || pokemon.secondaryType == primaryType) { + primaryCount++; + } + if (pokemon.primaryType == secondaryType || pokemon.secondaryType == secondaryType) { + secondaryCount++; + } + } + return primaryCount > secondaryCount ? primaryType : secondaryType; + } + + private boolean doesAnotherEncounterWithSamePokemonExistInArea(Encounter enc, EncounterSet area) { + for (Encounter encounterToCheck : area.encounters) { + if (enc != encounterToCheck && enc.pokemon == encounterToCheck.pokemon) { + return true; + } + } + return false; + } + + private List collapseAreasORAS(List currentEncounters) { + List output = new ArrayList<>(); + Map> zonesToEncounters = mapZonesToEncounters(currentEncounters); + for (Integer zone : zonesToEncounters.keySet()) { + List encountersInZone = zonesToEncounters.get(zone); + int crashThreshold = computeDexNavCrashThreshold(encountersInZone); + if (crashThreshold <= 18) { + output.addAll(encountersInZone); + continue; + } + + // Naive Area 1-to-1 randomization will crash the game, so let's start collapsing areas to prevent this. + // Start with combining all the fishing rod encounters, since it's a little less noticeable when they've + // been collapsed. + List collapsedEncounters = new ArrayList<>(encountersInZone); + EncounterSet rodGroup = new EncounterSet(); + rodGroup.offset = zone; + rodGroup.displayName = "Rod Group"; + for (EncounterSet area : encountersInZone) { + if (area.displayName.contains("Old Rod") || area.displayName.contains("Good Rod") || area.displayName.contains("Super Rod")) { + collapsedEncounters.remove(area); + rodGroup.encounters.addAll(area.encounters); + } + } + if (rodGroup.encounters.size() > 0) { + collapsedEncounters.add(rodGroup); + } + crashThreshold = computeDexNavCrashThreshold(collapsedEncounters); + if (crashThreshold <= 18) { + output.addAll(collapsedEncounters); + continue; + } + + // Even after combining all the fishing rod encounters, we're still not below the threshold to prevent + // DexNav from crashing the game. Combine all the grass encounters now to drop us below the threshold; + // we've combined everything that DexNav normally combines, so at this point, we're *guaranteed* not + // to crash the game. + EncounterSet grassGroup = new EncounterSet(); + grassGroup.offset = zone; + grassGroup.displayName = "Grass Group"; + for (EncounterSet area : encountersInZone) { + if (area.displayName.contains("Grass/Cave") || area.displayName.contains("Long Grass") || area.displayName.contains("Horde")) { + collapsedEncounters.remove(area); + grassGroup.encounters.addAll(area.encounters); + } + } + if (grassGroup.encounters.size() > 0) { + collapsedEncounters.add(grassGroup); + } + + output.addAll(collapsedEncounters); + } + return output; + } + + private int computeDexNavCrashThreshold(List encountersInZone) { + int crashThreshold = 0; + for (EncounterSet area : encountersInZone) { + if (area.displayName.contains("Rock Smash")) { + continue; // Rock Smash Pokemon don't display on DexNav + } + Set uniquePokemonInArea = new HashSet<>(); + for (Encounter enc : area.encounters) { + if (enc.pokemon.baseForme != null) { // DexNav treats different forms as one Pokemon + uniquePokemonInArea.add(enc.pokemon.baseForme); + } else { + uniquePokemonInArea.add(enc.pokemon); + } + } + crashThreshold += uniquePokemonInArea.size(); + } + return crashThreshold; + } + + private void setEvoChainAsIllegal(Pokemon newPK, List illegalList, boolean willForceEvolve) { + // set pre-evos as illegal + setIllegalPreEvos(newPK, illegalList); + + // if the placed Pokemon will be forced fully evolved, set its evolutions as illegal + if (willForceEvolve) { + setIllegalEvos(newPK, illegalList); + } + } + + private void setIllegalPreEvos(Pokemon pk, List illegalList) { + for (Evolution evo: pk.evolutionsTo) { + pk = evo.from; + illegalList.add(pk); + setIllegalPreEvos(pk, illegalList); + } + } + + private void setIllegalEvos(Pokemon pk, List illegalList) { + for (Evolution evo: pk.evolutionsFrom) { + pk = evo.to; + illegalList.add(pk); + setIllegalEvos(pk, illegalList); + } + } + + private List getFinalEvos(Pokemon pk) { + List finalEvos = new ArrayList<>(); + traverseEvolutions(pk, finalEvos); + return finalEvos; + } + + private void traverseEvolutions(Pokemon pk, List finalEvos) { + if (!pk.evolutionsFrom.isEmpty()) { + for (Evolution evo: pk.evolutionsFrom) { + pk = evo.to; + traverseEvolutions(pk, finalEvos); + } + } else { + finalEvos.add(pk); + } + } + + private void setFormeForTrainerPokemon(TrainerPokemon tp, Pokemon pk) { + boolean checkCosmetics = true; + tp.formeSuffix = ""; + tp.forme = 0; + if (pk.formeNumber > 0) { + tp.forme = pk.formeNumber; + tp.formeSuffix = pk.formeSuffix; + tp.pokemon = pk.baseForme; + checkCosmetics = false; + } + if (checkCosmetics && tp.pokemon.cosmeticForms > 0) { + tp.forme = tp.pokemon.getCosmeticFormNumber(this.random.nextInt(tp.pokemon.cosmeticForms)); + } else if (!checkCosmetics && pk.cosmeticForms > 0) { + tp.forme += pk.getCosmeticFormNumber(this.random.nextInt(pk.cosmeticForms)); + } + } + + private void applyLevelModifierToTrainerPokemon(Trainer trainer, int levelModifier) { + if (levelModifier != 0) { + for (TrainerPokemon tp : trainer.pokemon) { + tp.level = Math.min(100, (int) Math.round(tp.level * (1 + levelModifier / 100.0))); + } + } + } + + @Override + public void randomizeTrainerPokes(Settings settings) { + boolean usePowerLevels = settings.isTrainersUsePokemonOfSimilarStrength(); + boolean weightByFrequency = settings.isTrainersMatchTypingDistribution(); + boolean noLegendaries = settings.isTrainersBlockLegendaries(); + boolean noEarlyWonderGuard = settings.isTrainersBlockEarlyWonderGuard(); + int levelModifier = settings.isTrainersLevelModified() ? settings.getTrainersLevelModifier() : 0; + boolean isTypeThemed = settings.getTrainersMod() == Settings.TrainersMod.TYPE_THEMED; + boolean isTypeThemedEliteFourGymOnly = settings.getTrainersMod() == Settings.TrainersMod.TYPE_THEMED_ELITE4_GYMS; + boolean distributionSetting = settings.getTrainersMod() == Settings.TrainersMod.DISTRIBUTED; + boolean mainPlaythroughSetting = settings.getTrainersMod() == Settings.TrainersMod.MAINPLAYTHROUGH; + boolean includeFormes = settings.isAllowTrainerAlternateFormes(); + boolean banIrregularAltFormes = settings.isBanIrregularAltFormes(); + boolean swapMegaEvos = settings.isSwapTrainerMegaEvos(); + boolean shinyChance = settings.isShinyChance(); + boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE; + int eliteFourUniquePokemonNumber = settings.getEliteFourUniquePokemonNumber(); + boolean forceFullyEvolved = settings.isTrainersForceFullyEvolved(); + int forceFullyEvolvedLevel = settings.getTrainersForceFullyEvolvedLevel(); + boolean forceChallengeMode = (settings.getCurrentMiscTweaks() & MiscTweak.FORCE_CHALLENGE_MODE.getValue()) > 0; + boolean rivalCarriesStarter = settings.isRivalCarriesStarterThroughout(); + + checkPokemonRestrictions(); + + // Set up Pokemon pool + cachedReplacementLists = new TreeMap<>(); + cachedAllList = noLegendaries ? new ArrayList<>(noLegendaryList) : new ArrayList<>( + mainPokemonList); + if (includeFormes) { + if (noLegendaries) { + cachedAllList.addAll(noLegendaryAltsList); + } else { + cachedAllList.addAll(altFormesList); + } + } + cachedAllList = + cachedAllList + .stream() + .filter(pk -> !pk.actuallyCosmetic) + .collect(Collectors.toList()); + + List banned = this.getBannedFormesForTrainerPokemon(); + if (!abilitiesAreRandomized) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + banned.addAll(getIrregularFormes()); + } + cachedAllList.removeAll(banned); + + List currentTrainers = this.getTrainers(); + + // Type Themed related + Map trainerTypes = new TreeMap<>(); + Set usedUberTypes = new TreeSet<>(); + if (isTypeThemed || isTypeThemedEliteFourGymOnly) { + typeWeightings = new TreeMap<>(); + totalTypeWeighting = 0; + // Construct groupings for types + // Anything starting with GYM or ELITE or CHAMPION is a group + Map> groups = new TreeMap<>(); + for (Trainer t : currentTrainers) { + if (t.tag != null && t.tag.equals("IRIVAL")) { + // This is the first rival in Yellow. His Pokemon is used to determine the non-player + // starter, so we can't change it here. Just skip it. + continue; + } + String group = t.tag == null ? "" : t.tag; + if (group.contains("-")) { + group = group.substring(0, group.indexOf('-')); + } + if (group.startsWith("GYM") || group.startsWith("ELITE") || + ((group.startsWith("CHAMPION") || group.startsWith("THEMED")) && !isTypeThemedEliteFourGymOnly)) { + // Yep this is a group + if (!groups.containsKey(group)) { + groups.put(group, new ArrayList<>()); + } + groups.get(group).add(t); + } else if (group.startsWith("GIO")) { + // Giovanni has same grouping as his gym, gym 8 + if (!groups.containsKey("GYM8")) { + groups.put("GYM8", new ArrayList<>()); + } + groups.get("GYM8").add(t); + } + } + + // Give a type to each group + // Gym & elite types have to be unique + // So do uber types, including the type we pick for champion + Set usedGymTypes = new TreeSet<>(); + Set usedEliteTypes = new TreeSet<>(); + for (String group : groups.keySet()) { + List trainersInGroup = groups.get(group); + // Shuffle ordering within group to promote randomness + Collections.shuffle(trainersInGroup, random); + Type typeForGroup = pickType(weightByFrequency, noLegendaries, includeFormes); + if (group.startsWith("GYM")) { + while (usedGymTypes.contains(typeForGroup)) { + typeForGroup = pickType(weightByFrequency, noLegendaries, includeFormes); + } + usedGymTypes.add(typeForGroup); + } + if (group.startsWith("ELITE")) { + while (usedEliteTypes.contains(typeForGroup)) { + typeForGroup = pickType(weightByFrequency, noLegendaries, includeFormes); + } + usedEliteTypes.add(typeForGroup); + } + if (group.equals("CHAMPION")) { + usedUberTypes.add(typeForGroup); + } + + for (Trainer t : trainersInGroup) { + trainerTypes.put(t, typeForGroup); + } + } + } + + // Randomize the order trainers are randomized in. + // Leads to less predictable results for various modifiers. + // Need to keep the original ordering around for saving though. + List scrambledTrainers = new ArrayList<>(currentTrainers); + Collections.shuffle(scrambledTrainers, this.random); + + // Elite Four Unique Pokemon related + boolean eliteFourUniquePokemon = eliteFourUniquePokemonNumber > 0; + List illegalIfEvolvedList = new ArrayList<>(); + List bannedFromUniqueList = new ArrayList<>(); + boolean illegalEvoChains = false; + List eliteFourIndices = getEliteFourTrainers(forceChallengeMode); + if (eliteFourUniquePokemon) { + // Sort Elite Four Trainers to the start of the list + scrambledTrainers.sort((t1, t2) -> + Boolean.compare(eliteFourIndices.contains(currentTrainers.indexOf(t2)+1),eliteFourIndices.contains(currentTrainers.indexOf(t1)+1))); + illegalEvoChains = forceFullyEvolved; + if (rivalCarriesStarter) { + List starterList = getStarters().subList(0,3); + for (Pokemon starter: starterList) { + // If rival/friend carries starter, the starters cannot be set as unique + bannedFromUniqueList.add(starter); + setEvoChainAsIllegal(starter, bannedFromUniqueList, true); + + // If the final boss is a rival/friend, the fully evolved starters will be unique + if (hasRivalFinalBattle()) { + cachedAllList.removeAll(getFinalEvos(starter)); + if (illegalEvoChains) { + illegalIfEvolvedList.add(starter); + setEvoChainAsIllegal(starter, illegalIfEvolvedList, true); + } + } + } + } + } + + List mainPlaythroughTrainers = getMainPlaythroughTrainers(); + + // Randomize Trainer Pokemon + // The result after this is done will not be final if "Force Fully Evolved" or "Rival Carries Starter" + // are used, as they are applied later + for (Trainer t : scrambledTrainers) { + applyLevelModifierToTrainerPokemon(t, levelModifier); + if (t.tag != null && t.tag.equals("IRIVAL")) { + // This is the first rival in Yellow. His Pokemon is used to determine the non-player + // starter, so we can't change it here. Just skip it. + continue; + } + + // If type themed, give a type to each unassigned trainer + Type typeForTrainer = trainerTypes.get(t); + if (typeForTrainer == null && isTypeThemed) { + typeForTrainer = pickType(weightByFrequency, noLegendaries, includeFormes); + // Ubers: can't have the same type as each other + if (t.tag != null && t.tag.equals("UBER")) { + while (usedUberTypes.contains(typeForTrainer)) { + typeForTrainer = pickType(weightByFrequency, noLegendaries, includeFormes); + } + usedUberTypes.add(typeForTrainer); + } + } + + List evolvesIntoTheWrongType = new ArrayList<>(); + if (typeForTrainer != null) { + List pokemonOfType = includeFormes ? pokemonOfTypeInclFormes(typeForTrainer, noLegendaries) : + pokemonOfType(typeForTrainer, noLegendaries); + for (Pokemon pk : pokemonOfType) { + if (!pokemonOfType.contains(fullyEvolve(pk, t.index))) { + evolvesIntoTheWrongType.add(pk); + } + } + } + + List trainerPokemonList = new ArrayList<>(t.pokemon); + + // Elite Four Unique Pokemon related + boolean eliteFourTrackPokemon = false; + boolean eliteFourRival = false; + if (eliteFourUniquePokemon && eliteFourIndices.contains(t.index)) { + eliteFourTrackPokemon = true; + + // Sort Pokemon list back to front, and then put highest level Pokemon first + // (Only while randomizing, does not affect order in game) + Collections.reverse(trainerPokemonList); + trainerPokemonList.sort((tp1, tp2) -> Integer.compare(tp2.level, tp1.level)); + if (rivalCarriesStarter && (t.tag.contains("RIVAL") || t.tag.contains("FRIEND"))) { + eliteFourRival = true; + } + } + + for (TrainerPokemon tp : trainerPokemonList) { + boolean swapThisMegaEvo = swapMegaEvos && tp.canMegaEvolve(); + boolean wgAllowed = (!noEarlyWonderGuard) || tp.level >= 20; + boolean eliteFourSetUniquePokemon = + eliteFourTrackPokemon && eliteFourUniquePokemonNumber > trainerPokemonList.indexOf(tp); + boolean willForceEvolve = forceFullyEvolved && tp.level >= forceFullyEvolvedLevel; + + Pokemon oldPK = tp.pokemon; + if (tp.forme > 0) { + oldPK = getAltFormeOfPokemon(oldPK, tp.forme); + } + + bannedList = new ArrayList<>(); + bannedList.addAll(usedAsUniqueList); + if (illegalEvoChains && willForceEvolve) { + bannedList.addAll(illegalIfEvolvedList); + } + if (eliteFourSetUniquePokemon) { + bannedList.addAll(bannedFromUniqueList); + } + if (willForceEvolve) { + bannedList.addAll(evolvesIntoTheWrongType); + } + + Pokemon newPK = pickTrainerPokeReplacement( + oldPK, + usePowerLevels, + typeForTrainer, + noLegendaries, + wgAllowed, + distributionSetting || (mainPlaythroughSetting && mainPlaythroughTrainers.contains(t.index)), + swapThisMegaEvo, + abilitiesAreRandomized, + includeFormes, + banIrregularAltFormes + ); + + // Chosen Pokemon is locked in past here + if (distributionSetting || (mainPlaythroughSetting && mainPlaythroughTrainers.contains(t.index))) { + setPlacementHistory(newPK); + } + tp.pokemon = newPK; + setFormeForTrainerPokemon(tp, newPK); + tp.abilitySlot = getRandomAbilitySlot(newPK); + tp.resetMoves = true; + + if (!eliteFourRival) { + if (eliteFourSetUniquePokemon) { + List actualPKList; + if (willForceEvolve) { + actualPKList = getFinalEvos(newPK); + } else { + actualPKList = new ArrayList<>(); + actualPKList.add(newPK); + } + // If the unique Pokemon will evolve, we have to set all its potential evolutions as unique + for (Pokemon actualPK: actualPKList) { + usedAsUniqueList.add(actualPK); + if (illegalEvoChains) { + setEvoChainAsIllegal(actualPK, illegalIfEvolvedList, willForceEvolve); + } + } + } + if (eliteFourTrackPokemon) { + bannedFromUniqueList.add(newPK); + if (illegalEvoChains) { + setEvoChainAsIllegal(newPK, bannedFromUniqueList, willForceEvolve); + } + } + } else { + // If the champion is a rival, the first Pokemon will be skipped - it's already + // set as unique since it's a starter + eliteFourRival = false; + } + + if (swapThisMegaEvo) { + tp.heldItem = newPK + .megaEvolutionsFrom + .get(this.random.nextInt(newPK.megaEvolutionsFrom.size())) + .argument; + } + + if (shinyChance) { + if (this.random.nextInt(256) == 0) { + tp.IVs |= (1 << 30); + } + } + } + } + + // Save it all up + this.setTrainers(currentTrainers, false); + } + + @Override + public void randomizeTrainerHeldItems(Settings settings) { + boolean giveToBossPokemon = settings.isRandomizeHeldItemsForBossTrainerPokemon(); + boolean giveToImportantPokemon = settings.isRandomizeHeldItemsForImportantTrainerPokemon(); + boolean giveToRegularPokemon = settings.isRandomizeHeldItemsForRegularTrainerPokemon(); + boolean highestLevelOnly = settings.isHighestLevelGetsItemsForTrainers(); + + List moves = this.getMoves(); + Map> movesets = this.getMovesLearnt(); + List currentTrainers = this.getTrainers(); + for (Trainer t : currentTrainers) { + if (trainerShouldNotGetBuffs(t)) { + continue; + } + if (!giveToRegularPokemon && (!t.isImportant() && !t.isBoss())) { + continue; + } + if (!giveToImportantPokemon && t.isImportant()) { + continue; + } + if (!giveToBossPokemon && t.isBoss()) { + continue; + } + t.setPokemonHaveItems(true); + if (highestLevelOnly) { + int maxLevel = -1; + TrainerPokemon highestLevelPoke = null; + for (TrainerPokemon tp : t.pokemon) { + if (tp.level > maxLevel) { + highestLevelPoke = tp; + maxLevel = tp.level; + } + } + if (highestLevelPoke == null) { + continue; // should never happen - trainer had zero pokes + } + int[] moveset = highestLevelPoke.resetMoves ? + RomFunctions.getMovesAtLevel(getAltFormeOfPokemon( + highestLevelPoke.pokemon, highestLevelPoke.forme).number, + movesets, + highestLevelPoke.level) : + highestLevelPoke.moves; + randomizeHeldItem(highestLevelPoke, settings, moves, moveset); + } else { + for (TrainerPokemon tp : t.pokemon) { + int[] moveset = tp.resetMoves ? + RomFunctions.getMovesAtLevel(getAltFormeOfPokemon( + tp.pokemon, tp.forme).number, + movesets, + tp.level) : + tp.moves; + randomizeHeldItem(tp, settings, moves, moveset); + if (t.requiresUniqueHeldItems) { + while (!t.pokemonHaveUniqueHeldItems()) { + randomizeHeldItem(tp, settings, moves, moveset); + } + } + } + } + } + this.setTrainers(currentTrainers, false); + } + + private void randomizeHeldItem(TrainerPokemon tp, Settings settings, List moves, int[] moveset) { + boolean sensibleItemsOnly = settings.isSensibleItemsOnlyForTrainers(); + boolean consumableItemsOnly = settings.isConsumableItemsOnlyForTrainers(); + boolean swapMegaEvolutions = settings.isSwapTrainerMegaEvos(); + if (tp.hasZCrystal) { + return; // Don't overwrite existing Z Crystals. + } + if (tp.hasMegaStone && swapMegaEvolutions) { + return; // Don't overwrite mega stones if another setting handled that. + } + List toChooseFrom; + if (sensibleItemsOnly) { + toChooseFrom = getSensibleHeldItemsFor(tp, consumableItemsOnly, moves, moveset); + } else if (consumableItemsOnly) { + toChooseFrom = getAllConsumableHeldItems(); + } else { + toChooseFrom = getAllHeldItems(); + } + tp.heldItem = toChooseFrom.get(random.nextInt(toChooseFrom.size())); + } + + @Override + public void rivalCarriesStarter() { + checkPokemonRestrictions(); + List currentTrainers = this.getTrainers(); + rivalCarriesStarterUpdate(currentTrainers, "RIVAL", isORAS ? 0 : 1); + rivalCarriesStarterUpdate(currentTrainers, "FRIEND", 2); + this.setTrainers(currentTrainers, false); + } + + @Override + public boolean hasRivalFinalBattle() { + return false; + } + + @Override + public void forceFullyEvolvedTrainerPokes(Settings settings) { + int minLevel = settings.getTrainersForceFullyEvolvedLevel(); + + checkPokemonRestrictions(); + List currentTrainers = this.getTrainers(); + for (Trainer t : currentTrainers) { + for (TrainerPokemon tp : t.pokemon) { + if (tp.level >= minLevel) { + Pokemon newPokemon = fullyEvolve(tp.pokemon, t.index); + if (newPokemon != tp.pokemon) { + tp.pokemon = newPokemon; + setFormeForTrainerPokemon(tp, newPokemon); + tp.abilitySlot = getValidAbilitySlotFromOriginal(newPokemon, tp.abilitySlot); + tp.resetMoves = true; + } + } + } + } + this.setTrainers(currentTrainers, false); + } + + @Override + public void onlyChangeTrainerLevels(Settings settings) { + int levelModifier = settings.getTrainersLevelModifier(); + + List currentTrainers = this.getTrainers(); + for (Trainer t: currentTrainers) { + applyLevelModifierToTrainerPokemon(t, levelModifier); + } + this.setTrainers(currentTrainers, false); + } + + @Override + public void addTrainerPokemon(Settings settings) { + int additionalNormal = settings.getAdditionalRegularTrainerPokemon(); + int additionalImportant = settings.getAdditionalImportantTrainerPokemon(); + int additionalBoss = settings.getAdditionalBossTrainerPokemon(); + + List currentTrainers = this.getTrainers(); + for (Trainer t: currentTrainers) { + int additional; + if (t.isBoss()) { + additional = additionalBoss; + } else if (t.isImportant()) { + if (t.skipImportant()) continue; + additional = additionalImportant; + } else { + additional = additionalNormal; + } + + if (additional == 0) { + continue; + } + + int lowest = 100; + List potentialPokes = new ArrayList<>(); + + // First pass: find lowest level + for (TrainerPokemon tpk: t.pokemon) { + if (tpk.level < lowest) { + lowest = tpk.level; + } + } + + // Second pass: find all Pokemon at lowest level + for (TrainerPokemon tpk: t.pokemon) { + if (tpk.level == lowest) { + potentialPokes.add(tpk); + } + } + + // If a trainer can appear in a Multi Battle (i.e., a Double Battle where the enemy consists + // of two independent trainers), we want to be aware of that so we don't give them a team of + // six Pokemon and have a 6v12 battle + int maxPokemon = t.multiBattleStatus != Trainer.MultiBattleStatus.NEVER ? 3 : 6; + for (int i = 0; i < additional; i++) { + if (t.pokemon.size() >= maxPokemon) break; + + // We want to preserve the original last Pokemon because the order is sometimes used to + // determine the rival's starter + int secondToLastIndex = t.pokemon.size() - 1; + TrainerPokemon newPokemon = potentialPokes.get(i % potentialPokes.size()).copy(); + + // Clear out the held item because we only want one Pokemon with a mega stone if we're + // swapping mega evolvables + newPokemon.heldItem = 0; + t.pokemon.add(secondToLastIndex, newPokemon); + } + } + this.setTrainers(currentTrainers, false); + } + + @Override + public void doubleBattleMode() { + List currentTrainers = this.getTrainers(); + for (Trainer t: currentTrainers) { + if (t.pokemon.size() != 1 || t.multiBattleStatus == Trainer.MultiBattleStatus.ALWAYS || this.trainerShouldNotGetBuffs(t)) { + continue; + } + t.pokemon.add(t.pokemon.get(0).copy()); + } + this.setTrainers(currentTrainers, true); + } + + private Map> allLevelUpMoves; + private Map> allEggMoves; + private Map allTMCompat, allTutorCompat; + private List allTMMoves, allTutorMoves; + + @Override + public List getMoveSelectionPoolAtLevel(TrainerPokemon tp, boolean cyclicEvolutions) { + + List moves = getMoves(); + double eggMoveProbability = 0.1; + double preEvoMoveProbability = 0.5; + double tmMoveProbability = 0.6; + double tutorMoveProbability = 0.6; + + if (allLevelUpMoves == null) { + allLevelUpMoves = getMovesLearnt(); + } + + if (allEggMoves == null) { + allEggMoves = getEggMoves(); + } + + if (allTMCompat == null) { + allTMCompat = getTMHMCompatibility(); + } + + if (allTMMoves == null) { + allTMMoves = getTMMoves(); + } + + if (allTutorCompat == null && hasMoveTutors()) { + allTutorCompat = getMoveTutorCompatibility(); + } + + if (allTutorMoves == null) { + allTutorMoves = getMoveTutorMoves(); + } + + // Level-up Moves + List moveSelectionPoolAtLevel = allLevelUpMoves.get(getAltFormeOfPokemon(tp.pokemon, tp.forme).number) + .stream() + .filter(ml -> (ml.level <= tp.level && ml.level != 0) || (ml.level == 0 && tp.level >= 30)) + .map(ml -> moves.get(ml.move)) + .distinct() + .collect(Collectors.toList()); + + // Pre-Evo Moves + if (!cyclicEvolutions) { + Pokemon preEvo; + if (altFormesCanHaveDifferentEvolutions()) { + preEvo = getAltFormeOfPokemon(tp.pokemon, tp.forme); + } else { + preEvo = tp.pokemon; + } + while (!preEvo.evolutionsTo.isEmpty()) { + preEvo = preEvo.evolutionsTo.get(0).from; + moveSelectionPoolAtLevel.addAll(allLevelUpMoves.get(preEvo.number) + .stream() + .filter(ml -> ml.level <= tp.level) + .filter(ml -> this.random.nextDouble() < preEvoMoveProbability) + .map(ml -> moves.get(ml.move)) + .distinct() + .collect(Collectors.toList())); + } + } + + // TM Moves + boolean[] tmCompat = allTMCompat.get(getAltFormeOfPokemon(tp.pokemon, tp.forme)); + for (int tmMove: allTMMoves) { + if (tmCompat[allTMMoves.indexOf(tmMove) + 1]) { + Move thisMove = moves.get(tmMove); + if (thisMove.power > 1 && tp.level * 3 > thisMove.power * thisMove.hitCount && + this.random.nextDouble() < tmMoveProbability) { + moveSelectionPoolAtLevel.add(thisMove); + } else if ((thisMove.power <= 1 && this.random.nextInt(100) < tp.level) || + this.random.nextInt(200) < tp.level) { + moveSelectionPoolAtLevel.add(thisMove); + } + } + } + + // Move Tutor Moves + if (hasMoveTutors()) { + boolean[] tutorCompat = allTutorCompat.get(getAltFormeOfPokemon(tp.pokemon, tp.forme)); + for (int tutorMove: allTutorMoves) { + if (tutorCompat[allTutorMoves.indexOf(tutorMove) + 1]) { + Move thisMove = moves.get(tutorMove); + if (thisMove.power > 1 && tp.level * 3 > thisMove.power * thisMove.hitCount && + this.random.nextDouble() < tutorMoveProbability) { + moveSelectionPoolAtLevel.add(thisMove); + } else if ((thisMove.power <= 1 && this.random.nextInt(100) < tp.level) || + this.random.nextInt(200) < tp.level) { + moveSelectionPoolAtLevel.add(thisMove); + } + } + } + } + + // Egg Moves + if (!cyclicEvolutions) { + Pokemon firstEvo; + if (altFormesCanHaveDifferentEvolutions()) { + firstEvo = getAltFormeOfPokemon(tp.pokemon, tp.forme); + } else { + firstEvo = tp.pokemon; + } + while (!firstEvo.evolutionsTo.isEmpty()) { + firstEvo = firstEvo.evolutionsTo.get(0).from; + } + if (allEggMoves.get(firstEvo.number) != null) { + moveSelectionPoolAtLevel.addAll(allEggMoves.get(firstEvo.number) + .stream() + .filter(egm -> this.random.nextDouble() < eggMoveProbability) + .map(moves::get) + .collect(Collectors.toList())); + } + } + + + + return moveSelectionPoolAtLevel.stream().distinct().collect(Collectors.toList()); + } + + @Override + public void pickTrainerMovesets(Settings settings) { + boolean isCyclicEvolutions = settings.getEvolutionsMod() == Settings.EvolutionsMod.RANDOM_EVERY_LEVEL; + boolean doubleBattleMode = settings.isDoubleBattleMode(); + + List trainers = getTrainers(); + + for (Trainer t: trainers) { + t.setPokemonHaveCustomMoves(true); + + for (TrainerPokemon tp: t.pokemon) { + tp.resetMoves = false; + + List movesAtLevel = getMoveSelectionPoolAtLevel(tp, isCyclicEvolutions); + + movesAtLevel = trimMoveList(tp, movesAtLevel, doubleBattleMode); + + if (movesAtLevel.isEmpty()) { + continue; + } + + double trainerTypeModifier = 1; + if (t.isImportant()) { + trainerTypeModifier = 1.5; + } else if (t.isBoss()) { + trainerTypeModifier = 2; + } + double movePoolSizeModifier = movesAtLevel.size() / 10.0; + double bonusModifier = trainerTypeModifier * movePoolSizeModifier; + + double atkSpatkRatioModifier = 0.75; + double stabMoveBias = 0.25 * bonusModifier; + double hardAbilityMoveBias = 1 * bonusModifier; + double softAbilityMoveBias = 0.5 * bonusModifier; + double statBias = 0.5 * bonusModifier; + double softMoveBias = 0.25 * bonusModifier; + double hardMoveBias = 1 * bonusModifier; + double softMoveAntiBias = 0.5; + + // Add bias for STAB + + Pokemon pk = getAltFormeOfPokemon(tp.pokemon, tp.forme); + + List stabMoves = new ArrayList<>(movesAtLevel) + .stream() + .filter(mv -> mv.type == pk.primaryType && mv.category != MoveCategory.STATUS) + .collect(Collectors.toList()); + Collections.shuffle(stabMoves, this.random); + + for (int i = 0; i < stabMoveBias * stabMoves.size(); i++) { + int j = i % stabMoves.size(); + movesAtLevel.add(stabMoves.get(j)); + } + + if (pk.secondaryType != null) { + stabMoves = new ArrayList<>(movesAtLevel) + .stream() + .filter(mv -> mv.type == pk.secondaryType && mv.category != MoveCategory.STATUS) + .collect(Collectors.toList()); + Collections.shuffle(stabMoves, this.random); + + for (int i = 0; i < stabMoveBias * stabMoves.size(); i++) { + int j = i % stabMoves.size(); + movesAtLevel.add(stabMoves.get(j)); + } + } + + // Hard ability/move synergy + + List abilityMoveSynergyList = MoveSynergy.getHardAbilityMoveSynergy( + getAbilityForTrainerPokemon(tp), + pk.primaryType, + pk.secondaryType, + movesAtLevel, + generationOfPokemon(), + perfectAccuracy); + Collections.shuffle(abilityMoveSynergyList, this.random); + for (int i = 0; i < hardAbilityMoveBias * abilityMoveSynergyList.size(); i++) { + int j = i % abilityMoveSynergyList.size(); + movesAtLevel.add(abilityMoveSynergyList.get(j)); + } + + // Soft ability/move synergy + + List softAbilityMoveSynergyList = MoveSynergy.getSoftAbilityMoveSynergy( + getAbilityForTrainerPokemon(tp), + movesAtLevel, + pk.primaryType, + pk.secondaryType); + + Collections.shuffle(softAbilityMoveSynergyList, this.random); + for (int i = 0; i < softAbilityMoveBias * softAbilityMoveSynergyList.size(); i++) { + int j = i % softAbilityMoveSynergyList.size(); + movesAtLevel.add(softAbilityMoveSynergyList.get(j)); + } + + // Soft ability/move anti-synergy + + List softAbilityMoveAntiSynergyList = MoveSynergy.getSoftAbilityMoveAntiSynergy( + getAbilityForTrainerPokemon(tp), movesAtLevel); + List withoutSoftAntiSynergy = new ArrayList<>(movesAtLevel); + for (Move mv: softAbilityMoveAntiSynergyList) { + withoutSoftAntiSynergy.remove(mv); + } + if (withoutSoftAntiSynergy.size() > 0) { + movesAtLevel = withoutSoftAntiSynergy; + } + + List distinctMoveList = movesAtLevel.stream().distinct().collect(Collectors.toList()); + int movesLeft = distinctMoveList.size(); + + if (movesLeft <= 4) { + + for (int i = 0; i < 4; i++) { + if (i < movesLeft) { + tp.moves[i] = distinctMoveList.get(i).number; + } else { + tp.moves[i] = 0; + } + } + continue; + } + + // Stat/move synergy + + List statSynergyList = MoveSynergy.getStatMoveSynergy(pk, movesAtLevel); + Collections.shuffle(statSynergyList, this.random); + for (int i = 0; i < statBias * statSynergyList.size(); i++) { + int j = i % statSynergyList.size(); + movesAtLevel.add(statSynergyList.get(j)); + } + + // Stat/move anti-synergy + + List statAntiSynergyList = MoveSynergy.getStatMoveAntiSynergy(pk, movesAtLevel); + List withoutStatAntiSynergy = new ArrayList<>(movesAtLevel); + for (Move mv: statAntiSynergyList) { + withoutStatAntiSynergy.remove(mv); + } + if (withoutStatAntiSynergy.size() > 0) { + movesAtLevel = withoutStatAntiSynergy; + } + + distinctMoveList = movesAtLevel.stream().distinct().collect(Collectors.toList()); + movesLeft = distinctMoveList.size(); + + if (movesLeft <= 4) { + + for (int i = 0; i < 4; i++) { + if (i < movesLeft) { + tp.moves[i] = distinctMoveList.get(i).number; + } else { + tp.moves[i] = 0; + } + } + continue; + } + + // Add bias for atk/spatk ratio + + double atkSpatkRatio = (double)pk.attack / (double)pk.spatk; + switch(getAbilityForTrainerPokemon(tp)) { + case Abilities.hugePower: + case Abilities.purePower: + atkSpatkRatio *= 2; + break; + case Abilities.hustle: + case Abilities.gorillaTactics: + atkSpatkRatio *= 1.5; + break; + case Abilities.moxie: + atkSpatkRatio *= 1.1; + break; + case Abilities.soulHeart: + atkSpatkRatio *= 0.9; + break; + } + + List physicalMoves = new ArrayList<>(movesAtLevel) + .stream() + .filter(mv -> mv.category == MoveCategory.PHYSICAL) + .collect(Collectors.toList()); + List specialMoves = new ArrayList<>(movesAtLevel) + .stream() + .filter(mv -> mv.category == MoveCategory.SPECIAL) + .collect(Collectors.toList()); + + if (atkSpatkRatio < 1 && specialMoves.size() > 0) { + atkSpatkRatio = 1 / atkSpatkRatio; + double acceptedRatio = atkSpatkRatioModifier * atkSpatkRatio; + int additionalMoves = (int)(physicalMoves.size() * acceptedRatio) - specialMoves.size(); + for (int i = 0; i < additionalMoves; i++) { + Move mv = specialMoves.get(this.random.nextInt(specialMoves.size())); + movesAtLevel.add(mv); + } + } else if (physicalMoves.size() > 0) { + double acceptedRatio = atkSpatkRatioModifier * atkSpatkRatio; + int additionalMoves = (int)(specialMoves.size() * acceptedRatio) - physicalMoves.size(); + for (int i = 0; i < additionalMoves; i++) { + Move mv = physicalMoves.get(this.random.nextInt(physicalMoves.size())); + movesAtLevel.add(mv); + } + } + + // Pick moves + + List pickedMoves = new ArrayList<>(); + + for (int i = 1; i <= 4; i++) { + Move move; + List pickFrom; + + if (i == 1) { + pickFrom = movesAtLevel + .stream() + .filter(mv -> mv.isGoodDamaging(perfectAccuracy)) + .collect(Collectors.toList()); + if (pickFrom.isEmpty()) { + pickFrom = movesAtLevel; + } + } else { + pickFrom = movesAtLevel; + } + + if (i == 4) { + List requiresOtherMove = movesAtLevel + .stream() + .filter(mv -> GlobalConstants.requiresOtherMove.contains(mv.number)) + .distinct() + .collect(Collectors.toList()); + + for (Move dependentMove: requiresOtherMove) { + boolean hasRequiredMove = false; + for (Move requiredMove: MoveSynergy.requiresOtherMove(dependentMove, movesAtLevel)) { + if (pickedMoves.contains(requiredMove)) { + hasRequiredMove = true; + break; + } + } + if (!hasRequiredMove) { + movesAtLevel.removeAll(Collections.singletonList(dependentMove)); + } + } + } + + move = pickFrom.get(this.random.nextInt(pickFrom.size())); + pickedMoves.add(move); + + if (i == 4) { + break; + } + + movesAtLevel.removeAll(Collections.singletonList(move)); + + movesAtLevel.removeAll(MoveSynergy.getHardMoveAntiSynergy(move, movesAtLevel)); + + distinctMoveList = movesAtLevel.stream().distinct().collect(Collectors.toList()); + movesLeft = distinctMoveList.size(); + + if (movesLeft <= (4 - i)) { + pickedMoves.addAll(distinctMoveList); + break; + } + + List hardMoveSynergyList = MoveSynergy.getMoveSynergy( + move, + movesAtLevel, + generationOfPokemon()); + Collections.shuffle(hardMoveSynergyList, this.random); + for (int j = 0; j < hardMoveBias * hardMoveSynergyList.size(); j++) { + int k = j % hardMoveSynergyList.size(); + movesAtLevel.add(hardMoveSynergyList.get(k)); + } + + List softMoveSynergyList = MoveSynergy.getSoftMoveSynergy( + move, + movesAtLevel, + generationOfPokemon(), + isEffectivenessUpdated()); + Collections.shuffle(softMoveSynergyList, this.random); + for (int j = 0; j < softMoveBias * softMoveSynergyList.size(); j++) { + int k = j % softMoveSynergyList.size(); + movesAtLevel.add(softMoveSynergyList.get(k)); + } + + List softMoveAntiSynergyList = MoveSynergy.getSoftMoveAntiSynergy(move, movesAtLevel); + Collections.shuffle(softMoveAntiSynergyList, this.random); + for (int j = 0; j < softMoveAntiBias * softMoveAntiSynergyList.size(); j++) { + distinctMoveList = movesAtLevel.stream().distinct().collect(Collectors.toList()); + if (distinctMoveList.size() <= (4 - i)) { + break; + } + int k = j % softMoveAntiSynergyList.size(); + movesAtLevel.remove(softMoveAntiSynergyList.get(k)); + } + + distinctMoveList = movesAtLevel.stream().distinct().collect(Collectors.toList()); + movesLeft = distinctMoveList.size(); + + if (movesLeft <= (4 - i)) { + pickedMoves.addAll(distinctMoveList); + break; + } + } + + int movesPicked = pickedMoves.size(); + + for (int i = 0; i < 4; i++) { + if (i < movesPicked) { + tp.moves[i] = pickedMoves.get(i).number; + } else { + tp.moves[i] = 0; + } + } + } + } + setTrainers(trainers, false); + } + + private List trimMoveList(TrainerPokemon tp, List movesAtLevel, boolean doubleBattleMode) { + int movesLeft = movesAtLevel.size(); + + if (movesLeft <= 4) { + for (int i = 0; i < 4; i++) { + if (i < movesLeft) { + tp.moves[i] = movesAtLevel.get(i).number; + } else { + tp.moves[i] = 0; + } + } + return new ArrayList<>(); + } + + movesAtLevel = movesAtLevel + .stream() + .filter(mv -> !GlobalConstants.uselessMoves.contains(mv.number) && + (doubleBattleMode || !GlobalConstants.doubleBattleMoves.contains(mv.number))) + .collect(Collectors.toList()); + + movesLeft = movesAtLevel.size(); + + if (movesLeft <= 4) { + for (int i = 0; i < 4; i++) { + if (i < movesLeft) { + tp.moves[i] = movesAtLevel.get(i).number; + } else { + tp.moves[i] = 0; + } + } + return new ArrayList<>(); + } + + List obsoletedMoves = getObsoleteMoves(movesAtLevel); + + // Remove obsoleted moves + + movesAtLevel.removeAll(obsoletedMoves); + + movesLeft = movesAtLevel.size(); + + if (movesLeft <= 4) { + for (int i = 0; i < 4; i++) { + if (i < movesLeft) { + tp.moves[i] = movesAtLevel.get(i).number; + } else { + tp.moves[i] = 0; + } + } + return new ArrayList<>(); + } + + List requiresOtherMove = movesAtLevel + .stream() + .filter(mv -> GlobalConstants.requiresOtherMove.contains(mv.number)) + .collect(Collectors.toList()); + + for (Move dependentMove: requiresOtherMove) { + if (MoveSynergy.requiresOtherMove(dependentMove, movesAtLevel).isEmpty()) { + movesAtLevel.remove(dependentMove); + } + } + + movesLeft = movesAtLevel.size(); + + if (movesLeft <= 4) { + for (int i = 0; i < 4; i++) { + if (i < movesLeft) { + tp.moves[i] = movesAtLevel.get(i).number; + } else { + tp.moves[i] = 0; + } + } + return new ArrayList<>(); + } + + // Remove hard ability anti-synergy moves + + List withoutHardAntiSynergy = new ArrayList<>(movesAtLevel); + withoutHardAntiSynergy.removeAll(MoveSynergy.getHardAbilityMoveAntiSynergy( + getAbilityForTrainerPokemon(tp), + movesAtLevel)); + + if (withoutHardAntiSynergy.size() > 0) { + movesAtLevel = withoutHardAntiSynergy; + } + + movesLeft = movesAtLevel.size(); + + if (movesLeft <= 4) { + for (int i = 0; i < 4; i++) { + if (i < movesLeft) { + tp.moves[i] = movesAtLevel.get(i).number; + } else { + tp.moves[i] = 0; + } + } + return new ArrayList<>(); + } + return movesAtLevel; + } + + private List getObsoleteMoves(List movesAtLevel) { + List obsoletedMoves = new ArrayList<>(); + for (Move mv: movesAtLevel) { + if (GlobalConstants.cannotObsoleteMoves.contains(mv.number)) { + continue; + } + if (mv.power > 0) { + List obsoleteThis = movesAtLevel + .stream() + .filter(mv2 -> !GlobalConstants.cannotBeObsoletedMoves.contains(mv2.number) && + mv.type == mv2.type && + ((((mv.statChangeMoveType == mv2.statChangeMoveType && + mv.statChanges[0].equals(mv2.statChanges[0])) || + (mv2.statChangeMoveType == StatChangeMoveType.NONE_OR_UNKNOWN && + mv.hasBeneficialStatChange())) && + mv.absorbPercent >= mv2.absorbPercent && + !mv.isChargeMove && + !mv.isRechargeMove) || + mv2.power * mv2.hitCount <= 30) && + mv.hitratio >= mv2.hitratio && + mv.category == mv2.category && + mv.priority >= mv2.priority && + mv2.power > 0 && + mv.power * mv.hitCount > mv2.power * mv2.hitCount) + .collect(Collectors.toList()); + for (Move obsoleted: obsoleteThis) { + //System.out.println(obsoleted.name + " obsoleted by " + mv.name); + } + obsoletedMoves.addAll(obsoleteThis); + } else if (mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_USER || + mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_TARGET) { + List obsoleteThis = new ArrayList<>(); + List statChanges1 = new ArrayList<>(); + for (Move.StatChange sc: mv.statChanges) { + if (sc.type != StatChangeType.NONE) { + statChanges1.add(sc); + } + } + for (Move mv2: movesAtLevel + .stream() + .filter(otherMv -> !otherMv.equals(mv) && + otherMv.power <= 0 && + otherMv.statChangeMoveType == mv.statChangeMoveType && + (otherMv.statusType == mv.statusType || + otherMv.statusType == StatusType.NONE)) + .collect(Collectors.toList())) { + List statChanges2 = new ArrayList<>(); + for (Move.StatChange sc: mv2.statChanges) { + if (sc.type != StatChangeType.NONE) { + statChanges2.add(sc); + } + } + if (statChanges2.size() > statChanges1.size()) { + continue; + } + List statChanges1Filtered = statChanges1 + .stream() + .filter(sc -> !statChanges2.contains(sc)) + .collect(Collectors.toList()); + statChanges2.removeAll(statChanges1); + if (!statChanges1Filtered.isEmpty() && statChanges2.isEmpty()) { + if (!GlobalConstants.cannotBeObsoletedMoves.contains(mv2.number)) { + obsoleteThis.add(mv2); + } + continue; + } + if (statChanges1Filtered.isEmpty() && statChanges2.isEmpty()) { + continue; + } + boolean maybeBetter = false; + for (Move.StatChange sc1: statChanges1Filtered) { + boolean canStillBeBetter = false; + for (Move.StatChange sc2: statChanges2) { + if (sc1.type == sc2.type) { + canStillBeBetter = true; + if ((mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_USER && sc1.stages > sc2.stages) || + (mv.statChangeMoveType == StatChangeMoveType.NO_DAMAGE_TARGET && sc1.stages < sc2.stages)) { + maybeBetter = true; + } else { + canStillBeBetter = false; + } + } + } + if (!canStillBeBetter) { + maybeBetter = false; + break; + } + } + if (maybeBetter) { + if (!GlobalConstants.cannotBeObsoletedMoves.contains(mv2.number)) { + obsoleteThis.add(mv2); + } + } + } + for (Move obsoleted: obsoleteThis) { + //System.out.println(obsoleted.name + " obsoleted by " + mv.name); + } + obsoletedMoves.addAll(obsoleteThis); + } + } + + return obsoletedMoves.stream().distinct().collect(Collectors.toList()); + } + + private boolean trainerShouldNotGetBuffs(Trainer t) { + return t.tag != null && (t.tag.startsWith("RIVAL1-") || t.tag.startsWith("FRIEND1-") || t.tag.endsWith("NOTSTRONG")); + } + + public int getRandomAbilitySlot(Pokemon pokemon) { + if (abilitiesPerPokemon() == 0) { + return 0; + } + List abilitiesList = Arrays.asList(pokemon.ability1, pokemon.ability2, pokemon.ability3); + int slot = random.nextInt(this.abilitiesPerPokemon()); + while (abilitiesList.get(slot) == 0) { + slot = random.nextInt(this.abilitiesPerPokemon()); + } + return slot + 1; + } + + public int getValidAbilitySlotFromOriginal(Pokemon pokemon, int originalAbilitySlot) { + // This is used in cases where one Trainer Pokemon evolves into another. If the unevolved Pokemon + // is using slot 2, but the evolved Pokemon doesn't actually have a second ability, then we + // want the evolved Pokemon to use slot 1 for safety's sake. + if (originalAbilitySlot == 2 && pokemon.ability2 == 0) { + return 1; + } + return originalAbilitySlot; + } + + // MOVE DATA + // All randomizers don't touch move ID 165 (Struggle) + // They also have other exclusions where necessary to stop things glitching. + + @Override + public void randomizeMovePowers() { + List moves = this.getMoves(); + for (Move mv : moves) { + if (mv != null && mv.internalId != Moves.struggle && mv.power >= 10) { + // "Generic" damaging move to randomize power + if (random.nextInt(3) != 2) { + // "Regular" move + mv.power = random.nextInt(11) * 5 + 50; // 50 ... 100 + } else { + // "Extreme" move + mv.power = random.nextInt(27) * 5 + 20; // 20 ... 150 + } + // Tiny chance for massive power jumps + for (int i = 0; i < 2; i++) { + if (random.nextInt(100) == 0) { + mv.power += 50; + } + } + + if (mv.hitCount != 1) { + // Divide randomized power by average hit count, round to + // nearest 5 + mv.power = (int) (Math.round(mv.power / mv.hitCount / 5) * 5); + if (mv.power == 0) { + mv.power = 5; + } + } + } + } + } + + @Override + public void randomizeMovePPs() { + List moves = this.getMoves(); + for (Move mv : moves) { + if (mv != null && mv.internalId != Moves.struggle) { + if (random.nextInt(3) != 2) { + // "average" PP: 15-25 + mv.pp = random.nextInt(3) * 5 + 15; + } else { + // "extreme" PP: 5-40 + mv.pp = random.nextInt(8) * 5 + 5; + } + } + } + } + + @Override + public void randomizeMoveAccuracies() { + List moves = this.getMoves(); + for (Move mv : moves) { + if (mv != null && mv.internalId != Moves.struggle && mv.hitratio >= 5) { + // "Sane" accuracy randomization + // Broken into three tiers based on original accuracy + // Designed to limit the chances of 100% accurate OHKO moves and + // keep a decent base of 100% accurate regular moves. + + if (mv.hitratio <= 50) { + // lowest tier (acc <= 50) + // new accuracy = rand(20...50) inclusive + // with a 10% chance to increase by 50% + mv.hitratio = random.nextInt(7) * 5 + 20; + if (random.nextInt(10) == 0) { + mv.hitratio = (mv.hitratio * 3 / 2) / 5 * 5; + } + } else if (mv.hitratio < 90) { + // middle tier (50 < acc < 90) + // count down from 100% to 20% in 5% increments with 20% + // chance to "stop" and use the current accuracy at each + // increment + // gives decent-but-not-100% accuracy most of the time + mv.hitratio = 100; + while (mv.hitratio > 20) { + if (random.nextInt(10) < 2) { + break; + } + mv.hitratio -= 5; + } + } else { + // highest tier (90 <= acc <= 100) + // count down from 100% to 20% in 5% increments with 40% + // chance to "stop" and use the current accuracy at each + // increment + // gives high accuracy most of the time + mv.hitratio = 100; + while (mv.hitratio > 20) { + if (random.nextInt(10) < 4) { + break; + } + mv.hitratio -= 5; + } + } + } + } + } + + @Override + public void randomizeMoveTypes() { + List moves = this.getMoves(); + for (Move mv : moves) { + if (mv != null && mv.internalId != Moves.struggle && mv.type != null) { + mv.type = randomType(); + } + } + } + + @Override + public void randomizeMoveCategory() { + if (!this.hasPhysicalSpecialSplit()) { + return; + } + List moves = this.getMoves(); + for (Move mv : moves) { + if (mv != null && mv.internalId != Moves.struggle && mv.category != MoveCategory.STATUS) { + if (random.nextInt(2) == 0) { + mv.category = (mv.category == MoveCategory.PHYSICAL) ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL; + } + } + } + + } + + @Override + public void updateMoves(Settings settings) { + int generation = settings.getUpdateMovesToGeneration(); + + List moves = this.getMoves(); + + if (generation >= 2 && generationOfPokemon() < 2) { + // gen1 + // Karate Chop => FIGHTING (gen1) + updateMoveType(moves, Moves.karateChop, Type.FIGHTING); + // Gust => FLYING (gen1) + updateMoveType(moves, Moves.gust, Type.FLYING); + // Wing Attack => 60 power (gen1) + updateMovePower(moves, Moves.wingAttack, 60); + // Whirlwind => 100 accuracy (gen1) + updateMoveAccuracy(moves, Moves.whirlwind, 100); + // Sand Attack => GROUND (gen1) + updateMoveType(moves, Moves.sandAttack, Type.GROUND); + // Double-Edge => 120 power (gen1) + updateMovePower(moves, Moves.doubleEdge, 120); + // Move 44, Bite, becomes dark (but doesn't exist anyway) + // Blizzard => 70% accuracy (gen1) + updateMoveAccuracy(moves, Moves.blizzard, 70); + // Rock Throw => 90% accuracy (gen1) + updateMoveAccuracy(moves, Moves.rockThrow, 90); + // Hypnosis => 60% accuracy (gen1) + updateMoveAccuracy(moves, Moves.hypnosis, 60); + // SelfDestruct => 200power (gen1) + updateMovePower(moves, Moves.selfDestruct, 200); + // Explosion => 250 power (gen1) + updateMovePower(moves, Moves.explosion, 250); + // Dig => 60 power (gen1) + updateMovePower(moves, Moves.dig, 60); + } + + if (generation >= 3 && generationOfPokemon() < 3) { + // Razor Wind => 100% accuracy (gen1/2) + updateMoveAccuracy(moves, Moves.razorWind, 100); + // Move 67, Low Kick, has weight-based power in gen3+ + // Low Kick => 100% accuracy (gen1) + updateMoveAccuracy(moves, Moves.lowKick, 100); + } + + if (generation >= 4 && generationOfPokemon() < 4) { + // Fly => 90 power (gen1/2/3) + updateMovePower(moves, Moves.fly, 90); + // Vine Whip => 15 pp (gen1/2/3) + updateMovePP(moves, Moves.vineWhip, 15); + // Absorb => 25pp (gen1/2/3) + updateMovePP(moves, Moves.absorb, 25); + // Mega Drain => 15pp (gen1/2/3) + updateMovePP(moves, Moves.megaDrain, 15); + // Dig => 80 power (gen1/2/3) + updateMovePower(moves, Moves.dig, 80); + // Recover => 10pp (gen1/2/3) + updateMovePP(moves, Moves.recover, 10); + // Flash => 100% acc (gen1/2/3) + updateMoveAccuracy(moves, Moves.flash, 100); + // Petal Dance => 90 power (gen1/2/3) + updateMovePower(moves, Moves.petalDance, 90); + // Disable => 100% accuracy (gen1-4) + updateMoveAccuracy(moves, Moves.disable, 80); + // Jump Kick => 85 power + updateMovePower(moves, Moves.jumpKick, 85); + // Hi Jump Kick => 100 power + updateMovePower(moves, Moves.highJumpKick, 100); + + if (generationOfPokemon() >= 2) { + // Zap Cannon => 120 power (gen2-3) + updateMovePower(moves, Moves.zapCannon, 120); + // Outrage => 120 power (gen2-3) + updateMovePower(moves, Moves.outrage, 120); + updateMovePP(moves, Moves.outrage, 10); + // Giga Drain => 10pp (gen2-3) + updateMovePP(moves, Moves.gigaDrain, 10); + // Rock Smash => 40 power (gen2-3) + updateMovePower(moves, Moves.rockSmash, 40); + } + + if (generationOfPokemon() == 3) { + // Stockpile => 20 pp + updateMovePP(moves, Moves.stockpile, 20); + // Dive => 80 power + updateMovePower(moves, Moves.dive, 80); + // Leaf Blade => 90 power + updateMovePower(moves, Moves.leafBlade, 90); + } + } + + if (generation >= 5 && generationOfPokemon() < 5) { + // Bind => 85% accuracy (gen1-4) + updateMoveAccuracy(moves, Moves.bind, 85); + // Jump Kick => 10 pp, 100 power (gen1-4) + updateMovePP(moves, Moves.jumpKick, 10); + updateMovePower(moves, Moves.jumpKick, 100); + // Tackle => 50 power, 100% accuracy , gen1-4 + updateMovePower(moves, Moves.tackle, 50); + updateMoveAccuracy(moves, Moves.tackle, 100); + // Wrap => 90% accuracy (gen1-4) + updateMoveAccuracy(moves, Moves.wrap, 90); + // Thrash => 120 power, 10pp (gen1-4) + updateMovePP(moves, Moves.thrash, 10); + updateMovePower(moves, Moves.thrash, 120); + // Disable => 100% accuracy (gen1-4) + updateMoveAccuracy(moves, Moves.disable, 100); + // Petal Dance => 120power, 10pp (gen1-4) + updateMovePP(moves, Moves.petalDance, 10); + updateMovePower(moves, Moves.petalDance, 120); + // Fire Spin => 35 power, 85% acc (gen1-4) + updateMoveAccuracy(moves, Moves.fireSpin, 85); + updateMovePower(moves, Moves.fireSpin, 35); + // Toxic => 90% accuracy (gen1-4) + updateMoveAccuracy(moves, Moves.toxic, 90); + // Clamp => 15pp, 85% acc (gen1-4) + updateMoveAccuracy(moves, Moves.clamp, 85); + updateMovePP(moves, Moves.clamp, 15); + // HJKick => 130 power, 10pp (gen1-4) + updateMovePP(moves, Moves.highJumpKick, 10); + updateMovePower(moves, Moves.highJumpKick, 130); + // Glare => 90% acc (gen1-4) + updateMoveAccuracy(moves, Moves.glare, 90); + // Poison Gas => 80% acc (gen1-4) + updateMoveAccuracy(moves, Moves.poisonGas, 80); + // Crabhammer => 90% acc (gen1-4) + updateMoveAccuracy(moves, Moves.crabhammer, 90); + + if (generationOfPokemon() >= 2) { + // Curse => GHOST (gen2-4) + updateMoveType(moves, Moves.curse, Type.GHOST); + // Cotton Spore => 100% acc (gen2-4) + updateMoveAccuracy(moves, Moves.cottonSpore, 100); + // Scary Face => 100% acc (gen2-4) + updateMoveAccuracy(moves, Moves.scaryFace, 100); + // Bone Rush => 90% acc (gen2-4) + updateMoveAccuracy(moves, Moves.boneRush, 90); + // Giga Drain => 75 power (gen2-4) + updateMovePower(moves, Moves.gigaDrain, 75); + // Fury Cutter => 20 power (gen2-4) + updateMovePower(moves, Moves.furyCutter, 20); + // Future Sight => 10 pp, 100 power, 100% acc (gen2-4) + updateMovePP(moves, Moves.futureSight, 10); + updateMovePower(moves, Moves.futureSight, 100); + updateMoveAccuracy(moves, Moves.futureSight, 100); + // Whirlpool => 35 pow, 85% acc (gen2-4) + updateMovePower(moves, Moves.whirlpool, 35); + updateMoveAccuracy(moves, Moves.whirlpool, 85); + } + + if (generationOfPokemon() >= 3) { + // Uproar => 90 power (gen3-4) + updateMovePower(moves, Moves.uproar, 90); + // Sand Tomb => 35 pow, 85% acc (gen3-4) + updateMovePower(moves, Moves.sandTomb, 35); + updateMoveAccuracy(moves, Moves.sandTomb, 85); + // Bullet Seed => 25 power (gen3-4) + updateMovePower(moves, Moves.bulletSeed, 25); + // Icicle Spear => 25 power (gen3-4) + updateMovePower(moves, Moves.icicleSpear, 25); + // Covet => 60 power (gen3-4) + updateMovePower(moves, Moves.covet, 60); + // Rock Blast => 90% acc (gen3-4) + updateMoveAccuracy(moves, Moves.rockBlast, 90); + // Doom Desire => 140 pow, 100% acc, gen3-4 + updateMovePower(moves, Moves.doomDesire, 140); + updateMoveAccuracy(moves, Moves.doomDesire, 100); + } + + if (generationOfPokemon() == 4) { + // Feint => 30 pow + updateMovePower(moves, Moves.feint, 30); + // Last Resort => 140 pow + updateMovePower(moves, Moves.lastResort, 140); + // Drain Punch => 10 pp, 75 pow + updateMovePP(moves, Moves.drainPunch, 10); + updateMovePower(moves, Moves.drainPunch, 75); + // Magma Storm => 75% acc + updateMoveAccuracy(moves, Moves.magmaStorm, 75); + } + } + + if (generation >= 6 && generationOfPokemon() < 6) { + // gen 1 + // Swords Dance 20 PP + updateMovePP(moves, Moves.swordsDance, 20); + // Whirlwind can't miss + updateMoveAccuracy(moves, Moves.whirlwind, perfectAccuracy); + // Vine Whip 25 PP, 45 Power + updateMovePP(moves, Moves.vineWhip, 25); + updateMovePower(moves, Moves.vineWhip, 45); + // Pin Missile 25 Power, 95% Accuracy + updateMovePower(moves, Moves.pinMissile, 25); + updateMoveAccuracy(moves, Moves.pinMissile, 95); + // Flamethrower 90 Power + updateMovePower(moves, Moves.flamethrower, 90); + // Hydro Pump 110 Power + updateMovePower(moves, Moves.hydroPump, 110); + // Surf 90 Power + updateMovePower(moves, Moves.surf, 90); + // Ice Beam 90 Power + updateMovePower(moves, Moves.iceBeam, 90); + // Blizzard 110 Power + updateMovePower(moves, Moves.blizzard, 110); + // Growth 20 PP + updateMovePP(moves, Moves.growth, 20); + // Thunderbolt 90 Power + updateMovePower(moves, Moves.thunderbolt, 90); + // Thunder 110 Power + updateMovePower(moves, Moves.thunder, 110); + // Minimize 10 PP + updateMovePP(moves, Moves.minimize, 10); + // Barrier 20 PP + updateMovePP(moves, Moves.barrier, 20); + // Lick 30 Power + updateMovePower(moves, Moves.lick, 30); + // Smog 30 Power + updateMovePower(moves, Moves.smog, 30); + // Fire Blast 110 Power + updateMovePower(moves, Moves.fireBlast, 110); + // Skull Bash 10 PP, 130 Power + updateMovePP(moves, Moves.skullBash, 10); + updateMovePower(moves, Moves.skullBash, 130); + // Glare 100% Accuracy + updateMoveAccuracy(moves, Moves.glare, 100); + // Poison Gas 90% Accuracy + updateMoveAccuracy(moves, Moves.poisonGas, 90); + // Bubble 40 Power + updateMovePower(moves, Moves.bubble, 40); + // Psywave 100% Accuracy + updateMoveAccuracy(moves, Moves.psywave, 100); + // Acid Armor 20 PP + updateMovePP(moves, Moves.acidArmor, 20); + // Crabhammer 100 Power + updateMovePower(moves, Moves.crabhammer, 100); + + if (generationOfPokemon() >= 2) { + // Thief 25 PP, 60 Power + updateMovePP(moves, Moves.thief, 25); + updateMovePower(moves, Moves.thief, 60); + // Snore 50 Power + updateMovePower(moves, Moves.snore, 50); + // Fury Cutter 40 Power + updateMovePower(moves, Moves.furyCutter, 40); + // Future Sight 120 Power + updateMovePower(moves, Moves.futureSight, 120); + } + + if (generationOfPokemon() >= 3) { + // Heat Wave 95 Power + updateMovePower(moves, Moves.heatWave, 95); + // Will-o-Wisp 85% Accuracy + updateMoveAccuracy(moves, Moves.willOWisp, 85); + // Smellingsalt 70 Power + updateMovePower(moves, Moves.smellingSalts, 70); + // Knock off 65 Power + updateMovePower(moves, Moves.knockOff, 65); + // Meteor Mash 90 Power, 90% Accuracy + updateMovePower(moves, Moves.meteorMash, 90); + updateMoveAccuracy(moves, Moves.meteorMash, 90); + // Air Cutter 60 Power + updateMovePower(moves, Moves.airCutter, 60); + // Overheat 130 Power + updateMovePower(moves, Moves.overheat, 130); + // Rock Tomb 15 PP, 60 Power, 95% Accuracy + updateMovePP(moves, Moves.rockTomb, 15); + updateMovePower(moves, Moves.rockTomb, 60); + updateMoveAccuracy(moves, Moves.rockTomb, 95); + // Extrasensory 20 PP + updateMovePP(moves, Moves.extrasensory, 20); + // Muddy Water 90 Power + updateMovePower(moves, Moves.muddyWater, 90); + // Covet 25 PP + updateMovePP(moves, Moves.covet, 25); + } + + if (generationOfPokemon() >= 4) { + // Wake-Up Slap 70 Power + updateMovePower(moves, Moves.wakeUpSlap, 70); + // Tailwind 15 PP + updateMovePP(moves, Moves.tailwind, 15); + // Assurance 60 Power + updateMovePower(moves, Moves.assurance, 60); + // Psycho Shift 100% Accuracy + updateMoveAccuracy(moves, Moves.psychoShift, 100); + // Aura Sphere 80 Power + updateMovePower(moves, Moves.auraSphere, 80); + // Air Slash 15 PP + updateMovePP(moves, Moves.airSlash, 15); + // Dragon Pulse 85 Power + updateMovePower(moves, Moves.dragonPulse, 85); + // Power Gem 80 Power + updateMovePower(moves, Moves.powerGem, 80); + // Energy Ball 90 Power + updateMovePower(moves, Moves.energyBall, 90); + // Draco Meteor 130 Power + updateMovePower(moves, Moves.dracoMeteor, 130); + // Leaf Storm 130 Power + updateMovePower(moves, Moves.leafStorm, 130); + // Gunk Shot 80% Accuracy + updateMoveAccuracy(moves, Moves.gunkShot, 80); + // Chatter 65 Power + updateMovePower(moves, Moves.chatter, 65); + // Magma Storm 100 Power + updateMovePower(moves, Moves.magmaStorm, 100); + } + + if (generationOfPokemon() == 5) { + // Synchronoise 120 Power + updateMovePower(moves, Moves.synchronoise, 120); + // Low Sweep 65 Power + updateMovePower(moves, Moves.lowSweep, 65); + // Hex 65 Power + updateMovePower(moves, Moves.hex, 65); + // Incinerate 60 Power + updateMovePower(moves, Moves.incinerate, 60); + // Pledges 80 Power + updateMovePower(moves, Moves.waterPledge, 80); + updateMovePower(moves, Moves.firePledge, 80); + updateMovePower(moves, Moves.grassPledge, 80); + // Struggle Bug 50 Power + updateMovePower(moves, Moves.struggleBug, 50); + // Frost Breath and Storm Throw 45 Power + // Crits are 2x in these games, so we need to multiply BP by 3/4 + // Storm Throw was also updated to have a base BP of 60 + updateMovePower(moves, Moves.frostBreath, 45); + updateMovePower(moves, Moves.stormThrow, 45); + // Sacred Sword 15 PP + updateMovePP(moves, Moves.sacredSword, 15); + // Hurricane 110 Power + updateMovePower(moves, Moves.hurricane, 110); + // Techno Blast 120 Power + updateMovePower(moves, Moves.technoBlast, 120); + } + } + + if (generation >= 7 && generationOfPokemon() < 7) { + // Leech Life 80 Power, 10 PP + updateMovePower(moves, Moves.leechLife, 80); + updateMovePP(moves, Moves.leechLife, 10); + // Submission 20 PP + updateMovePP(moves, Moves.submission, 20); + // Tackle 40 Power + updateMovePower(moves, Moves.tackle, 40); + // Thunder Wave 90% Accuracy + updateMoveAccuracy(moves, Moves.thunderWave, 90); + + if (generationOfPokemon() >= 2) { + // Swagger 85% Accuracy + updateMoveAccuracy(moves, Moves.swagger, 85); + } + + if (generationOfPokemon() >= 3) { + // Knock Off 20 PP + updateMovePP(moves, Moves.knockOff, 20); + } + + if (generationOfPokemon() >= 4) { + // Dark Void 50% Accuracy + updateMoveAccuracy(moves, Moves.darkVoid, 50); + // Sucker Punch 70 Power + updateMovePower(moves, Moves.suckerPunch, 70); + } + + if (generationOfPokemon() == 6) { + // Aromatic Mist can't miss + updateMoveAccuracy(moves, Moves.aromaticMist, perfectAccuracy); + // Fell Stinger 50 Power + updateMovePower(moves, Moves.fellStinger, 50); + // Flying Press 100 Power + updateMovePower(moves, Moves.flyingPress, 100); + // Mat Block 10 PP + updateMovePP(moves, Moves.matBlock, 10); + // Mystical Fire 75 Power + updateMovePower(moves, Moves.mysticalFire, 75); + // Parabolic Charge 65 Power + updateMovePower(moves, Moves.parabolicCharge, 65); + // Topsy-Turvy can't miss + updateMoveAccuracy(moves, Moves.topsyTurvy, perfectAccuracy); + // Water Shuriken Special + updateMoveCategory(moves, Moves.waterShuriken, MoveCategory.SPECIAL); + } + } + + if (generation >= 8 && generationOfPokemon() < 8) { + if (generationOfPokemon() >= 2) { + // Rapid Spin 50 Power + updateMovePower(moves, Moves.rapidSpin, 50); + } + + if (generationOfPokemon() == 7) { + // Multi-Attack 120 Power + updateMovePower(moves, Moves.multiAttack, 120); + } + } + + if (generation >= 9 && generationOfPokemon() < 9) { + // Gen 1 + // Recover 5 PP + updateMovePP(moves, Moves.recover, 5); + // Soft-Boiled 5 PP + updateMovePP(moves, Moves.softBoiled, 5); + // Rest 5 PP + updateMovePP(moves, Moves.rest, 5); + + if (generationOfPokemon() >= 2) { + // Milk Drink 5 PP + updateMovePP(moves, Moves.milkDrink, 5); + } + + if (generationOfPokemon() >= 3) { + // Slack Off 5 PP + updateMovePP(moves, Moves.slackOff, 5); + } + + if (generationOfPokemon() >= 4) { + // Roost 5 PP + updateMovePP(moves, Moves.roost, 5); + } + + if (generationOfPokemon() >= 7) { + // Shore Up 5 PP + updateMovePP(moves, Moves.shoreUp, 5); + } + + if (generationOfPokemon() >= 8) { + // Grassy Glide 60 Power + updateMovePower(moves, Moves.grassyGlide, 60); + // Wicked Blow 75 Power + updateMovePower(moves, Moves.wickedBlow, 75); + // Glacial Lance 120 Power + updateMovePower(moves, Moves.glacialLance, 120); + } + } + } + + private Map moveUpdates; + + @Override + public void initMoveUpdates() { + moveUpdates = new TreeMap<>(); + } + + @Override + public Map getMoveUpdates() { + return moveUpdates; + } + + @Override + public void randomizeMovesLearnt(Settings settings) { + boolean typeThemed = settings.getMovesetsMod() == Settings.MovesetsMod.RANDOM_PREFER_SAME_TYPE; + boolean noBroken = settings.isBlockBrokenMovesetMoves(); + boolean forceStartingMoves = supportsFourStartingMoves() && settings.isStartWithGuaranteedMoves(); + int forceStartingMoveCount = settings.getGuaranteedMoveCount(); + double goodDamagingPercentage = + settings.isMovesetsForceGoodDamaging() ? settings.getMovesetsGoodDamagingPercent() / 100.0 : 0; + boolean evolutionMovesForAll = settings.isEvolutionMovesForAll(); + + // Get current sets + Map> movesets = this.getMovesLearnt(); + + // Build sets of moves + List validMoves = new ArrayList<>(); + List validDamagingMoves = new ArrayList<>(); + Map> validTypeMoves = new HashMap<>(); + Map> validTypeDamagingMoves = new HashMap<>(); + createSetsOfMoves(noBroken, validMoves, validDamagingMoves, validTypeMoves, validTypeDamagingMoves); + + for (Integer pkmnNum : movesets.keySet()) { + List learnt = new ArrayList<>(); + List moves = movesets.get(pkmnNum); + int lv1AttackingMove = 0; + Pokemon pkmn = findPokemonInPoolWithSpeciesID(mainPokemonListInclFormes, pkmnNum); + if (pkmn == null) { + continue; + } + + double atkSpAtkRatio = pkmn.getAttackSpecialAttackRatio(); + + // 4 starting moves? + if (forceStartingMoves) { + int lv1count = 0; + for (MoveLearnt ml : moves) { + if (ml.level == 1) { + lv1count++; + } + } + if (lv1count < forceStartingMoveCount) { + for (int i = 0; i < forceStartingMoveCount - lv1count; i++) { + MoveLearnt fakeLv1 = new MoveLearnt(); + fakeLv1.level = 1; + fakeLv1.move = 0; + moves.add(0, fakeLv1); + } + } + } + + if (evolutionMovesForAll) { + if (moves.get(0).level != 0) { + MoveLearnt fakeEvoMove = new MoveLearnt(); + fakeEvoMove.level = 0; + fakeEvoMove.move = 0; + moves.add(0, fakeEvoMove); + } + } + + if (pkmn.actuallyCosmetic) { + for (int i = 0; i < moves.size(); i++) { + moves.get(i).move = movesets.get(pkmn.baseForme.number).get(i).move; + } + continue; + } + + // Find last lv1 move + // lv1index ends up as the index of the first non-lv1 move + int lv1index = moves.get(0).level == 1 ? 0 : 1; // Evolution move handling (level 0 = evo move) + while (lv1index < moves.size() && moves.get(lv1index).level == 1) { + lv1index++; + } + + // last lv1 move is 1 before lv1index + if (lv1index != 0) { + lv1index--; + } + + // Force a certain amount of good damaging moves depending on the percentage + int goodDamagingLeft = (int)Math.round(goodDamagingPercentage * moves.size()); + + // Replace moves as needed + for (int i = 0; i < moves.size(); i++) { + // should this move be forced damaging? + boolean attemptDamaging = i == lv1index || goodDamagingLeft > 0; + + // type themed? + Type typeOfMove = null; + if (typeThemed) { + double picked = random.nextDouble(); + if ((pkmn.primaryType == Type.NORMAL && pkmn.secondaryType != null) || + (pkmn.secondaryType == Type.NORMAL)) { + + Type otherType = pkmn.primaryType == Type.NORMAL ? pkmn.secondaryType : pkmn.primaryType; + + // Normal/OTHER: 10% normal, 30% other, 60% random + if (picked < 0.1) { + typeOfMove = Type.NORMAL; + } else if (picked < 0.4) { + typeOfMove = otherType; + } + // else random + } else if (pkmn.secondaryType != null) { + // Primary/Secondary: 20% primary, 20% secondary, 60% random + if (picked < 0.2) { + typeOfMove = pkmn.primaryType; + } else if (picked < 0.4) { + typeOfMove = pkmn.secondaryType; + } + // else random + } else { + // Primary/None: 40% primary, 60% random + if (picked < 0.4) { + typeOfMove = pkmn.primaryType; + } + // else random + } + } + + // select a list to pick a move from that has at least one free + List pickList = validMoves; + if (attemptDamaging) { + if (typeOfMove != null) { + if (validTypeDamagingMoves.containsKey(typeOfMove) + && checkForUnusedMove(validTypeDamagingMoves.get(typeOfMove), learnt)) { + pickList = validTypeDamagingMoves.get(typeOfMove); + } else if (checkForUnusedMove(validDamagingMoves, learnt)) { + pickList = validDamagingMoves; + } + } else if (checkForUnusedMove(validDamagingMoves, learnt)) { + pickList = validDamagingMoves; + } + MoveCategory forcedCategory = random.nextDouble() < atkSpAtkRatio ? MoveCategory.PHYSICAL : MoveCategory.SPECIAL; + List filteredList = pickList.stream().filter(mv -> mv.category == forcedCategory).collect(Collectors.toList()); + if (!filteredList.isEmpty() && checkForUnusedMove(filteredList, learnt)) { + pickList = filteredList; + } + } else if (typeOfMove != null) { + if (validTypeMoves.containsKey(typeOfMove) + && checkForUnusedMove(validTypeMoves.get(typeOfMove), learnt)) { + pickList = validTypeMoves.get(typeOfMove); + } + } + + // now pick a move until we get a valid one + Move mv = pickList.get(random.nextInt(pickList.size())); + while (learnt.contains(mv.number)) { + mv = pickList.get(random.nextInt(pickList.size())); + } + + if (i == lv1index) { + lv1AttackingMove = mv.number; + } else { + goodDamagingLeft--; + } + learnt.add(mv.number); + + } + + Collections.shuffle(learnt, random); + if (learnt.get(lv1index) != lv1AttackingMove) { + for (int i = 0; i < learnt.size(); i++) { + if (learnt.get(i) == lv1AttackingMove) { + learnt.set(i, learnt.get(lv1index)); + learnt.set(lv1index, lv1AttackingMove); + break; + } + } + } + + // write all moves for the pokemon + for (int i = 0; i < learnt.size(); i++) { + moves.get(i).move = learnt.get(i); + if (i == lv1index) { + // just in case, set this to lv1 + moves.get(i).level = 1; + } + } + } + // Done, save + this.setMovesLearnt(movesets); + + } + + @Override + public void randomizeEggMoves(Settings settings) { + boolean typeThemed = settings.getMovesetsMod() == Settings.MovesetsMod.RANDOM_PREFER_SAME_TYPE; + boolean noBroken = settings.isBlockBrokenMovesetMoves(); + double goodDamagingPercentage = + settings.isMovesetsForceGoodDamaging() ? settings.getMovesetsGoodDamagingPercent() / 100.0 : 0; + + // Get current sets + Map> movesets = this.getEggMoves(); + + // Build sets of moves + List validMoves = new ArrayList<>(); + List validDamagingMoves = new ArrayList<>(); + Map> validTypeMoves = new HashMap<>(); + Map> validTypeDamagingMoves = new HashMap<>(); + createSetsOfMoves(noBroken, validMoves, validDamagingMoves, validTypeMoves, validTypeDamagingMoves); + + for (Integer pkmnNum : movesets.keySet()) { + List learnt = new ArrayList<>(); + List moves = movesets.get(pkmnNum); + Pokemon pkmn = findPokemonInPoolWithSpeciesID(mainPokemonListInclFormes, pkmnNum); + if (pkmn == null) { + continue; + } + + double atkSpAtkRatio = pkmn.getAttackSpecialAttackRatio(); + + if (pkmn.actuallyCosmetic) { + for (int i = 0; i < moves.size(); i++) { + moves.set(i, movesets.get(pkmn.baseForme.number).get(i)); + } + continue; + } + + // Force a certain amount of good damaging moves depending on the percentage + int goodDamagingLeft = (int)Math.round(goodDamagingPercentage * moves.size()); + + // Replace moves as needed + for (int i = 0; i < moves.size(); i++) { + // should this move be forced damaging? + boolean attemptDamaging = goodDamagingLeft > 0; + + // type themed? + Type typeOfMove = null; + if (typeThemed) { + double picked = random.nextDouble(); + if ((pkmn.primaryType == Type.NORMAL && pkmn.secondaryType != null) || + (pkmn.secondaryType == Type.NORMAL)) { + + Type otherType = pkmn.primaryType == Type.NORMAL ? pkmn.secondaryType : pkmn.primaryType; + + // Normal/OTHER: 10% normal, 30% other, 60% random + if (picked < 0.1) { + typeOfMove = Type.NORMAL; + } else if (picked < 0.4) { + typeOfMove = otherType; + } + // else random + } else if (pkmn.secondaryType != null) { + // Primary/Secondary: 20% primary, 20% secondary, 60% random + if (picked < 0.2) { + typeOfMove = pkmn.primaryType; + } else if (picked < 0.4) { + typeOfMove = pkmn.secondaryType; + } + // else random + } else { + // Primary/None: 40% primary, 60% random + if (picked < 0.4) { + typeOfMove = pkmn.primaryType; + } + // else random + } + } + + // select a list to pick a move from that has at least one free + List pickList = validMoves; + if (attemptDamaging) { + if (typeOfMove != null) { + if (validTypeDamagingMoves.containsKey(typeOfMove) + && checkForUnusedMove(validTypeDamagingMoves.get(typeOfMove), learnt)) { + pickList = validTypeDamagingMoves.get(typeOfMove); + } else if (checkForUnusedMove(validDamagingMoves, learnt)) { + pickList = validDamagingMoves; + } + } else if (checkForUnusedMove(validDamagingMoves, learnt)) { + pickList = validDamagingMoves; + } + MoveCategory forcedCategory = random.nextDouble() < atkSpAtkRatio ? MoveCategory.PHYSICAL : MoveCategory.SPECIAL; + List filteredList = pickList.stream().filter(mv -> mv.category == forcedCategory).collect(Collectors.toList()); + if (!filteredList.isEmpty() && checkForUnusedMove(filteredList, learnt)) { + pickList = filteredList; + } + } else if (typeOfMove != null) { + if (validTypeMoves.containsKey(typeOfMove) + && checkForUnusedMove(validTypeMoves.get(typeOfMove), learnt)) { + pickList = validTypeMoves.get(typeOfMove); + } + } + + // now pick a move until we get a valid one + Move mv = pickList.get(random.nextInt(pickList.size())); + while (learnt.contains(mv.number)) { + mv = pickList.get(random.nextInt(pickList.size())); + } + + goodDamagingLeft--; + learnt.add(mv.number); + } + + // write all moves for the pokemon + Collections.shuffle(learnt, random); + for (int i = 0; i < learnt.size(); i++) { + moves.set(i, learnt.get(i)); + } + } + // Done, save + this.setEggMoves(movesets); + } + + private void createSetsOfMoves(boolean noBroken, List validMoves, List validDamagingMoves, + Map> validTypeMoves, Map> validTypeDamagingMoves) { + List allMoves = this.getMoves(); + List hms = this.getHMMoves(); + Set allBanned = new HashSet(noBroken ? this.getGameBreakingMoves() : Collections.EMPTY_SET); + allBanned.addAll(hms); + allBanned.addAll(this.getMovesBannedFromLevelup()); + allBanned.addAll(GlobalConstants.zMoves); + allBanned.addAll(this.getIllegalMoves()); + + for (Move mv : allMoves) { + if (mv != null && !GlobalConstants.bannedRandomMoves[mv.number] && !allBanned.contains(mv.number)) { + validMoves.add(mv); + if (mv.type != null) { + if (!validTypeMoves.containsKey(mv.type)) { + validTypeMoves.put(mv.type, new ArrayList<>()); + } + validTypeMoves.get(mv.type).add(mv); + } + + if (!GlobalConstants.bannedForDamagingMove[mv.number]) { + if (mv.isGoodDamaging(perfectAccuracy)) { + validDamagingMoves.add(mv); + if (mv.type != null) { + if (!validTypeDamagingMoves.containsKey(mv.type)) { + validTypeDamagingMoves.put(mv.type, new ArrayList<>()); + } + validTypeDamagingMoves.get(mv.type).add(mv); + } + } + } + } + } + + Map avgTypePowers = new TreeMap<>(); + double totalAvgPower = 0; + + for (Type type: validTypeMoves.keySet()) { + List typeMoves = validTypeMoves.get(type); + int attackingSum = 0; + for (Move typeMove: typeMoves) { + if (typeMove.power > 0) { + attackingSum += (typeMove.power * typeMove.hitCount); + } + } + double avgTypePower = (double)attackingSum / (double)typeMoves.size(); + avgTypePowers.put(type, avgTypePower); + totalAvgPower += (avgTypePower); + } + + totalAvgPower /= (double)validTypeMoves.keySet().size(); + + // Want the average power of each type to be within 25% both directions + double minAvg = totalAvgPower * 0.75; + double maxAvg = totalAvgPower * 1.25; + + // Add extra moves to type lists outside of the range to balance the average power of each type + + for (Type type: avgTypePowers.keySet()) { + double avgPowerForType = avgTypePowers.get(type); + List typeMoves = validTypeMoves.get(type); + List alreadyPicked = new ArrayList<>(); + int iterLoops = 0; + while (avgPowerForType < minAvg && iterLoops < 10000) { + final double finalAvgPowerForType = avgPowerForType; + List strongerThanAvgTypeMoves = typeMoves + .stream() + .filter(mv -> mv.power * mv.hitCount > finalAvgPowerForType) + .collect(Collectors.toList()); + if (strongerThanAvgTypeMoves.isEmpty()) break; + if (alreadyPicked.containsAll(strongerThanAvgTypeMoves)) { + alreadyPicked = new ArrayList<>(); + } else { + strongerThanAvgTypeMoves.removeAll(alreadyPicked); + } + Move extraMove = strongerThanAvgTypeMoves.get(random.nextInt(strongerThanAvgTypeMoves.size())); + avgPowerForType = (avgPowerForType * typeMoves.size() + extraMove.power * extraMove.hitCount) + / (typeMoves.size() + 1); + typeMoves.add(extraMove); + alreadyPicked.add(extraMove); + iterLoops++; + } + iterLoops = 0; + while (avgPowerForType > maxAvg && iterLoops < 10000) { + final double finalAvgPowerForType = avgPowerForType; + List weakerThanAvgTypeMoves = typeMoves + .stream() + .filter(mv -> mv.power * mv.hitCount < finalAvgPowerForType) + .collect(Collectors.toList()); + if (weakerThanAvgTypeMoves.isEmpty()) break; + if (alreadyPicked.containsAll(weakerThanAvgTypeMoves)) { + alreadyPicked = new ArrayList<>(); + } else { + weakerThanAvgTypeMoves.removeAll(alreadyPicked); + } + Move extraMove = weakerThanAvgTypeMoves.get(random.nextInt(weakerThanAvgTypeMoves.size())); + avgPowerForType = (avgPowerForType * typeMoves.size() + extraMove.power * extraMove.hitCount) + / (typeMoves.size() + 1); + typeMoves.add(extraMove); + alreadyPicked.add(extraMove); + iterLoops++; + } + } + } + + @Override + public void orderDamagingMovesByDamage() { + Map> movesets = this.getMovesLearnt(); + List allMoves = this.getMoves(); + for (Integer pkmn : movesets.keySet()) { + List moves = movesets.get(pkmn); + + // Build up a list of damaging moves and their positions + List damagingMoveIndices = new ArrayList<>(); + List damagingMoves = new ArrayList<>(); + for (int i = 0; i < moves.size(); i++) { + if (moves.get(i).level == 0) continue; // Don't reorder evolution move + Move mv = allMoves.get(moves.get(i).move); + if (mv.power > 1) { + // considered a damaging move for this purpose + damagingMoveIndices.add(i); + damagingMoves.add(mv); + } + } + + // Ties should be sorted randomly, so shuffle the list first. + Collections.shuffle(damagingMoves, random); + + // Sort the damaging moves by power + damagingMoves.sort(Comparator.comparingDouble(m -> m.power * m.hitCount)); + + // Reassign damaging moves in the ordered positions + for (int i = 0; i < damagingMoves.size(); i++) { + moves.get(damagingMoveIndices.get(i)).move = damagingMoves.get(i).number; + } + } + + // Done, save + this.setMovesLearnt(movesets); + } + + @Override + public void metronomeOnlyMode() { + + // movesets + Map> movesets = this.getMovesLearnt(); + + MoveLearnt metronomeML = new MoveLearnt(); + metronomeML.level = 1; + metronomeML.move = Moves.metronome; + + for (List ms : movesets.values()) { + if (ms != null && ms.size() > 0) { + ms.clear(); + ms.add(metronomeML); + } + } + + this.setMovesLearnt(movesets); + + // trainers + // run this to remove all custom non-Metronome moves + List trainers = this.getTrainers(); + + for (Trainer t : trainers) { + for (TrainerPokemon tpk : t.pokemon) { + tpk.resetMoves = true; + } + } + + this.setTrainers(trainers, false); + + // tms + List tmMoves = this.getTMMoves(); + + for (int i = 0; i < tmMoves.size(); i++) { + tmMoves.set(i, Moves.metronome); + } + + this.setTMMoves(tmMoves); + + // movetutors + if (this.hasMoveTutors()) { + List mtMoves = this.getMoveTutorMoves(); + + for (int i = 0; i < mtMoves.size(); i++) { + mtMoves.set(i, Moves.metronome); + } + + this.setMoveTutorMoves(mtMoves); + } + + // move tweaks + List moveData = this.getMoves(); + + Move metronome = moveData.get(Moves.metronome); + + metronome.pp = 40; + + List hms = this.getHMMoves(); + + for (int hm : hms) { + Move thisHM = moveData.get(hm); + thisHM.pp = 0; + } + } + + @Override + public void customStarters(Settings settings) { + boolean abilitiesUnchanged = settings.getAbilitiesMod() == Settings.AbilitiesMod.UNCHANGED; + int[] customStarters = settings.getCustomStarters(); + boolean allowAltFormes = settings.isAllowStarterAltFormes(); + boolean banIrregularAltFormes = settings.isBanIrregularAltFormes(); + + List romPokemon = getPokemonInclFormes() + .stream() + .filter(pk -> pk == null || !pk.actuallyCosmetic) + .collect(Collectors.toList()); + + List banned = getBannedFormesForPlayerPokemon(); + pickedStarters = new ArrayList<>(); + if (abilitiesUnchanged) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + banned.addAll(getIrregularFormes()); + } + // loop to add chosen pokemon to banned, preventing it from being a random option. + for (int i = 0; i < customStarters.length; i = i + 1){ + if (!(customStarters[i] - 1 == 0)){ + banned.add(romPokemon.get(customStarters[i] - 1)); + } + } + if (customStarters[0] - 1 == 0){ + Pokemon pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon(); + while (pickedStarters.contains(pkmn) || banned.contains(pkmn) || pkmn.actuallyCosmetic) { + pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon(); + } + pickedStarters.add(pkmn); + } else { + Pokemon pkmn1 = romPokemon.get(customStarters[0] - 1); + pickedStarters.add(pkmn1); + } + if (customStarters[1] - 1 == 0){ + Pokemon pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon(); + while (pickedStarters.contains(pkmn) || banned.contains(pkmn) || pkmn.actuallyCosmetic) { + pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon(); + } + pickedStarters.add(pkmn); + } else { + Pokemon pkmn2 = romPokemon.get(customStarters[1] - 1); + pickedStarters.add(pkmn2); + } + + if (isYellow()) { + setStarters(pickedStarters); + } else { + if (customStarters[2] - 1 == 0){ + Pokemon pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon(); + while (pickedStarters.contains(pkmn) || banned.contains(pkmn) || pkmn.actuallyCosmetic) { + pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon(); + } + pickedStarters.add(pkmn); + } else { + Pokemon pkmn3 = romPokemon.get(customStarters[2] - 1); + pickedStarters.add(pkmn3); + } + if (starterCount() > 3) { + for (int i = 3; i < starterCount(); i++) { + Pokemon pkmn = random2EvosPokemon(allowAltFormes); + while (pickedStarters.contains(pkmn)) { + pkmn = random2EvosPokemon(allowAltFormes); + } + pickedStarters.add(pkmn); + } + setStarters(pickedStarters); + } else { + setStarters(pickedStarters); + } + } + } + + @Override + public void randomizeStarters(Settings settings) { + boolean abilitiesUnchanged = settings.getAbilitiesMod() == Settings.AbilitiesMod.UNCHANGED; + boolean allowAltFormes = settings.isAllowStarterAltFormes(); + boolean banIrregularAltFormes = settings.isBanIrregularAltFormes(); + + int starterCount = starterCount(); + pickedStarters = new ArrayList<>(); + List banned = getBannedFormesForPlayerPokemon(); + if (abilitiesUnchanged) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + banned.addAll(getIrregularFormes()); + } + for (int i = 0; i < starterCount; i++) { + Pokemon pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon(); + while (pickedStarters.contains(pkmn) || banned.contains(pkmn) || pkmn.actuallyCosmetic) { + pkmn = allowAltFormes ? randomPokemonInclFormes() : randomPokemon(); + } + pickedStarters.add(pkmn); + } + setStarters(pickedStarters); + } + + @Override + public void randomizeBasicTwoEvosStarters(Settings settings) { + boolean abilitiesUnchanged = settings.getAbilitiesMod() == Settings.AbilitiesMod.UNCHANGED; + boolean allowAltFormes = settings.isAllowStarterAltFormes(); + boolean banIrregularAltFormes = settings.isBanIrregularAltFormes(); + + int starterCount = starterCount(); + pickedStarters = new ArrayList<>(); + List banned = getBannedFormesForPlayerPokemon(); + if (abilitiesUnchanged) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + banned.addAll(getIrregularFormes()); + } + for (int i = 0; i < starterCount; i++) { + Pokemon pkmn = random2EvosPokemon(allowAltFormes); + while (pickedStarters.contains(pkmn) || banned.contains(pkmn)) { + pkmn = random2EvosPokemon(allowAltFormes); + } + pickedStarters.add(pkmn); + } + setStarters(pickedStarters); + } + + @Override + public List getPickedStarters() { + return pickedStarters; + } + + + @Override + public void randomizeStaticPokemon(Settings settings) { + boolean swapLegendaries = settings.getStaticPokemonMod() == Settings.StaticPokemonMod.RANDOM_MATCHING; + boolean similarStrength = settings.getStaticPokemonMod() == Settings.StaticPokemonMod.SIMILAR_STRENGTH; + boolean limitMainGameLegendaries = settings.isLimitMainGameLegendaries(); + boolean limit600 = settings.isLimit600(); + boolean allowAltFormes = settings.isAllowStaticAltFormes(); + boolean banIrregularAltFormes = settings.isBanIrregularAltFormes(); + boolean swapMegaEvos = settings.isSwapStaticMegaEvos(); + boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE; + int levelModifier = settings.isStaticLevelModified() ? settings.getStaticLevelModifier() : 0; + boolean correctStaticMusic = settings.isCorrectStaticMusic(); + + // Load + checkPokemonRestrictions(); + List currentStaticPokemon = this.getStaticPokemon(); + List replacements = new ArrayList<>(); + List banned = this.bannedForStaticPokemon(); + banned.addAll(this.getBannedFormesForPlayerPokemon()); + if (!abilitiesAreRandomized) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + banned.addAll(getIrregularFormes()); + } + boolean reallySwapMegaEvos = forceSwapStaticMegaEvos() || swapMegaEvos; + + Map specialMusicStaticChanges = new HashMap<>(); + List changeMusicStatics = new ArrayList<>(); + if (correctStaticMusic) { + changeMusicStatics = getSpecialMusicStatics(); + } + + if (swapLegendaries) { + List legendariesLeft = new ArrayList<>(onlyLegendaryList); + if (allowAltFormes) { + legendariesLeft.addAll(onlyLegendaryAltsList); + legendariesLeft = + legendariesLeft + .stream() + .filter(pk -> !pk.actuallyCosmetic) + .collect(Collectors.toList()); + } + List nonlegsLeft = new ArrayList<>(noLegendaryList); + if (allowAltFormes) { + nonlegsLeft.addAll(noLegendaryAltsList); + nonlegsLeft = + nonlegsLeft + .stream() + .filter(pk -> !pk.actuallyCosmetic) + .collect(Collectors.toList()); + } + List ultraBeastsLeft = new ArrayList<>(ultraBeastList); + legendariesLeft.removeAll(banned); + nonlegsLeft.removeAll(banned); + ultraBeastsLeft.removeAll(banned); + + // Full pools for easier refilling later + List legendariesPool = new ArrayList<>(legendariesLeft); + List nonlegsPool = new ArrayList<>(nonlegsLeft); + List ultraBeastsPool = new ArrayList<>(ultraBeastsLeft); + + for (StaticEncounter old : currentStaticPokemon) { + StaticEncounter newStatic = cloneStaticEncounter(old); + Pokemon newPK; + if (old.pkmn.isLegendary()) { + if (reallySwapMegaEvos && old.canMegaEvolve()) { + newPK = getMegaEvoPokemon(onlyLegendaryList, legendariesLeft, newStatic); + } else { + if (old.restrictedPool) { + newPK = getRestrictedPokemon(legendariesPool, legendariesLeft, old); + } else { + newPK = legendariesLeft.remove(this.random.nextInt(legendariesLeft.size())); + } + } + + setPokemonAndFormeForStaticEncounter(newStatic, newPK); + + if (legendariesLeft.size() == 0) { + legendariesLeft.addAll(legendariesPool); + } + } else if (ultraBeastList.contains(old.pkmn)) { + if (old.restrictedPool) { + newPK = getRestrictedPokemon(ultraBeastsPool, ultraBeastsLeft, old); + } else { + newPK = ultraBeastsLeft.remove(this.random.nextInt(ultraBeastsLeft.size())); + } + + setPokemonAndFormeForStaticEncounter(newStatic, newPK); + + if (ultraBeastsLeft.size() == 0) { + ultraBeastsLeft.addAll(ultraBeastsPool); + } + } else { + if (reallySwapMegaEvos && old.canMegaEvolve()) { + newPK = getMegaEvoPokemon(noLegendaryList, nonlegsLeft, newStatic); + } else { + if (old.restrictedPool) { + newPK = getRestrictedPokemon(nonlegsPool, nonlegsLeft, old); + } else { + newPK = nonlegsLeft.remove(this.random.nextInt(nonlegsLeft.size())); + } + } + setPokemonAndFormeForStaticEncounter(newStatic, newPK); + + if (nonlegsLeft.size() == 0) { + nonlegsLeft.addAll(nonlegsPool); + } + } + replacements.add(newStatic); + if (changeMusicStatics.contains(old.pkmn.number)) { + specialMusicStaticChanges.put(old.pkmn.number, newPK.number); + } + } + } else if (similarStrength) { + List listInclFormesExclCosmetics = + mainPokemonListInclFormes + .stream() + .filter(pk -> !pk.actuallyCosmetic) + .collect(Collectors.toList()); + List pokemonLeft = new ArrayList<>(!allowAltFormes ? mainPokemonList : listInclFormesExclCosmetics); + pokemonLeft.removeAll(banned); + + List pokemonPool = new ArrayList<>(pokemonLeft); + + List mainGameLegendaries = getMainGameLegendaries(); + for (StaticEncounter old : currentStaticPokemon) { + StaticEncounter newStatic = cloneStaticEncounter(old); + Pokemon newPK; + Pokemon oldPK = old.pkmn; + if (old.forme > 0) { + oldPK = getAltFormeOfPokemon(oldPK, old.forme); + } + Integer oldBST = oldPK.bstForPowerLevels(); + if (oldBST >= 600 && limit600) { + if (reallySwapMegaEvos && old.canMegaEvolve()) { + newPK = getMegaEvoPokemon(mainPokemonList, pokemonLeft, newStatic); + } else { + if (old.restrictedPool) { + newPK = getRestrictedPokemon(pokemonPool, pokemonLeft, old); + } else { + newPK = pokemonLeft.remove(this.random.nextInt(pokemonLeft.size())); + } + } + setPokemonAndFormeForStaticEncounter(newStatic, newPK); + } else { + boolean limitBST = oldPK.baseForme == null ? + limitMainGameLegendaries && mainGameLegendaries.contains(oldPK.number) : + limitMainGameLegendaries && mainGameLegendaries.contains(oldPK.baseForme.number); + if (reallySwapMegaEvos && old.canMegaEvolve()) { + List megaEvoPokemonLeft = + megaEvolutionsList + .stream() + .filter(mega -> mega.method == 1) + .map(mega -> mega.from) + .distinct() + .filter(pokemonLeft::contains) + .collect(Collectors.toList()); + if (megaEvoPokemonLeft.isEmpty()) { + megaEvoPokemonLeft = + megaEvolutionsList + .stream() + .filter(mega -> mega.method == 1) + .map(mega -> mega.from) + .distinct() + .filter(mainPokemonList::contains) + .collect(Collectors.toList()); + } + newPK = pickStaticPowerLvlReplacement( + megaEvoPokemonLeft, + oldPK, + true, + limitBST); + newStatic.heldItem = newPK + .megaEvolutionsFrom + .get(this.random.nextInt(newPK.megaEvolutionsFrom.size())) + .argument; + } else { + if (old.restrictedPool) { + List restrictedPool = pokemonLeft + .stream() + .filter(pk -> old.restrictedList.contains(pk)) + .collect(Collectors.toList()); + if (restrictedPool.isEmpty()) { + restrictedPool = pokemonPool + .stream() + .filter(pk -> old.restrictedList.contains(pk)) + .collect(Collectors.toList()); + } + newPK = pickStaticPowerLvlReplacement( + restrictedPool, + oldPK, + false, // Allow same Pokemon just in case + limitBST); + } else { + newPK = pickStaticPowerLvlReplacement( + pokemonLeft, + oldPK, + true, + limitBST); + } + } + pokemonLeft.remove(newPK); + setPokemonAndFormeForStaticEncounter(newStatic, newPK); + } + + if (pokemonLeft.size() == 0) { + pokemonLeft.addAll(pokemonPool); + } + replacements.add(newStatic); + if (changeMusicStatics.contains(old.pkmn.number)) { + specialMusicStaticChanges.put(old.pkmn.number, newPK.number); + } + } + } else { // Completely random + List listInclFormesExclCosmetics = + mainPokemonListInclFormes + .stream() + .filter(pk -> !pk.actuallyCosmetic) + .collect(Collectors.toList()); + List pokemonLeft = new ArrayList<>(!allowAltFormes ? mainPokemonList : listInclFormesExclCosmetics); + pokemonLeft.removeAll(banned); + + List pokemonPool = new ArrayList<>(pokemonLeft); + + for (StaticEncounter old : currentStaticPokemon) { + StaticEncounter newStatic = cloneStaticEncounter(old); + Pokemon newPK; + if (reallySwapMegaEvos && old.canMegaEvolve()) { + newPK = getMegaEvoPokemon(mainPokemonList, pokemonLeft, newStatic); + } else { + if (old.restrictedPool) { + newPK = getRestrictedPokemon(pokemonPool, pokemonLeft, old); + } else { + newPK = pokemonLeft.remove(this.random.nextInt(pokemonLeft.size())); + } + } + pokemonLeft.remove(newPK); + setPokemonAndFormeForStaticEncounter(newStatic, newPK); + if (pokemonLeft.size() == 0) { + pokemonLeft.addAll(pokemonPool); + } + replacements.add(newStatic); + if (changeMusicStatics.contains(old.pkmn.number)) { + specialMusicStaticChanges.put(old.pkmn.number, newPK.number); + } + } + } + + if (levelModifier != 0) { + for (StaticEncounter se : replacements) { + if (!se.isEgg) { + se.level = Math.min(100, (int) Math.round(se.level * (1 + levelModifier / 100.0))); + se.maxLevel = Math.min(100, (int) Math.round(se.maxLevel * (1 + levelModifier / 100.0))); + for (StaticEncounter linkedStatic : se.linkedEncounters) { + if (!linkedStatic.isEgg) { + linkedStatic.level = Math.min(100, (int) Math.round(linkedStatic.level * (1 + levelModifier / 100.0))); + linkedStatic.maxLevel = Math.min(100, (int) Math.round(linkedStatic.maxLevel * (1 + levelModifier / 100.0))); + } + } + } + } + } + + if (specialMusicStaticChanges.size() > 0) { + applyCorrectStaticMusic(specialMusicStaticChanges); + } + + // Save + this.setStaticPokemon(replacements); + } + + private Pokemon getRestrictedPokemon(List fullList, List pokemonLeft, StaticEncounter old) { + Pokemon newPK; + List restrictedPool = pokemonLeft.stream().filter(pk -> old.restrictedList.contains(pk)).collect(Collectors.toList()); + if (restrictedPool.isEmpty()) { + restrictedPool = fullList + .stream() + .filter(pk -> old.restrictedList.contains(pk)) + .collect(Collectors.toList()); + } + newPK = restrictedPool.remove(this.random.nextInt(restrictedPool.size())); + pokemonLeft.remove(newPK); + return newPK; + } + + @Override + public void onlyChangeStaticLevels(Settings settings) { + int levelModifier = settings.getStaticLevelModifier(); + + List currentStaticPokemon = this.getStaticPokemon(); + for (StaticEncounter se : currentStaticPokemon) { + if (!se.isEgg) { + se.level = Math.min(100, (int) Math.round(se.level * (1 + levelModifier / 100.0))); + for (StaticEncounter linkedStatic : se.linkedEncounters) { + if (!linkedStatic.isEgg) { + linkedStatic.level = Math.min(100, (int) Math.round(linkedStatic.level * (1 + levelModifier / 100.0))); + } + } + } + setPokemonAndFormeForStaticEncounter(se, se.pkmn); + } + this.setStaticPokemon(currentStaticPokemon); + } + + private StaticEncounter cloneStaticEncounter(StaticEncounter old) { + StaticEncounter newStatic = new StaticEncounter(); + newStatic.pkmn = old.pkmn; + newStatic.level = old.level; + newStatic.maxLevel = old.maxLevel; + newStatic.heldItem = old.heldItem; + newStatic.isEgg = old.isEgg; + newStatic.resetMoves = true; + for (StaticEncounter oldLinked : old.linkedEncounters) { + StaticEncounter newLinked = new StaticEncounter(); + newLinked.pkmn = oldLinked.pkmn; + newLinked.level = oldLinked.level; + newLinked.maxLevel = oldLinked.maxLevel; + newLinked.heldItem = oldLinked.heldItem; + newLinked.isEgg = oldLinked.isEgg; + newLinked.resetMoves = true; + newStatic.linkedEncounters.add(newLinked); + } + return newStatic; + } + + private void setPokemonAndFormeForStaticEncounter(StaticEncounter newStatic, Pokemon pk) { + boolean checkCosmetics = true; + Pokemon newPK = pk; + int newForme = 0; + if (pk.formeNumber > 0) { + newForme = pk.formeNumber; + newPK = pk.baseForme; + checkCosmetics = false; + } + if (checkCosmetics && pk.cosmeticForms > 0) { + newForme = pk.getCosmeticFormNumber(this.random.nextInt(pk.cosmeticForms)); + } else if (!checkCosmetics && pk.cosmeticForms > 0) { + newForme += pk.getCosmeticFormNumber(this.random.nextInt(pk.cosmeticForms)); + } + newStatic.pkmn = newPK; + newStatic.forme = newForme; + for (StaticEncounter linked : newStatic.linkedEncounters) { + linked.pkmn = newPK; + linked.forme = newForme; + } + } + + private void setFormeForStaticEncounter(StaticEncounter newStatic, Pokemon pk) { + boolean checkCosmetics = true; + newStatic.forme = 0; + if (pk.formeNumber > 0) { + newStatic.forme = pk.formeNumber; + newStatic.pkmn = pk.baseForme; + checkCosmetics = false; + } + if (checkCosmetics && newStatic.pkmn.cosmeticForms > 0) { + newStatic.forme = newStatic.pkmn.getCosmeticFormNumber(this.random.nextInt(newStatic.pkmn.cosmeticForms)); + } else if (!checkCosmetics && pk.cosmeticForms > 0) { + newStatic.forme += pk.getCosmeticFormNumber(this.random.nextInt(pk.cosmeticForms)); + } + } + + private Pokemon getMegaEvoPokemon(List fullList, List pokemonLeft, StaticEncounter newStatic) { + List megaEvos = megaEvolutionsList; + List megaEvoPokemon = + megaEvos + .stream() + .filter(mega -> mega.method == 1) + .map(mega -> mega.from) + .distinct() + .collect(Collectors.toList()); + Pokemon newPK; + List megaEvoPokemonLeft = + megaEvoPokemon + .stream() + .filter(pokemonLeft::contains) + .collect(Collectors.toList()); + if (megaEvoPokemonLeft.isEmpty()) { + megaEvoPokemonLeft = megaEvoPokemon + .stream() + .filter(fullList::contains) + .collect(Collectors.toList()); + } + newPK = megaEvoPokemonLeft.remove(this.random.nextInt(megaEvoPokemonLeft.size())); + pokemonLeft.remove(newPK); + newStatic.heldItem = newPK + .megaEvolutionsFrom + .get(this.random.nextInt(newPK.megaEvolutionsFrom.size())) + .argument; + return newPK; + } + + @Override + public void randomizeTMMoves(Settings settings) { + boolean noBroken = settings.isBlockBrokenTMMoves(); + boolean preserveField = settings.isKeepFieldMoveTMs(); + double goodDamagingPercentage = settings.isTmsForceGoodDamaging() ? settings.getTmsGoodDamagingPercent() / 100.0 : 0; + + // Pick some random TM moves. + int tmCount = this.getTMCount(); + List allMoves = this.getMoves(); + List hms = this.getHMMoves(); + List oldTMs = this.getTMMoves(); + @SuppressWarnings("unchecked") + List banned = new ArrayList(noBroken ? this.getGameBreakingMoves() : Collections.EMPTY_LIST); + banned.addAll(getMovesBannedFromLevelup()); + banned.addAll(this.getIllegalMoves()); + // field moves? + List fieldMoves = this.getFieldMoves(); + int preservedFieldMoveCount = 0; + + if (preserveField) { + List banExistingField = new ArrayList<>(oldTMs); + banExistingField.retainAll(fieldMoves); + preservedFieldMoveCount = banExistingField.size(); + banned.addAll(banExistingField); + } + + // Determine which moves are pickable + List usableMoves = new ArrayList<>(allMoves); + usableMoves.remove(0); // remove null entry + Set unusableMoves = new HashSet<>(); + Set unusableDamagingMoves = new HashSet<>(); + + for (Move mv : usableMoves) { + if (GlobalConstants.bannedRandomMoves[mv.number] || GlobalConstants.zMoves.contains(mv.number) || + hms.contains(mv.number) || banned.contains(mv.number)) { + unusableMoves.add(mv); + } else if (GlobalConstants.bannedForDamagingMove[mv.number] || !mv.isGoodDamaging(perfectAccuracy)) { + unusableDamagingMoves.add(mv); + } + } + + usableMoves.removeAll(unusableMoves); + List usableDamagingMoves = new ArrayList<>(usableMoves); + usableDamagingMoves.removeAll(unusableDamagingMoves); + + // pick (tmCount - preservedFieldMoveCount) moves + List pickedMoves = new ArrayList<>(); + + // Force a certain amount of good damaging moves depending on the percentage + int goodDamagingLeft = (int)Math.round(goodDamagingPercentage * (tmCount - preservedFieldMoveCount)); + + for (int i = 0; i < tmCount - preservedFieldMoveCount; i++) { + Move chosenMove; + if (goodDamagingLeft > 0 && usableDamagingMoves.size() > 0) { + chosenMove = usableDamagingMoves.get(random.nextInt(usableDamagingMoves.size())); + } else { + chosenMove = usableMoves.get(random.nextInt(usableMoves.size())); + } + pickedMoves.add(chosenMove.number); + usableMoves.remove(chosenMove); + usableDamagingMoves.remove(chosenMove); + goodDamagingLeft--; + } + + // shuffle the picked moves because high goodDamagingPercentage + // will bias them towards early numbers otherwise + + Collections.shuffle(pickedMoves, random); + + // finally, distribute them as tms + int pickedMoveIndex = 0; + List newTMs = new ArrayList<>(); + + for (int i = 0; i < tmCount; i++) { + if (preserveField && fieldMoves.contains(oldTMs.get(i))) { + newTMs.add(oldTMs.get(i)); + } else { + newTMs.add(pickedMoves.get(pickedMoveIndex++)); + } + } + + this.setTMMoves(newTMs); + } + + @Override + public void randomizeTMHMCompatibility(Settings settings) { + boolean preferSameType = settings.getTmsHmsCompatibilityMod() == Settings.TMsHMsCompatibilityMod.RANDOM_PREFER_TYPE; + boolean followEvolutions = settings.isTmsFollowEvolutions(); + + // Get current compatibility + // increase HM chances if required early on + List requiredEarlyOn = this.getEarlyRequiredHMMoves(); + Map compat = this.getTMHMCompatibility(); + List tmHMs = new ArrayList<>(this.getTMMoves()); + tmHMs.addAll(this.getHMMoves()); + + if (followEvolutions) { + copyUpEvolutionsHelper(pk -> randomizePokemonMoveCompatibility( + pk, compat.get(pk), tmHMs, requiredEarlyOn, preferSameType), + (evFrom, evTo, toMonIsFinalEvo) -> copyPokemonMoveCompatibilityUpEvolutions( + evFrom, evTo, compat.get(evFrom), compat.get(evTo), tmHMs, preferSameType + ), null, true); + } + else { + for (Map.Entry compatEntry : compat.entrySet()) { + randomizePokemonMoveCompatibility(compatEntry.getKey(), compatEntry.getValue(), tmHMs, + requiredEarlyOn, preferSameType); + } + } + + // Set the new compatibility + this.setTMHMCompatibility(compat); + } + + private void randomizePokemonMoveCompatibility(Pokemon pkmn, boolean[] moveCompatibilityFlags, + List moveIDs, List prioritizedMoves, + boolean preferSameType) { + List moveData = this.getMoves(); + for (int i = 1; i <= moveIDs.size(); i++) { + int move = moveIDs.get(i - 1); + Move mv = moveData.get(move); + double probability = getMoveCompatibilityProbability( + pkmn, + mv, + prioritizedMoves.contains(move), + preferSameType + ); + moveCompatibilityFlags[i] = (this.random.nextDouble() < probability); + } + } + + private void copyPokemonMoveCompatibilityUpEvolutions(Pokemon evFrom, Pokemon evTo, boolean[] prevCompatibilityFlags, + boolean[] toCompatibilityFlags, List moveIDs, + boolean preferSameType) { + List moveData = this.getMoves(); + for (int i = 1; i <= moveIDs.size(); i++) { + if (!prevCompatibilityFlags[i]) { + // Slight chance to gain TM/HM compatibility for a move if not learned by an earlier evolution step + // Without prefer same type: 25% chance + // With prefer same type: 10% chance, 90% chance for a type new to this evolution + int move = moveIDs.get(i - 1); + Move mv = moveData.get(move); + double probability = 0.25; + if (preferSameType) { + probability = 0.1; + if (evTo.primaryType.equals(mv.type) + && !evTo.primaryType.equals(evFrom.primaryType) && !evTo.primaryType.equals(evFrom.secondaryType) + || evTo.secondaryType != null && evTo.secondaryType.equals(mv.type) + && !evTo.secondaryType.equals(evFrom.secondaryType) && !evTo.secondaryType.equals(evFrom.primaryType)) { + probability = 0.9; + } + } + toCompatibilityFlags[i] = (this.random.nextDouble() < probability); + } + else { + toCompatibilityFlags[i] = prevCompatibilityFlags[i]; + } + } + } + + private double getMoveCompatibilityProbability(Pokemon pkmn, Move mv, boolean requiredEarlyOn, + boolean preferSameType) { + double probability = 0.5; + if (preferSameType) { + if (pkmn.primaryType.equals(mv.type) + || (pkmn.secondaryType != null && pkmn.secondaryType.equals(mv.type))) { + probability = 0.9; + } else if (mv.type != null && mv.type.equals(Type.NORMAL)) { + probability = 0.5; + } else { + probability = 0.25; + } + } + if (requiredEarlyOn) { + probability = Math.min(1.0, probability * 1.8); + } + return probability; + } + + @Override + public void fullTMHMCompatibility() { + Map compat = this.getTMHMCompatibility(); + for (Map.Entry compatEntry : compat.entrySet()) { + boolean[] flags = compatEntry.getValue(); + for (int i = 1; i < flags.length; i++) { + flags[i] = true; + } + } + this.setTMHMCompatibility(compat); + } + + @Override + public void ensureTMCompatSanity() { + // if a pokemon learns a move in its moveset + // and there is a TM of that move, make sure + // that TM can be learned. + Map compat = this.getTMHMCompatibility(); + Map> movesets = this.getMovesLearnt(); + List tmMoves = this.getTMMoves(); + for (Pokemon pkmn : compat.keySet()) { + List moveset = movesets.get(pkmn.number); + boolean[] pkmnCompat = compat.get(pkmn); + for (MoveLearnt ml : moveset) { + if (tmMoves.contains(ml.move)) { + int tmIndex = tmMoves.indexOf(ml.move); + pkmnCompat[tmIndex + 1] = true; + } + } + } + this.setTMHMCompatibility(compat); + } + + @Override + public void ensureTMEvolutionSanity() { + Map compat = this.getTMHMCompatibility(); + // Don't do anything with the base, just copy upwards to ensure later evolutions retain learn compatibility + copyUpEvolutionsHelper(pk -> {}, ((evFrom, evTo, toMonIsFinalEvo) -> { + boolean[] fromCompat = compat.get(evFrom); + boolean[] toCompat = compat.get(evTo); + for (int i = 1; i < toCompat.length; i++) { + toCompat[i] |= fromCompat[i]; + } + }), null, true); + this.setTMHMCompatibility(compat); + } + + @Override + public void fullHMCompatibility() { + Map compat = this.getTMHMCompatibility(); + int tmCount = this.getTMCount(); + for (boolean[] flags : compat.values()) { + for (int i = tmCount + 1; i < flags.length; i++) { + flags[i] = true; + } + } + + // Set the new compatibility + this.setTMHMCompatibility(compat); + } + + @Override + public void copyTMCompatibilityToCosmeticFormes() { + Map compat = this.getTMHMCompatibility(); + + for (Map.Entry compatEntry : compat.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + if (pkmn.actuallyCosmetic) { + boolean[] baseFlags = compat.get(pkmn.baseForme); + for (int i = 1; i < flags.length; i++) { + flags[i] = baseFlags[i]; + } + } + } + + this.setTMHMCompatibility(compat); + } + + @Override + public void randomizeMoveTutorMoves(Settings settings) { + boolean noBroken = settings.isBlockBrokenTutorMoves(); + boolean preserveField = settings.isKeepFieldMoveTutors(); + double goodDamagingPercentage = settings.isTutorsForceGoodDamaging() ? settings.getTutorsGoodDamagingPercent() / 100.0 : 0; + + if (!this.hasMoveTutors()) { + return; + } + + // Pick some random Move Tutor moves, excluding TMs. + List allMoves = this.getMoves(); + List tms = this.getTMMoves(); + List oldMTs = this.getMoveTutorMoves(); + int mtCount = oldMTs.size(); + List hms = this.getHMMoves(); + @SuppressWarnings("unchecked") + List banned = new ArrayList(noBroken ? this.getGameBreakingMoves() : Collections.EMPTY_LIST); + banned.addAll(getMovesBannedFromLevelup()); + banned.addAll(this.getIllegalMoves()); + + // field moves? + List fieldMoves = this.getFieldMoves(); + int preservedFieldMoveCount = 0; + if (preserveField) { + List banExistingField = new ArrayList<>(oldMTs); + banExistingField.retainAll(fieldMoves); + preservedFieldMoveCount = banExistingField.size(); + banned.addAll(banExistingField); + } + + // Determine which moves are pickable + List usableMoves = new ArrayList<>(allMoves); + usableMoves.remove(0); // remove null entry + Set unusableMoves = new HashSet<>(); + Set unusableDamagingMoves = new HashSet<>(); + + for (Move mv : usableMoves) { + if (GlobalConstants.bannedRandomMoves[mv.number] || tms.contains(mv.number) || hms.contains(mv.number) + || banned.contains(mv.number) || GlobalConstants.zMoves.contains(mv.number)) { + unusableMoves.add(mv); + } else if (GlobalConstants.bannedForDamagingMove[mv.number] || !mv.isGoodDamaging(perfectAccuracy)) { + unusableDamagingMoves.add(mv); + } + } + + usableMoves.removeAll(unusableMoves); + List usableDamagingMoves = new ArrayList<>(usableMoves); + usableDamagingMoves.removeAll(unusableDamagingMoves); + + // pick (tmCount - preservedFieldMoveCount) moves + List pickedMoves = new ArrayList<>(); + + // Force a certain amount of good damaging moves depending on the percentage + int goodDamagingLeft = (int)Math.round(goodDamagingPercentage * (mtCount - preservedFieldMoveCount)); + + for (int i = 0; i < mtCount - preservedFieldMoveCount; i++) { + Move chosenMove; + if (goodDamagingLeft > 0 && usableDamagingMoves.size() > 0) { + chosenMove = usableDamagingMoves.get(random.nextInt(usableDamagingMoves.size())); + } else { + chosenMove = usableMoves.get(random.nextInt(usableMoves.size())); + } + pickedMoves.add(chosenMove.number); + usableMoves.remove(chosenMove); + usableDamagingMoves.remove(chosenMove); + goodDamagingLeft--; + } + + // shuffle the picked moves because high goodDamagingPercentage + // will bias them towards early numbers otherwise + + Collections.shuffle(pickedMoves, random); + + // finally, distribute them as tutors + int pickedMoveIndex = 0; + List newMTs = new ArrayList<>(); + + for (Integer oldMT : oldMTs) { + if (preserveField && fieldMoves.contains(oldMT)) { + newMTs.add(oldMT); + } else { + newMTs.add(pickedMoves.get(pickedMoveIndex++)); + } + } + + this.setMoveTutorMoves(newMTs); + } + + @Override + public void randomizeMoveTutorCompatibility(Settings settings) { + boolean preferSameType = settings.getMoveTutorsCompatibilityMod() == Settings.MoveTutorsCompatibilityMod.RANDOM_PREFER_TYPE; + boolean followEvolutions = settings.isTutorFollowEvolutions(); + + if (!this.hasMoveTutors()) { + return; + } + // Get current compatibility + Map compat = this.getMoveTutorCompatibility(); + List mts = this.getMoveTutorMoves(); + + // Empty list + List priorityTutors = new ArrayList(); + + if (followEvolutions) { + copyUpEvolutionsHelper(pk -> randomizePokemonMoveCompatibility( + pk, compat.get(pk), mts, priorityTutors, preferSameType), + (evFrom, evTo, toMonIsFinalEvo) -> copyPokemonMoveCompatibilityUpEvolutions( + evFrom, evTo, compat.get(evFrom), compat.get(evTo), mts, preferSameType + ), null, true); + } + else { + for (Map.Entry compatEntry : compat.entrySet()) { + randomizePokemonMoveCompatibility(compatEntry.getKey(), compatEntry.getValue(), mts, priorityTutors, preferSameType); + } + } + + // Set the new compatibility + this.setMoveTutorCompatibility(compat); + } + + @Override + public void fullMoveTutorCompatibility() { + if (!this.hasMoveTutors()) { + return; + } + Map compat = this.getMoveTutorCompatibility(); + for (Map.Entry compatEntry : compat.entrySet()) { + boolean[] flags = compatEntry.getValue(); + for (int i = 1; i < flags.length; i++) { + flags[i] = true; + } + } + this.setMoveTutorCompatibility(compat); + } + + @Override + public void ensureMoveTutorCompatSanity() { + if (!this.hasMoveTutors()) { + return; + } + // if a pokemon learns a move in its moveset + // and there is a tutor of that move, make sure + // that tutor can be learned. + Map compat = this.getMoveTutorCompatibility(); + Map> movesets = this.getMovesLearnt(); + List mtMoves = this.getMoveTutorMoves(); + for (Pokemon pkmn : compat.keySet()) { + List moveset = movesets.get(pkmn.number); + boolean[] pkmnCompat = compat.get(pkmn); + for (MoveLearnt ml : moveset) { + if (mtMoves.contains(ml.move)) { + int mtIndex = mtMoves.indexOf(ml.move); + pkmnCompat[mtIndex + 1] = true; + } + } + } + this.setMoveTutorCompatibility(compat); + } + + @Override + public void ensureMoveTutorEvolutionSanity() { + if (!this.hasMoveTutors()) { + return; + } + Map compat = this.getMoveTutorCompatibility(); + // Don't do anything with the base, just copy upwards to ensure later evolutions retain learn compatibility + copyUpEvolutionsHelper(pk -> {}, ((evFrom, evTo, toMonIsFinalEvo) -> { + boolean[] fromCompat = compat.get(evFrom); + boolean[] toCompat = compat.get(evTo); + for (int i = 1; i < toCompat.length; i++) { + toCompat[i] |= fromCompat[i]; + } + }), null, true); + this.setMoveTutorCompatibility(compat); + } + + @Override + public void copyMoveTutorCompatibilityToCosmeticFormes() { + Map compat = this.getMoveTutorCompatibility(); + + for (Map.Entry compatEntry : compat.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + if (pkmn.actuallyCosmetic) { + boolean[] baseFlags = compat.get(pkmn.baseForme); + for (int i = 1; i < flags.length; i++) { + flags[i] = baseFlags[i]; + } + } + } + + this.setMoveTutorCompatibility(compat); + } + + @SuppressWarnings("unchecked") + @Override + public void randomizeTrainerNames(Settings settings) { + CustomNamesSet customNames = settings.getCustomNames(); + + if (!this.canChangeTrainerText()) { + return; + } + + // index 0 = singles, 1 = doubles + List[] allTrainerNames = new List[] { new ArrayList(), new ArrayList() }; + Map> trainerNamesByLength[] = new Map[] { new TreeMap>(), + new TreeMap>() }; + + List repeatedTrainerNames = Arrays.asList(new String[] { "GRUNT", "EXECUTIVE", "SHADOW", "ADMIN", "GOON", "EMPLOYEE" }); + + // Read name lists + for (String trainername : customNames.getTrainerNames()) { + int len = this.internalStringLength(trainername); + if (len <= 10) { + allTrainerNames[0].add(trainername); + if (trainerNamesByLength[0].containsKey(len)) { + trainerNamesByLength[0].get(len).add(trainername); + } else { + List namesOfThisLength = new ArrayList<>(); + namesOfThisLength.add(trainername); + trainerNamesByLength[0].put(len, namesOfThisLength); + } + } + } + + for (String trainername : customNames.getDoublesTrainerNames()) { + int len = this.internalStringLength(trainername); + if (len <= 10) { + allTrainerNames[1].add(trainername); + if (trainerNamesByLength[1].containsKey(len)) { + trainerNamesByLength[1].get(len).add(trainername); + } else { + List namesOfThisLength = new ArrayList<>(); + namesOfThisLength.add(trainername); + trainerNamesByLength[1].put(len, namesOfThisLength); + } + } + } + + // Get the current trainer names data + List currentTrainerNames = this.getTrainerNames(); + if (currentTrainerNames.size() == 0) { + // RBY have no trainer names + return; + } + TrainerNameMode mode = this.trainerNameMode(); + int maxLength = this.maxTrainerNameLength(); + int totalMaxLength = this.maxSumOfTrainerNameLengths(); + + boolean success = false; + int tries = 0; + + // Init the translation map and new list + Map translation = new HashMap<>(); + List newTrainerNames = new ArrayList<>(); + List tcNameLengths = this.getTCNameLengthsByTrainer(); + + // loop until we successfully pick names that fit + // should always succeed first attempt except for gen2. + while (!success && tries < 10000) { + success = true; + translation.clear(); + newTrainerNames.clear(); + int totalLength = 0; + + // Start choosing + int tnIndex = -1; + for (String trainerName : currentTrainerNames) { + tnIndex++; + if (translation.containsKey(trainerName) && !repeatedTrainerNames.contains(trainerName.toUpperCase())) { + // use an already picked translation + newTrainerNames.add(translation.get(trainerName)); + totalLength += this.internalStringLength(translation.get(trainerName)); + } else { + int idx = trainerName.contains("&") ? 1 : 0; + List pickFrom = allTrainerNames[idx]; + int intStrLen = this.internalStringLength(trainerName); + if (mode == TrainerNameMode.SAME_LENGTH) { + pickFrom = trainerNamesByLength[idx].get(intStrLen); + } + String changeTo = trainerName; + int ctl = intStrLen; + if (pickFrom != null && pickFrom.size() > 0 && intStrLen > 0) { + int innerTries = 0; + changeTo = pickFrom.get(this.cosmeticRandom.nextInt(pickFrom.size())); + ctl = this.internalStringLength(changeTo); + while ((mode == TrainerNameMode.MAX_LENGTH && ctl > maxLength) + || (mode == TrainerNameMode.MAX_LENGTH_WITH_CLASS && ctl + tcNameLengths.get(tnIndex) > maxLength)) { + innerTries++; + if (innerTries == 100) { + changeTo = trainerName; + ctl = intStrLen; + break; + } + changeTo = pickFrom.get(this.cosmeticRandom.nextInt(pickFrom.size())); + ctl = this.internalStringLength(changeTo); + } + } + translation.put(trainerName, changeTo); + newTrainerNames.add(changeTo); + totalLength += ctl; + } + + if (totalLength > totalMaxLength) { + success = false; + tries++; + break; + } + } + } + + if (!success) { + throw new RandomizationException("Could not randomize trainer names in a reasonable amount of attempts." + + "\nPlease add some shorter names to your custom trainer names."); + } + + // Done choosing, save + this.setTrainerNames(newTrainerNames); + } + + @SuppressWarnings("unchecked") + @Override + public void randomizeTrainerClassNames(Settings settings) { + CustomNamesSet customNames = settings.getCustomNames(); + + if (!this.canChangeTrainerText()) { + return; + } + + // index 0 = singles, index 1 = doubles + List allTrainerClasses[] = new List[] { new ArrayList(), new ArrayList() }; + Map> trainerClassesByLength[] = new Map[] { new HashMap>(), + new HashMap>() }; + + // Read names data + for (String trainerClassName : customNames.getTrainerClasses()) { + allTrainerClasses[0].add(trainerClassName); + int len = this.internalStringLength(trainerClassName); + if (trainerClassesByLength[0].containsKey(len)) { + trainerClassesByLength[0].get(len).add(trainerClassName); + } else { + List namesOfThisLength = new ArrayList<>(); + namesOfThisLength.add(trainerClassName); + trainerClassesByLength[0].put(len, namesOfThisLength); + } + } + + for (String trainerClassName : customNames.getDoublesTrainerClasses()) { + allTrainerClasses[1].add(trainerClassName); + int len = this.internalStringLength(trainerClassName); + if (trainerClassesByLength[1].containsKey(len)) { + trainerClassesByLength[1].get(len).add(trainerClassName); + } else { + List namesOfThisLength = new ArrayList<>(); + namesOfThisLength.add(trainerClassName); + trainerClassesByLength[1].put(len, namesOfThisLength); + } + } + + // Get the current trainer names data + List currentClassNames = this.getTrainerClassNames(); + boolean mustBeSameLength = this.fixedTrainerClassNamesLength(); + int maxLength = this.maxTrainerClassNameLength(); + + // Init the translation map and new list + Map translation = new HashMap<>(); + List newClassNames = new ArrayList<>(); + + int numTrainerClasses = currentClassNames.size(); + List doublesClasses = this.getDoublesTrainerClasses(); + + // Start choosing + for (int i = 0; i < numTrainerClasses; i++) { + String trainerClassName = currentClassNames.get(i); + if (translation.containsKey(trainerClassName)) { + // use an already picked translation + newClassNames.add(translation.get(trainerClassName)); + } else { + int idx = doublesClasses.contains(i) ? 1 : 0; + List pickFrom = allTrainerClasses[idx]; + int intStrLen = this.internalStringLength(trainerClassName); + if (mustBeSameLength) { + pickFrom = trainerClassesByLength[idx].get(intStrLen); + } + String changeTo = trainerClassName; + if (pickFrom != null && pickFrom.size() > 0) { + changeTo = pickFrom.get(this.cosmeticRandom.nextInt(pickFrom.size())); + while (changeTo.length() > maxLength) { + changeTo = pickFrom.get(this.cosmeticRandom.nextInt(pickFrom.size())); + } + } + translation.put(trainerClassName, changeTo); + newClassNames.add(changeTo); + } + } + + // Done choosing, save + this.setTrainerClassNames(newClassNames); + } + + @Override + public void randomizeWildHeldItems(Settings settings) { + boolean banBadItems = settings.isBanBadRandomWildPokemonHeldItems(); + + List pokemon = allPokemonInclFormesWithoutNull(); + ItemList possibleItems = banBadItems ? this.getNonBadItems() : this.getAllowedItems(); + for (Pokemon pk : pokemon) { + if (pk.guaranteedHeldItem == -1 && pk.commonHeldItem == -1 && pk.rareHeldItem == -1 + && pk.darkGrassHeldItem == -1) { + // No held items at all, abort + return; + } + boolean canHaveDarkGrass = pk.darkGrassHeldItem != -1; + if (pk.guaranteedHeldItem != -1) { + // Guaranteed held items are supported. + if (pk.guaranteedHeldItem > 0) { + // Currently have a guaranteed item + double decision = this.random.nextDouble(); + if (decision < 0.9) { + // Stay as guaranteed + canHaveDarkGrass = false; + pk.guaranteedHeldItem = possibleItems.randomItem(this.random); + } else { + // Change to 25% or 55% chance + pk.guaranteedHeldItem = 0; + pk.commonHeldItem = possibleItems.randomItem(this.random); + pk.rareHeldItem = possibleItems.randomItem(this.random); + while (pk.rareHeldItem == pk.commonHeldItem) { + pk.rareHeldItem = possibleItems.randomItem(this.random); + } + } + } else { + // No guaranteed item atm + double decision = this.random.nextDouble(); + if (decision < 0.5) { + // No held item at all + pk.commonHeldItem = 0; + pk.rareHeldItem = 0; + } else if (decision < 0.65) { + // Just a rare item + pk.commonHeldItem = 0; + pk.rareHeldItem = possibleItems.randomItem(this.random); + } else if (decision < 0.8) { + // Just a common item + pk.commonHeldItem = possibleItems.randomItem(this.random); + pk.rareHeldItem = 0; + } else if (decision < 0.95) { + // Both a common and rare item + pk.commonHeldItem = possibleItems.randomItem(this.random); + pk.rareHeldItem = possibleItems.randomItem(this.random); + while (pk.rareHeldItem == pk.commonHeldItem) { + pk.rareHeldItem = possibleItems.randomItem(this.random); + } + } else { + // Guaranteed item + canHaveDarkGrass = false; + pk.guaranteedHeldItem = possibleItems.randomItem(this.random); + pk.commonHeldItem = 0; + pk.rareHeldItem = 0; + } + } + } else { + // Code for no guaranteed items + double decision = this.random.nextDouble(); + if (decision < 0.5) { + // No held item at all + pk.commonHeldItem = 0; + pk.rareHeldItem = 0; + } else if (decision < 0.65) { + // Just a rare item + pk.commonHeldItem = 0; + pk.rareHeldItem = possibleItems.randomItem(this.random); + } else if (decision < 0.8) { + // Just a common item + pk.commonHeldItem = possibleItems.randomItem(this.random); + pk.rareHeldItem = 0; + } else { + // Both a common and rare item + pk.commonHeldItem = possibleItems.randomItem(this.random); + pk.rareHeldItem = possibleItems.randomItem(this.random); + while (pk.rareHeldItem == pk.commonHeldItem) { + pk.rareHeldItem = possibleItems.randomItem(this.random); + } + } + } + + if (canHaveDarkGrass) { + double dgDecision = this.random.nextDouble(); + if (dgDecision < 0.5) { + // Yes, dark grass item + pk.darkGrassHeldItem = possibleItems.randomItem(this.random); + } else { + pk.darkGrassHeldItem = 0; + } + } else if (pk.darkGrassHeldItem != -1) { + pk.darkGrassHeldItem = 0; + } + } + + } + + @Override + public void randomizeStarterHeldItems(Settings settings) { + boolean banBadItems = settings.isBanBadRandomStarterHeldItems(); + + List oldHeldItems = this.getStarterHeldItems(); + List newHeldItems = new ArrayList<>(); + ItemList possibleItems = banBadItems ? this.getNonBadItems() : this.getAllowedItems(); + for (int i = 0; i < oldHeldItems.size(); i++) { + newHeldItems.add(possibleItems.randomItem(this.random)); + } + this.setStarterHeldItems(newHeldItems); + } + + @Override + public void shuffleFieldItems() { + List currentItems = this.getRegularFieldItems(); + List currentTMs = this.getCurrentFieldTMs(); + + Collections.shuffle(currentItems, this.random); + Collections.shuffle(currentTMs, this.random); + + this.setRegularFieldItems(currentItems); + this.setFieldTMs(currentTMs); + } + + @Override + public void randomizeFieldItems(Settings settings) { + boolean banBadItems = settings.isBanBadRandomFieldItems(); + boolean distributeItemsControl = settings.getFieldItemsMod() == Settings.FieldItemsMod.RANDOM_EVEN; + boolean uniqueItems = !settings.isBalanceShopPrices(); + + ItemList possibleItems = banBadItems ? this.getNonBadItems().copy() : this.getAllowedItems().copy(); + List currentItems = this.getRegularFieldItems(); + List currentTMs = this.getCurrentFieldTMs(); + List requiredTMs = this.getRequiredFieldTMs(); + List uniqueNoSellItems = this.getUniqueNoSellItems(); + // System.out.println("distributeItemsControl: "+ distributeItemsControl); + + int fieldItemCount = currentItems.size(); + int fieldTMCount = currentTMs.size(); + int reqTMCount = requiredTMs.size(); + int totalTMCount = this.getTMCount(); + + List newItems = new ArrayList<>(); + List newTMs = new ArrayList<>(requiredTMs); + + // List chosenItems = new ArrayList(); // collecting chosenItems for later process + + if (distributeItemsControl) { + for (int i = 0; i < fieldItemCount; i++) { + int chosenItem = possibleItems.randomNonTM(this.random); + int iterNum = 0; + while ((this.getItemPlacementHistory(chosenItem) > this.getItemPlacementAverage()) && iterNum < 100) { + chosenItem = possibleItems.randomNonTM(this.random); + iterNum +=1; + } + newItems.add(chosenItem); + if (uniqueItems && uniqueNoSellItems.contains(chosenItem)) { + possibleItems.banSingles(chosenItem); + } else { + this.setItemPlacementHistory(chosenItem); + } + } + } else { + for (int i = 0; i < fieldItemCount; i++) { + int chosenItem = possibleItems.randomNonTM(this.random); + newItems.add(chosenItem); + if (uniqueItems && uniqueNoSellItems.contains(chosenItem)) { + possibleItems.banSingles(chosenItem); + } + } + } + + for (int i = reqTMCount; i < fieldTMCount; i++) { + while (true) { + int tm = this.random.nextInt(totalTMCount) + 1; + if (!newTMs.contains(tm)) { + newTMs.add(tm); + break; + } + } + } + + + Collections.shuffle(newItems, this.random); + Collections.shuffle(newTMs, this.random); + + this.setRegularFieldItems(newItems); + this.setFieldTMs(newTMs); + } + + @Override + public void randomizeIngameTrades(Settings settings) { + boolean randomizeRequest = settings.getInGameTradesMod() == Settings.InGameTradesMod.RANDOMIZE_GIVEN_AND_REQUESTED; + boolean randomNickname = settings.isRandomizeInGameTradesNicknames(); + boolean randomOT = settings.isRandomizeInGameTradesOTs(); + boolean randomStats = settings.isRandomizeInGameTradesIVs(); + boolean randomItem = settings.isRandomizeInGameTradesItems(); + CustomNamesSet customNames = settings.getCustomNames(); + + checkPokemonRestrictions(); + // Process trainer names + List trainerNames = new ArrayList<>(); + // Check for the file + if (randomOT) { + int maxOT = this.maxTradeOTNameLength(); + for (String trainername : customNames.getTrainerNames()) { + int len = this.internalStringLength(trainername); + if (len <= maxOT && !trainerNames.contains(trainername)) { + trainerNames.add(trainername); + } + } + } + + // Process nicknames + List nicknames = new ArrayList<>(); + // Check for the file + if (randomNickname) { + int maxNN = this.maxTradeNicknameLength(); + for (String nickname : customNames.getPokemonNicknames()) { + int len = this.internalStringLength(nickname); + if (len <= maxNN && !nicknames.contains(nickname)) { + nicknames.add(nickname); + } + } + } + + // get old trades + List trades = this.getIngameTrades(); + List usedRequests = new ArrayList<>(); + List usedGivens = new ArrayList<>(); + List usedOTs = new ArrayList<>(); + List usedNicknames = new ArrayList<>(); + ItemList possibleItems = this.getAllowedItems(); + + int nickCount = nicknames.size(); + int trnameCount = trainerNames.size(); + + for (IngameTrade trade : trades) { + // pick new given pokemon + Pokemon oldgiven = trade.givenPokemon; + Pokemon given = this.randomPokemon(); + while (usedGivens.contains(given)) { + given = this.randomPokemon(); + } + usedGivens.add(given); + trade.givenPokemon = given; + + // requested pokemon? + if (oldgiven == trade.requestedPokemon) { + // preserve trades for the same pokemon + trade.requestedPokemon = given; + } else if (randomizeRequest) { + if (trade.requestedPokemon != null) { + Pokemon request = this.randomPokemon(); + while (usedRequests.contains(request) || request == given) { + request = this.randomPokemon(); + } + usedRequests.add(request); + trade.requestedPokemon = request; + } + } + + // nickname? + if (randomNickname && nickCount > usedNicknames.size()) { + String nickname = nicknames.get(this.random.nextInt(nickCount)); + while (usedNicknames.contains(nickname)) { + nickname = nicknames.get(this.random.nextInt(nickCount)); + } + usedNicknames.add(nickname); + trade.nickname = nickname; + } else if (trade.nickname.equalsIgnoreCase(oldgiven.name)) { + // change the name for sanity + trade.nickname = trade.givenPokemon.name; + } + + if (randomOT && trnameCount > usedOTs.size()) { + String ot = trainerNames.get(this.random.nextInt(trnameCount)); + while (usedOTs.contains(ot)) { + ot = trainerNames.get(this.random.nextInt(trnameCount)); + } + usedOTs.add(ot); + trade.otName = ot; + trade.otId = this.random.nextInt(65536); + } + + if (randomStats) { + int maxIV = this.hasDVs() ? 16 : 32; + for (int i = 0; i < trade.ivs.length; i++) { + trade.ivs[i] = this.random.nextInt(maxIV); + } + } + + if (randomItem) { + trade.item = possibleItems.randomItem(this.random); + } + } + + // things that the game doesn't support should just be ignored + this.setIngameTrades(trades); + } + + @Override + public void condenseLevelEvolutions(int maxLevel, int maxIntermediateLevel) { + List allPokemon = this.getPokemon(); + // search for level evolutions + for (Pokemon pk : allPokemon) { + if (pk != null) { + for (Evolution checkEvo : pk.evolutionsFrom) { + if (checkEvo.type.usesLevel()) { + // If evo is intermediate and too high, bring it down + // Else if it's just too high, bring it down + if (checkEvo.extraInfo > maxIntermediateLevel && checkEvo.to.evolutionsFrom.size() > 0) { + checkEvo.extraInfo = maxIntermediateLevel; + addEvoUpdateCondensed(easierEvolutionUpdates, checkEvo, false); + } else if (checkEvo.extraInfo > maxLevel) { + checkEvo.extraInfo = maxLevel; + addEvoUpdateCondensed(easierEvolutionUpdates, checkEvo, false); + } + } + if (checkEvo.type == EvolutionType.LEVEL_UPSIDE_DOWN) { + checkEvo.type = EvolutionType.LEVEL; + addEvoUpdateCondensed(easierEvolutionUpdates, checkEvo, false); + } + } + } + } + + } + + @Override + public Set getImpossibleEvoUpdates() { + return impossibleEvolutionUpdates; + } + + @Override + public Set getEasierEvoUpdates() { + return easierEvolutionUpdates; + } + + @Override + public Set getTimeBasedEvoUpdates() { + return timeBasedEvolutionUpdates; + } + + @Override + public void randomizeEvolutions(Settings settings) { + boolean similarStrength = settings.isEvosSimilarStrength(); + boolean sameType = settings.isEvosSameTyping(); + boolean limitToThreeStages = settings.isEvosMaxThreeStages(); + boolean forceChange = settings.isEvosForceChange(); + boolean allowAltFormes = settings.isEvosAllowAltFormes(); + boolean banIrregularAltFormes = settings.isBanIrregularAltFormes(); + boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE; + + checkPokemonRestrictions(); + List pokemonPool; + if (this.altFormesCanHaveDifferentEvolutions()) { + pokemonPool = new ArrayList<>(mainPokemonListInclFormes); + } else { + pokemonPool = new ArrayList<>(mainPokemonList); + } + List actuallyCosmeticPokemonPool = new ArrayList<>(); + int stageLimit = limitToThreeStages ? 3 : 10; + + List banned = this.getBannedFormesForPlayerPokemon(); + if (!abilitiesAreRandomized) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + banned.addAll(getIrregularFormes()); + } + + for (int i = 0; i < pokemonPool.size(); i++) { + Pokemon pk = pokemonPool.get(i); + if (pk.actuallyCosmetic) { + pokemonPool.remove(pk); + i--; + actuallyCosmeticPokemonPool.add(pk); + } + } + + // Cache old evolutions for data later + Map> originalEvos = new HashMap<>(); + for (Pokemon pk : pokemonPool) { + originalEvos.put(pk, new ArrayList<>(pk.evolutionsFrom)); + } + + Set newEvoPairs = new HashSet<>(); + Set oldEvoPairs = new HashSet<>(); + + if (forceChange) { + for (Pokemon pk : pokemonPool) { + for (Evolution ev : pk.evolutionsFrom) { + oldEvoPairs.add(new EvolutionPair(ev.from, ev.to)); + if (generationOfPokemon() >= 7 && ev.from.number == Species.cosmoem) { // Special case for Cosmoem to add Lunala/Solgaleo since we remove the split evo + int oppositeVersionLegendary = ev.to.number == Species.solgaleo ? Species.lunala : Species.solgaleo; + Pokemon toPkmn = findPokemonInPoolWithSpeciesID(pokemonPool, oppositeVersionLegendary); + if (toPkmn != null) { + oldEvoPairs.add(new EvolutionPair(ev.from, toPkmn)); + } + } + } + } + } + + List replacements = new ArrayList<>(); + + int loops = 0; + while (loops < 1) { + // Setup for this loop. + boolean hadError = false; + for (Pokemon pk : pokemonPool) { + pk.evolutionsFrom.clear(); + pk.evolutionsTo.clear(); + } + newEvoPairs.clear(); + + // Shuffle pokemon list so the results aren't overly predictable. + Collections.shuffle(pokemonPool, this.random); + + for (Pokemon fromPK : pokemonPool) { + List oldEvos = originalEvos.get(fromPK); + for (Evolution ev : oldEvos) { + // Pick a Pokemon as replacement + replacements.clear(); + + List chosenList = + allowAltFormes ? + mainPokemonListInclFormes + .stream() + .filter(pk -> !pk.actuallyCosmetic) + .collect(Collectors.toList()) : + mainPokemonList; + // Step 1: base filters + for (Pokemon pk : chosenList) { + // Prevent evolving into oneself (mandatory) + if (pk == fromPK) { + continue; + } + + // Force same EXP curve (mandatory) + if (pk.growthCurve != fromPK.growthCurve) { + continue; + } + + // Prevent evolving into banned Pokemon (mandatory) + if (banned.contains(pk)) { + continue; + } + + EvolutionPair ep = new EvolutionPair(fromPK, pk); + // Prevent split evos choosing the same Pokemon + // (mandatory) + if (newEvoPairs.contains(ep)) { + continue; + } + + // Prevent evolving into old thing if flagged + if (forceChange && oldEvoPairs.contains(ep)) { + continue; + } + + // Prevent evolution that causes cycle (mandatory) + if (evoCycleCheck(fromPK, pk)) { + continue; + } + + // Prevent evolution that exceeds stage limit + Evolution tempEvo = new Evolution(fromPK, pk, false, EvolutionType.NONE, 0); + fromPK.evolutionsFrom.add(tempEvo); + pk.evolutionsTo.add(tempEvo); + boolean exceededLimit = false; + + Set related = relatedPokemon(fromPK); + + for (Pokemon pk2 : related) { + int numPreEvos = numPreEvolutions(pk2, stageLimit); + if (numPreEvos >= stageLimit) { + exceededLimit = true; + break; + } else if (numPreEvos == stageLimit - 1 && pk2.evolutionsFrom.size() == 0 + && originalEvos.get(pk2).size() > 0) { + exceededLimit = true; + break; + } + } + + fromPK.evolutionsFrom.remove(tempEvo); + pk.evolutionsTo.remove(tempEvo); + + if (exceededLimit) { + continue; + } + + // Passes everything, add as a candidate. + replacements.add(pk); + } + + // If we don't have any candidates after Step 1, severe + // failure + // exit out of this loop and try again from scratch + if (replacements.size() == 0) { + hadError = true; + break; + } + + // Step 2: filter by type, if needed + if (replacements.size() > 1 && sameType) { + Set includeType = new HashSet<>(); + for (Pokemon pk : replacements) { + // Special case for Eevee + if (fromPK.number == Species.eevee) { + if (pk.primaryType == ev.to.primaryType + || (pk.secondaryType != null) && pk.secondaryType == ev.to.primaryType) { + includeType.add(pk); + } + } else if (pk.primaryType == fromPK.primaryType + || (fromPK.secondaryType != null && pk.primaryType == fromPK.secondaryType) + || (pk.secondaryType != null && pk.secondaryType == fromPK.primaryType) + || (fromPK.secondaryType != null && pk.secondaryType != null && pk.secondaryType == fromPK.secondaryType)) { + includeType.add(pk); + } + } + + if (includeType.size() != 0) { + replacements.retainAll(includeType); + } + } + + if (!alreadyPicked.containsAll(replacements) && !similarStrength) { + replacements.removeAll(alreadyPicked); + } + + // Step 3: pick - by similar strength or otherwise + Pokemon picked; + + if (replacements.size() == 1) { + // Foregone conclusion. + picked = replacements.get(0); + alreadyPicked.add(picked); + } else if (similarStrength) { + picked = pickEvoPowerLvlReplacement(replacements, ev.to); + alreadyPicked.add(picked); + } else { + picked = replacements.get(this.random.nextInt(replacements.size())); + alreadyPicked.add(picked); + } + + // Step 4: add it to the new evos pool + Evolution newEvo = new Evolution(fromPK, picked, ev.carryStats, ev.type, ev.extraInfo); + boolean checkCosmetics = true; + if (picked.formeNumber > 0) { + newEvo.forme = picked.formeNumber; + newEvo.formeSuffix = picked.formeSuffix; + checkCosmetics = false; + } + if (checkCosmetics && newEvo.to.cosmeticForms > 0) { + newEvo.forme = newEvo.to.getCosmeticFormNumber(this.random.nextInt(newEvo.to.cosmeticForms)); + } else if (!checkCosmetics && picked.cosmeticForms > 0) { + newEvo.forme += picked.getCosmeticFormNumber(this.random.nextInt(picked.cosmeticForms)); + } + if (newEvo.type == EvolutionType.LEVEL_FEMALE_ESPURR) { + newEvo.type = EvolutionType.LEVEL_FEMALE_ONLY; + } + fromPK.evolutionsFrom.add(newEvo); + picked.evolutionsTo.add(newEvo); + newEvoPairs.add(new EvolutionPair(fromPK, picked)); + } + + if (hadError) { + // No need to check the other Pokemon if we already errored + break; + } + } + + // If no error, done and return + if (!hadError) { + for (Pokemon pk: actuallyCosmeticPokemonPool) { + pk.copyBaseFormeEvolutions(pk.baseForme); + } + return; + } else { + loops++; + } + } + + // If we made it out of the loop, we weren't able to randomize evos. + throw new RandomizationException("Not able to randomize evolutions in a sane amount of retries."); + } + + @Override + public void randomizeEvolutionsEveryLevel(Settings settings) { + boolean sameType = settings.isEvosSameTyping(); + boolean forceChange = settings.isEvosForceChange(); + boolean allowAltFormes = settings.isEvosAllowAltFormes(); + boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE; + + checkPokemonRestrictions(); + List pokemonPool; + if (this.altFormesCanHaveDifferentEvolutions()) { + pokemonPool = new ArrayList<>(mainPokemonListInclFormes); + } else { + pokemonPool = new ArrayList<>(mainPokemonList); + } + List actuallyCosmeticPokemonPool = new ArrayList<>(); + + List banned = this.getBannedFormesForPlayerPokemon(); + if (!abilitiesAreRandomized) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + + for (int i = 0; i < pokemonPool.size(); i++) { + Pokemon pk = pokemonPool.get(i); + if (pk.actuallyCosmetic) { + pokemonPool.remove(pk); + i--; + actuallyCosmeticPokemonPool.add(pk); + } + } + + Set oldEvoPairs = new HashSet<>(); + + if (forceChange) { + for (Pokemon pk : pokemonPool) { + for (Evolution ev : pk.evolutionsFrom) { + oldEvoPairs.add(new EvolutionPair(ev.from, ev.to)); + if (generationOfPokemon() >= 7 && ev.from.number == Species.cosmoem) { // Special case for Cosmoem to add Lunala/Solgaleo since we remove the split evo + int oppositeVersionLegendary = ev.to.number == Species.solgaleo ? Species.lunala : Species.solgaleo; + Pokemon toPkmn = findPokemonInPoolWithSpeciesID(pokemonPool, oppositeVersionLegendary); + if (toPkmn != null) { + oldEvoPairs.add(new EvolutionPair(ev.from, toPkmn)); + } + } + } + } + } + + List replacements = new ArrayList<>(); + + int loops = 0; + while (loops < 1) { + // Setup for this loop. + boolean hadError = false; + for (Pokemon pk : pokemonPool) { + pk.evolutionsFrom.clear(); + pk.evolutionsTo.clear(); + } + + // Shuffle pokemon list so the results aren't overly predictable. + Collections.shuffle(pokemonPool, this.random); + + for (Pokemon fromPK : pokemonPool) { + // Pick a Pokemon as replacement + replacements.clear(); + + List chosenList = + allowAltFormes ? + mainPokemonListInclFormes + .stream() + .filter(pk -> !pk.actuallyCosmetic) + .collect(Collectors.toList()) : + mainPokemonList; + // Step 1: base filters + for (Pokemon pk : chosenList) { + // Prevent evolving into oneself (mandatory) + if (pk == fromPK) { + continue; + } + + // Force same EXP curve (mandatory) + if (pk.growthCurve != fromPK.growthCurve) { + continue; + } + + // Prevent evolving into banned Pokemon (mandatory) + if (banned.contains(pk)) { + continue; + } + + // Prevent evolving into old thing if flagged + EvolutionPair ep = new EvolutionPair(fromPK, pk); + if (forceChange && oldEvoPairs.contains(ep)) { + continue; + } + + // Passes everything, add as a candidate. + replacements.add(pk); + } + + // If we don't have any candidates after Step 1, severe failure + // exit out of this loop and try again from scratch + if (replacements.size() == 0) { + hadError = true; + break; + } + + // Step 2: filter by type, if needed + if (replacements.size() > 1 && sameType) { + Set includeType = new HashSet<>(); + for (Pokemon pk : replacements) { + if (pk.primaryType == fromPK.primaryType + || (fromPK.secondaryType != null && pk.primaryType == fromPK.secondaryType) + || (pk.secondaryType != null && pk.secondaryType == fromPK.primaryType) + || (pk.secondaryType != null && pk.secondaryType == fromPK.secondaryType)) { + includeType.add(pk); + } + } + + if (includeType.size() != 0) { + replacements.retainAll(includeType); + } + } + + // Step 3: pick - by similar strength or otherwise + Pokemon picked; + + if (replacements.size() == 1) { + // Foregone conclusion. + picked = replacements.get(0); + } else { + picked = replacements.get(this.random.nextInt(replacements.size())); + } + + // Step 4: create new level 1 evo and add it to the new evos pool + Evolution newEvo = new Evolution(fromPK, picked, false, EvolutionType.LEVEL, 1); + newEvo.level = 1; + boolean checkCosmetics = true; + if (picked.formeNumber > 0) { + newEvo.forme = picked.formeNumber; + newEvo.formeSuffix = picked.formeSuffix; + checkCosmetics = false; + } + if (checkCosmetics && newEvo.to.cosmeticForms > 0) { + newEvo.forme = newEvo.to.getCosmeticFormNumber(this.random.nextInt(newEvo.to.cosmeticForms)); + } else if (!checkCosmetics && picked.cosmeticForms > 0) { + newEvo.forme += picked.getCosmeticFormNumber(this.random.nextInt(picked.cosmeticForms)); + } + fromPK.evolutionsFrom.add(newEvo); + picked.evolutionsTo.add(newEvo); + } + + // If no error, done and return + if (!hadError) { + for (Pokemon pk: actuallyCosmeticPokemonPool) { + pk.copyBaseFormeEvolutions(pk.baseForme); + } + return; + } else { + loops++; + } + } + + // If we made it out of the loop, we weren't able to randomize evos. + throw new RandomizationException("Not able to randomize evolutions in a sane amount of retries."); + } + + @Override + public void changeCatchRates(Settings settings) { + int minimumCatchRateLevel = settings.getMinimumCatchRateLevel(); + + if (minimumCatchRateLevel == 5) { + enableGuaranteedPokemonCatching(); + } else { + int normalMin, legendaryMin; + switch (minimumCatchRateLevel) { + case 1: + default: + normalMin = 75; + legendaryMin = 37; + break; + case 2: + normalMin = 128; + legendaryMin = 64; + break; + case 3: + normalMin = 200; + legendaryMin = 100; + break; + case 4: + normalMin = legendaryMin = 255; + break; + } + minimumCatchRate(normalMin, legendaryMin); + } + } + + @Override + public void shuffleShopItems() { + Map currentItems = this.getShopItems(); + if (currentItems == null) return; + List itemList = new ArrayList<>(); + for (Shop shop: currentItems.values()) { + itemList.addAll(shop.items); + } + Collections.shuffle(itemList, this.random); + + Iterator itemListIter = itemList.iterator(); + + for (Shop shop: currentItems.values()) { + for (int i = 0; i < shop.items.size(); i++) { + shop.items.remove(i); + shop.items.add(i, itemListIter.next()); + } + } + + this.setShopItems(currentItems); + } + + // Note: If you use this on a game where the amount of randomizable shop items is greater than the amount of + // possible items, you will get owned by the while loop + @Override + public void randomizeShopItems(Settings settings) { + boolean banBadItems = settings.isBanBadRandomShopItems(); + boolean banRegularShopItems = settings.isBanRegularShopItems(); + boolean banOPShopItems = settings.isBanOPShopItems(); + boolean balancePrices = settings.isBalanceShopPrices(); + boolean placeEvolutionItems = settings.isGuaranteeEvolutionItems(); + boolean placeXItems = settings.isGuaranteeXItems(); + + if (this.getShopItems() == null) return; + ItemList possibleItems = banBadItems ? this.getNonBadItems() : this.getAllowedItems(); + if (banRegularShopItems) { + possibleItems.banSingles(this.getRegularShopItems().stream().mapToInt(Integer::intValue).toArray()); + } + if (banOPShopItems) { + possibleItems.banSingles(this.getOPShopItems().stream().mapToInt(Integer::intValue).toArray()); + } + Map currentItems = this.getShopItems(); + + int shopItemCount = currentItems.values().stream().mapToInt(s -> s.items.size()).sum(); + + List newItems = new ArrayList<>(); + Map newItemsMap = new TreeMap<>(); + int newItem; + List guaranteedItems = new ArrayList<>(); + if (placeEvolutionItems) { + guaranteedItems.addAll(getEvolutionItems()); + } + if (placeXItems) { + guaranteedItems.addAll(getXItems()); + } + if (placeEvolutionItems || placeXItems) { + newItems.addAll(guaranteedItems); + shopItemCount = shopItemCount - newItems.size(); + + for (int i = 0; i < shopItemCount; i++) { + while (newItems.contains(newItem = possibleItems.randomNonTM(this.random))); + newItems.add(newItem); + } + + // Guarantee main-game + List mainGameShops = new ArrayList<>(); + List nonMainGameShops = new ArrayList<>(); + for (int i: currentItems.keySet()) { + if (currentItems.get(i).isMainGame) { + mainGameShops.add(i); + } else { + nonMainGameShops.add(i); + } + } + + // Place items in non-main-game shops; skip over guaranteed items + Collections.shuffle(newItems, this.random); + for (int i: nonMainGameShops) { + int j = 0; + List newShopItems = new ArrayList<>(); + Shop oldShop = currentItems.get(i); + for (Integer ignored: oldShop.items) { + Integer item = newItems.get(j); + while (guaranteedItems.contains(item)) { + j++; + item = newItems.get(j); + } + newShopItems.add(item); + newItems.remove(item); + } + Shop shop = new Shop(oldShop); + shop.items = newShopItems; + newItemsMap.put(i, shop); + } + + // Place items in main-game shops + Collections.shuffle(newItems, this.random); + for (int i: mainGameShops) { + List newShopItems = new ArrayList<>(); + Shop oldShop = currentItems.get(i); + for (Integer ignored: oldShop.items) { + Integer item = newItems.get(0); + newShopItems.add(item); + newItems.remove(0); + } + Shop shop = new Shop(oldShop); + shop.items = newShopItems; + newItemsMap.put(i, shop); + } + } else { + for (int i = 0; i < shopItemCount; i++) { + while (newItems.contains(newItem = possibleItems.randomNonTM(this.random))); + newItems.add(newItem); + } + + Iterator newItemsIter = newItems.iterator(); + + for (int i: currentItems.keySet()) { + List newShopItems = new ArrayList<>(); + Shop oldShop = currentItems.get(i); + for (Integer ignored: oldShop.items) { + newShopItems.add(newItemsIter.next()); + } + Shop shop = new Shop(oldShop); + shop.items = newShopItems; + newItemsMap.put(i, shop); + } + } + + this.setShopItems(newItemsMap); + if (balancePrices) { + this.setShopPrices(); + } + } + + @Override + public void randomizePickupItems(Settings settings) { + boolean banBadItems = settings.isBanBadRandomPickupItems(); + + ItemList possibleItems = banBadItems ? this.getNonBadItems() : this.getAllowedItems(); + List currentItems = this.getPickupItems(); + List newItems = new ArrayList<>(); + for (int i = 0; i < currentItems.size(); i++) { + int item; + if (this.generationOfPokemon() == 3 || this.generationOfPokemon() == 4) { + // Allow TMs in Gen 3/4 since they aren't infinite (and you get TMs from Pickup in the vanilla game) + item = possibleItems.randomItem(this.random); + } else { + item = possibleItems.randomNonTM(this.random); + } + PickupItem pickupItem = new PickupItem(item); + pickupItem.probabilities = Arrays.copyOf(currentItems.get(i).probabilities, currentItems.size()); + newItems.add(pickupItem); + } + + this.setPickupItems(newItems); + } + + @Override + public void minimumCatchRate(int rateNonLegendary, int rateLegendary) { + List pokes = getPokemonInclFormes(); + for (Pokemon pkmn : pokes) { + if (pkmn == null) { + continue; + } + int minCatchRate = pkmn.isLegendary() ? rateLegendary : rateNonLegendary; + pkmn.catchRate = Math.max(pkmn.catchRate, minCatchRate); + } + + } + + @Override + public void standardizeEXPCurves(Settings settings) { + Settings.ExpCurveMod mod = settings.getExpCurveMod(); + ExpCurve expCurve = settings.getSelectedEXPCurve(); + + List pokes = getPokemonInclFormes(); + switch (mod) { + case LEGENDARIES: + for (Pokemon pkmn : pokes) { + if (pkmn == null) { + continue; + } + pkmn.growthCurve = pkmn.isLegendary() ? ExpCurve.SLOW : expCurve; + } + break; + case STRONG_LEGENDARIES: + for (Pokemon pkmn : pokes) { + if (pkmn == null) { + continue; + } + pkmn.growthCurve = pkmn.isStrongLegendary() ? ExpCurve.SLOW : expCurve; + } + break; + case ALL: + for (Pokemon pkmn : pokes) { + if (pkmn == null) { + continue; + } + pkmn.growthCurve = expCurve; + } + break; + } + } + + /* Private methods/structs used internally by the above methods */ + + private void updateMovePower(List moves, int moveNum, int power) { + Move mv = moves.get(moveNum); + if (mv.power != power) { + mv.power = power; + addMoveUpdate(moveNum, 0); + } + } + + private void updateMovePP(List moves, int moveNum, int pp) { + Move mv = moves.get(moveNum); + if (mv.pp != pp) { + mv.pp = pp; + addMoveUpdate(moveNum, 1); + } + } + + private void updateMoveAccuracy(List moves, int moveNum, int accuracy) { + Move mv = moves.get(moveNum); + if (Math.abs(mv.hitratio - accuracy) >= 1) { + mv.hitratio = accuracy; + addMoveUpdate(moveNum, 2); + } + } + + private void updateMoveType(List moves, int moveNum, Type type) { + Move mv = moves.get(moveNum); + if (mv.type != type) { + mv.type = type; + addMoveUpdate(moveNum, 3); + } + } + + private void updateMoveCategory(List moves, int moveNum, MoveCategory category) { + Move mv = moves.get(moveNum); + if (mv.category != category) { + mv.category = category; + addMoveUpdate(moveNum, 4); + } + } + + private void addMoveUpdate(int moveNum, int updateType) { + if (!moveUpdates.containsKey(moveNum)) { + boolean[] updateField = new boolean[5]; + updateField[updateType] = true; + moveUpdates.put(moveNum, updateField); + } else { + moveUpdates.get(moveNum)[updateType] = true; + } + } + + protected Set impossibleEvolutionUpdates = new TreeSet<>(); + protected Set timeBasedEvolutionUpdates = new TreeSet<>(); + protected Set easierEvolutionUpdates = new TreeSet<>(); + + protected void addEvoUpdateLevel(Set evolutionUpdates, Evolution evo) { + Pokemon pkFrom = evo.from; + Pokemon pkTo = evo.to; + int level = evo.extraInfo; + evolutionUpdates.add(new EvolutionUpdate(pkFrom, pkTo, EvolutionType.LEVEL, String.valueOf(level), + false, false)); + } + + protected void addEvoUpdateStone(Set evolutionUpdates, Evolution evo, String item) { + Pokemon pkFrom = evo.from; + Pokemon pkTo = evo.to; + evolutionUpdates.add(new EvolutionUpdate(pkFrom, pkTo, EvolutionType.STONE, item, + false, false)); + } + + protected void addEvoUpdateHappiness(Set evolutionUpdates, Evolution evo) { + Pokemon pkFrom = evo.from; + Pokemon pkTo = evo.to; + evolutionUpdates.add(new EvolutionUpdate(pkFrom, pkTo, EvolutionType.HAPPINESS, "", + false, false)); + } + + protected void addEvoUpdateHeldItem(Set evolutionUpdates, Evolution evo, String item) { + Pokemon pkFrom = evo.from; + Pokemon pkTo = evo.to; + evolutionUpdates.add(new EvolutionUpdate(pkFrom, pkTo, EvolutionType.LEVEL_ITEM_DAY, item, + false, false)); + } + + protected void addEvoUpdateParty(Set evolutionUpdates, Evolution evo, String otherPk) { + Pokemon pkFrom = evo.from; + Pokemon pkTo = evo.to; + evolutionUpdates.add(new EvolutionUpdate(pkFrom, pkTo, EvolutionType.LEVEL_WITH_OTHER, otherPk, + false, false)); + } + + protected void addEvoUpdateCondensed(Set evolutionUpdates, Evolution evo, boolean additional) { + Pokemon pkFrom = evo.from; + Pokemon pkTo = evo.to; + int level = evo.extraInfo; + evolutionUpdates.add(new EvolutionUpdate(pkFrom, pkTo, EvolutionType.LEVEL, String.valueOf(level), + true, additional)); + } + + private Pokemon pickEvoPowerLvlReplacement(List pokemonPool, Pokemon current) { + // start with within 10% and add 5% either direction till we find + // something + int currentBST = current.bstForPowerLevels(); + int minTarget = currentBST - currentBST / 10; + int maxTarget = currentBST + currentBST / 10; + List canPick = new ArrayList<>(); + List emergencyPick = new ArrayList<>(); + int expandRounds = 0; + while (canPick.isEmpty() || (canPick.size() < 3 && expandRounds < 3)) { + for (Pokemon pk : pokemonPool) { + if (pk.bstForPowerLevels() >= minTarget && pk.bstForPowerLevels() <= maxTarget && !canPick.contains(pk) && !emergencyPick.contains(pk)) { + if (alreadyPicked.contains(pk)) { + emergencyPick.add(pk); + } else { + canPick.add(pk); + } + } + } + if (expandRounds >= 2 && canPick.isEmpty()) { + canPick.addAll(emergencyPick); + } + minTarget -= currentBST / 20; + maxTarget += currentBST / 20; + expandRounds++; + } + return canPick.get(this.random.nextInt(canPick.size())); + } + + // Note that this is slow and somewhat hacky. + private Pokemon findPokemonInPoolWithSpeciesID(List pokemonPool, int speciesID) { + for (int i = 0; i < pokemonPool.size(); i++) { + if (pokemonPool.get(i).number == speciesID) { + return pokemonPool.get(i); + } + } + return null; + } + + private List getEvolutionaryRelatives(Pokemon pk) { + List evolutionaryRelatives = new ArrayList<>(); + for (Evolution ev : pk.evolutionsFrom) { + if (!evolutionaryRelatives.contains(ev.to)) { + Pokemon evo = ev.to; + evolutionaryRelatives.add(evo); + Queue evolutionsList = new LinkedList<>(); + evolutionsList.addAll(evo.evolutionsFrom); + while (evolutionsList.size() > 0) { + evo = evolutionsList.remove().to; + if (!evolutionaryRelatives.contains(evo)) { + evolutionaryRelatives.add(evo); + evolutionsList.addAll(evo.evolutionsFrom); + } + } + } + } + + for (Evolution ev : pk.evolutionsTo) { + if (!evolutionaryRelatives.contains(ev.from)) { + Pokemon preEvo = ev.from; + evolutionaryRelatives.add(preEvo); + + // At this point, preEvo is basically the "parent" of pk. Run + // getEvolutionaryRelatives on preEvo in order to get pk's + // "sibling" evolutions too. For example, if pk is Espeon, then + // preEvo here will be Eevee, and this will add all the other + // eeveelutions to the relatives list. + List relativesForPreEvo = getEvolutionaryRelatives(preEvo); + for (Pokemon preEvoRelative : relativesForPreEvo) { + if (!evolutionaryRelatives.contains(preEvoRelative)) { + evolutionaryRelatives.add(preEvoRelative); + } + } + + while (preEvo.evolutionsTo.size() > 0) { + preEvo = preEvo.evolutionsTo.get(0).from; + if (!evolutionaryRelatives.contains(preEvo)) { + evolutionaryRelatives.add(preEvo); + + // Similar to above, get the "sibling" evolutions here too. + relativesForPreEvo = getEvolutionaryRelatives(preEvo); + for (Pokemon preEvoRelative : relativesForPreEvo) { + if (!evolutionaryRelatives.contains(preEvoRelative)) { + evolutionaryRelatives.add(preEvoRelative); + } + } + } + } + } + } + + return evolutionaryRelatives; + } + + private static class EvolutionPair { + private Pokemon from; + private Pokemon to; + + EvolutionPair(Pokemon from, Pokemon to) { + this.from = from; + this.to = to; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((from == null) ? 0 : from.hashCode()); + result = prime * result + ((to == null) ? 0 : to.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EvolutionPair other = (EvolutionPair) obj; + if (from == null) { + if (other.from != null) + return false; + } else if (!from.equals(other.from)) + return false; + if (to == null) { + return other.to == null; + } else return to.equals(other.to); + } + } + + /** + * Check whether adding an evolution from one Pokemon to another will cause + * an evolution cycle. + * + * @param from Pokemon that is evolving + * @param to Pokemon to evolve to + * @return True if there is an evolution cycle, else false + */ + private boolean evoCycleCheck(Pokemon from, Pokemon to) { + Evolution tempEvo = new Evolution(from, to, false, EvolutionType.NONE, 0); + from.evolutionsFrom.add(tempEvo); + Set visited = new HashSet<>(); + Set recStack = new HashSet<>(); + boolean recur = isCyclic(from, visited, recStack); + from.evolutionsFrom.remove(tempEvo); + return recur; + } + + private boolean isCyclic(Pokemon pk, Set visited, Set recStack) { + if (!visited.contains(pk)) { + visited.add(pk); + recStack.add(pk); + for (Evolution ev : pk.evolutionsFrom) { + if (!visited.contains(ev.to) && isCyclic(ev.to, visited, recStack)) { + return true; + } else if (recStack.contains(ev.to)) { + return true; + } + } + } + recStack.remove(pk); + return false; + } + + private interface BasePokemonAction { + void applyTo(Pokemon pk); + } + + private interface EvolvedPokemonAction { + void applyTo(Pokemon evFrom, Pokemon evTo, boolean toMonIsFinalEvo); + } + + private interface CosmeticFormAction { + void applyTo(Pokemon pk, Pokemon baseForme); + } + + /** + * Universal implementation for things that have "copy X up evolutions" + * support. + * @param bpAction + * Method to run on all base or no-copy Pokemon + * @param epAction + * Method to run on all evolved Pokemon with a linear chain of + * @param copySplitEvos + * If true, treat split evolutions the same way as base Pokemon + */ + private void copyUpEvolutionsHelper(BasePokemonAction bpAction, EvolvedPokemonAction epAction, + EvolvedPokemonAction splitAction, boolean copySplitEvos) { + List allPokes = this.getPokemonInclFormes(); + for (Pokemon pk : allPokes) { + if (pk != null) { + pk.temporaryFlag = false; + } + } + + // Get evolution data. + Set basicPokes = RomFunctions.getBasicPokemon(this); + Set splitEvos = RomFunctions.getSplitEvolutions(this); + Set middleEvos = RomFunctions.getMiddleEvolutions(this, copySplitEvos); + + for (Pokemon pk : basicPokes) { + bpAction.applyTo(pk); + pk.temporaryFlag = true; + } + + if (!copySplitEvos) { + for (Pokemon pk : splitEvos) { + bpAction.applyTo(pk); + pk.temporaryFlag = true; + } + } + + // go "up" evolutions looking for pre-evos to do first + for (Pokemon pk : allPokes) { + if (pk != null && !pk.temporaryFlag) { + + // Non-randomized pokes at this point must have + // a linear chain of single evolutions down to + // a randomized poke. + Stack currentStack = new Stack<>(); + Evolution ev = pk.evolutionsTo.get(0); + while (!ev.from.temporaryFlag) { + currentStack.push(ev); + ev = ev.from.evolutionsTo.get(0); + } + + // Now "ev" is set to an evolution from a Pokemon that has had + // the base action done on it to one that hasn't. + // Do the evolution action for everything left on the stack. + + if (copySplitEvos && splitAction != null && splitEvos.contains(ev.to)) { + splitAction.applyTo(ev.from, ev.to, !middleEvos.contains(ev.to)); + } else { + epAction.applyTo(ev.from, ev.to, !middleEvos.contains(ev.to)); + } + ev.to.temporaryFlag = true; + while (!currentStack.isEmpty()) { + ev = currentStack.pop(); + if (copySplitEvos && splitAction != null && splitEvos.contains(pk)) { + splitAction.applyTo(ev.from, ev.to, !middleEvos.contains(ev.to)); + } else { + epAction.applyTo(ev.from, ev.to, !middleEvos.contains(ev.to)); + } + ev.to.temporaryFlag = true; + } + + } + } + } + + private void copyUpEvolutionsHelper(BasePokemonAction bpAction, EvolvedPokemonAction epAction) { + copyUpEvolutionsHelper(bpAction, epAction, null, false); + } + + private boolean checkForUnusedMove(List potentialList, List alreadyUsed) { + for (Move mv : potentialList) { + if (!alreadyUsed.contains(mv.number)) { + return true; + } + } + return false; + } + + private List pokemonOfType(Type type, boolean noLegendaries) { + List typedPokes = new ArrayList<>(); + for (Pokemon pk : mainPokemonList) { + if (pk != null && (!noLegendaries || !pk.isLegendary()) && !pk.actuallyCosmetic) { + if (pk.primaryType == type || pk.secondaryType == type) { + typedPokes.add(pk); + } + } + } + return typedPokes; + } + + private List pokemonOfTypeInclFormes(Type type, boolean noLegendaries) { + List typedPokes = new ArrayList<>(); + for (Pokemon pk : mainPokemonListInclFormes) { + if (pk != null && !pk.actuallyCosmetic && (!noLegendaries || !pk.isLegendary())) { + if (pk.primaryType == type || pk.secondaryType == type) { + typedPokes.add(pk); + } + } + } + return typedPokes; + } + + private List allPokemonWithoutNull() { + List allPokes = new ArrayList<>(this.getPokemon()); + allPokes.remove(0); + return allPokes; + } + + private List allPokemonInclFormesWithoutNull() { + List allPokes = new ArrayList<>(this.getPokemonInclFormes()); + allPokes.remove(0); + return allPokes; + } + + private Set pokemonInArea(EncounterSet area) { + Set inArea = new TreeSet<>(); + for (Encounter enc : area.encounters) { + inArea.add(enc.pokemon); + } + return inArea; + } + + private Map typeWeightings; + private int totalTypeWeighting; + + private Type pickType(boolean weightByFrequency, boolean noLegendaries, boolean allowAltFormes) { + if (totalTypeWeighting == 0) { + // Determine weightings + for (Type t : Type.values()) { + if (typeInGame(t)) { + List pokemonOfType = allowAltFormes ? pokemonOfTypeInclFormes(t, noLegendaries) : + pokemonOfType(t, noLegendaries); + int pkWithTyping = pokemonOfType.size(); + typeWeightings.put(t, pkWithTyping); + totalTypeWeighting += pkWithTyping; + } + } + } + + if (weightByFrequency) { + int typePick = this.random.nextInt(totalTypeWeighting); + int typePos = 0; + for (Type t : typeWeightings.keySet()) { + int weight = typeWeightings.get(t); + if (typePos + weight > typePick) { + return t; + } + typePos += weight; + } + return null; + } else { + return randomType(); + } + } + + private void rivalCarriesStarterUpdate(List currentTrainers, String prefix, int pokemonOffset) { + // Find the highest rival battle # + int highestRivalNum = 0; + for (Trainer t : currentTrainers) { + if (t.tag != null && t.tag.startsWith(prefix)) { + highestRivalNum = Math.max(highestRivalNum, + Integer.parseInt(t.tag.substring(prefix.length(), t.tag.indexOf('-')))); + } + } + + if (highestRivalNum == 0) { + // This rival type not used in this game + return; + } + + // Get the starters + // us 0 1 2 => them 0+n 1+n 2+n + List starters = this.getStarters(); + + // Yellow needs its own case, unfortunately. + if (isYellow()) { + // The rival's starter is index 1 + Pokemon rivalStarter = starters.get(1); + int timesEvolves = numEvolutions(rivalStarter, 2); + // Yellow does not have abilities + int abilitySlot = 0; + // Apply evolutions as appropriate + if (timesEvolves == 0) { + for (int j = 1; j <= 3; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-0", rivalStarter, abilitySlot); + } + for (int j = 4; j <= 7; j++) { + for (int i = 0; i < 3; i++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, rivalStarter, abilitySlot); + } + } + } else if (timesEvolves == 1) { + for (int j = 1; j <= 3; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-0", rivalStarter, abilitySlot); + } + rivalStarter = pickRandomEvolutionOf(rivalStarter, false); + for (int j = 4; j <= 7; j++) { + for (int i = 0; i < 3; i++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, rivalStarter, abilitySlot); + } + } + } else if (timesEvolves == 2) { + for (int j = 1; j <= 2; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + 0, rivalStarter, abilitySlot); + } + rivalStarter = pickRandomEvolutionOf(rivalStarter, true); + changeStarterWithTag(currentTrainers, prefix + "3-0", rivalStarter, abilitySlot); + for (int i = 0; i < 3; i++) { + changeStarterWithTag(currentTrainers, prefix + "4-" + i, rivalStarter, abilitySlot); + } + rivalStarter = pickRandomEvolutionOf(rivalStarter, false); + for (int j = 5; j <= 7; j++) { + for (int i = 0; i < 3; i++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, rivalStarter, abilitySlot); + } + } + } + } else { + // Replace each starter as appropriate + // Use level to determine when to evolve, not number anymore + for (int i = 0; i < 3; i++) { + // Rival's starters are pokemonOffset over from each of ours + int starterToUse = (i + pokemonOffset) % 3; + Pokemon thisStarter = starters.get(starterToUse); + int timesEvolves = numEvolutions(thisStarter, 2); + int abilitySlot = getRandomAbilitySlot(thisStarter); + while (abilitySlot == 3) { + // Since starters never have hidden abilities, the rival's starter shouldn't either + abilitySlot = getRandomAbilitySlot(thisStarter); + } + // If a fully evolved pokemon, use throughout + // Otherwise split by evolutions as appropriate + if (timesEvolves == 0) { + for (int j = 1; j <= highestRivalNum; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter, abilitySlot); + } + } else if (timesEvolves == 1) { + int j = 1; + for (; j <= highestRivalNum / 2; j++) { + if (getLevelOfStarter(currentTrainers, prefix + j + "-" + i) >= 30) { + break; + } + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter, abilitySlot); + } + thisStarter = pickRandomEvolutionOf(thisStarter, false); + int evolvedAbilitySlot = getValidAbilitySlotFromOriginal(thisStarter, abilitySlot); + for (; j <= highestRivalNum; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter, evolvedAbilitySlot); + } + } else if (timesEvolves == 2) { + int j = 1; + for (; j <= highestRivalNum; j++) { + if (getLevelOfStarter(currentTrainers, prefix + j + "-" + i) >= 16) { + break; + } + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter, abilitySlot); + } + thisStarter = pickRandomEvolutionOf(thisStarter, true); + int evolvedAbilitySlot = getValidAbilitySlotFromOriginal(thisStarter, abilitySlot); + for (; j <= highestRivalNum; j++) { + if (getLevelOfStarter(currentTrainers, prefix + j + "-" + i) >= 36) { + break; + } + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter, evolvedAbilitySlot); + } + thisStarter = pickRandomEvolutionOf(thisStarter, false); + evolvedAbilitySlot = getValidAbilitySlotFromOriginal(thisStarter, abilitySlot); + for (; j <= highestRivalNum; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter, evolvedAbilitySlot); + } + } + } + } + + } + + private Pokemon pickRandomEvolutionOf(Pokemon base, boolean mustEvolveItself) { + // Used for "rival carries starter" + // Pick a random evolution of base Pokemon, subject to + // "must evolve itself" if appropriate. + List candidates = new ArrayList<>(); + for (Evolution ev : base.evolutionsFrom) { + if (!mustEvolveItself || ev.to.evolutionsFrom.size() > 0) { + candidates.add(ev.to); + } + } + + if (candidates.size() == 0) { + throw new RandomizationException("Random evolution called on a Pokemon without any usable evolutions."); + } + + return candidates.get(random.nextInt(candidates.size())); + } + + private int getLevelOfStarter(List currentTrainers, String tag) { + for (Trainer t : currentTrainers) { + if (t.tag != null && t.tag.equals(tag)) { + // Bingo, get highest level + // last pokemon is given priority +2 but equal priority + // = first pokemon wins, so its effectively +1 + // If it's tagged the same we can assume it's the same team + // just the opposite gender or something like that... + // So no need to check other trainers with same tag. + int highestLevel = t.pokemon.get(0).level; + int trainerPkmnCount = t.pokemon.size(); + for (int i = 1; i < trainerPkmnCount; i++) { + int levelBonus = (i == trainerPkmnCount - 1) ? 2 : 0; + if (t.pokemon.get(i).level + levelBonus > highestLevel) { + highestLevel = t.pokemon.get(i).level; + } + } + return highestLevel; + } + } + return 0; + } + + private void changeStarterWithTag(List currentTrainers, String tag, Pokemon starter, int abilitySlot) { + for (Trainer t : currentTrainers) { + if (t.tag != null && t.tag.equals(tag)) { + + // Bingo + TrainerPokemon bestPoke = t.pokemon.get(0); + + if (t.forceStarterPosition >= 0) { + bestPoke = t.pokemon.get(t.forceStarterPosition); + } else { + // Change the highest level pokemon, not the last. + // BUT: last gets +2 lvl priority (effectively +1) + // same as above, equal priority = earlier wins + int trainerPkmnCount = t.pokemon.size(); + for (int i = 1; i < trainerPkmnCount; i++) { + int levelBonus = (i == trainerPkmnCount - 1) ? 2 : 0; + if (t.pokemon.get(i).level + levelBonus > bestPoke.level) { + bestPoke = t.pokemon.get(i); + } + } + } + bestPoke.pokemon = starter; + setFormeForTrainerPokemon(bestPoke,starter); + bestPoke.resetMoves = true; + bestPoke.abilitySlot = abilitySlot; + } + } + + } + + // Return the max depth of pre-evolutions a Pokemon has + private int numPreEvolutions(Pokemon pk, int maxInterested) { + return numPreEvolutions(pk, 0, maxInterested); + } + + private int numPreEvolutions(Pokemon pk, int depth, int maxInterested) { + if (pk.evolutionsTo.size() == 0) { + return 0; + } else { + if (depth == maxInterested - 1) { + return 1; + } else { + int maxPreEvos = 0; + for (Evolution ev : pk.evolutionsTo) { + maxPreEvos = Math.max(maxPreEvos, numPreEvolutions(ev.from, depth + 1, maxInterested) + 1); + } + return maxPreEvos; + } + } + } + + private int numEvolutions(Pokemon pk, int maxInterested) { + return numEvolutions(pk, 0, maxInterested); + } + + private int numEvolutions(Pokemon pk, int depth, int maxInterested) { + if (pk.evolutionsFrom.size() == 0) { + return 0; + } else { + if (depth == maxInterested - 1) { + return 1; + } else { + int maxEvos = 0; + for (Evolution ev : pk.evolutionsFrom) { + maxEvos = Math.max(maxEvos, numEvolutions(ev.to, depth + 1, maxInterested) + 1); + } + return maxEvos; + } + } + } + + private Pokemon fullyEvolve(Pokemon pokemon, int trainerIndex) { + // If the fullyEvolvedRandomSeed hasn't been set yet, set it here. + if (this.fullyEvolvedRandomSeed == -1) { + this.fullyEvolvedRandomSeed = random.nextInt(GlobalConstants.LARGEST_NUMBER_OF_SPLIT_EVOS); + } + + Set seenMons = new HashSet<>(); + seenMons.add(pokemon); + + while (true) { + if (pokemon.evolutionsFrom.size() == 0) { + // fully evolved + break; + } + + // check for cyclic evolutions from what we've already seen + boolean cyclic = false; + for (Evolution ev : pokemon.evolutionsFrom) { + if (seenMons.contains(ev.to)) { + // cyclic evolution detected - bail now + cyclic = true; + break; + } + } + + if (cyclic) { + break; + } + + // We want to make split evolutions deterministic, but still random on a seed-to-seed basis. + // Therefore, we take a random value (which is generated once per seed) and add it to the trainer's + // index to get a pseudorandom number that can be used to decide which split to take. + int evolutionIndex = (this.fullyEvolvedRandomSeed + trainerIndex) % pokemon.evolutionsFrom.size(); + pokemon = pokemon.evolutionsFrom.get(evolutionIndex).to; + seenMons.add(pokemon); + } + + return pokemon; + } + + private Set relatedPokemon(Pokemon original) { + Set results = new HashSet<>(); + results.add(original); + Queue toCheck = new LinkedList<>(); + toCheck.add(original); + while (!toCheck.isEmpty()) { + Pokemon check = toCheck.poll(); + for (Evolution ev : check.evolutionsFrom) { + if (!results.contains(ev.to)) { + results.add(ev.to); + toCheck.add(ev.to); + } + } + for (Evolution ev : check.evolutionsTo) { + if (!results.contains(ev.from)) { + results.add(ev.from); + toCheck.add(ev.from); + } + } + } + return results; + } + + private Map> cachedReplacementLists; + private List cachedAllList; + private List bannedList = new ArrayList<>(); + private List usedAsUniqueList = new ArrayList<>(); + + + private Pokemon pickTrainerPokeReplacement(Pokemon current, boolean usePowerLevels, Type type, + boolean noLegendaries, boolean wonderGuardAllowed, + boolean usePlacementHistory, boolean swapMegaEvos, + boolean abilitiesAreRandomized, boolean allowAltFormes, + boolean banIrregularAltFormes) { + List pickFrom; + List withoutBannedPokemon; + + if (swapMegaEvos) { + pickFrom = megaEvolutionsList + .stream() + .filter(mega -> mega.method == 1) + .map(mega -> mega.from) + .distinct() + .collect(Collectors.toList()); + } else { + pickFrom = cachedAllList; + } + + if (usePlacementHistory) { + // "Distributed" settings + double placementAverage = getPlacementAverage(); + pickFrom = pickFrom + .stream() + .filter(pk -> getPlacementHistory(pk) < placementAverage * 2) + .collect(Collectors.toList()); + if (pickFrom.isEmpty()) { + pickFrom = cachedAllList; + } + } else if (type != null && cachedReplacementLists != null) { + // "Type Themed" settings + if (!cachedReplacementLists.containsKey(type)) { + List pokemonOfType = allowAltFormes ? pokemonOfTypeInclFormes(type, noLegendaries) : + pokemonOfType(type, noLegendaries); + pokemonOfType.removeAll(this.getBannedFormesForPlayerPokemon()); + if (!abilitiesAreRandomized) { + List abilityDependentFormes = getAbilityDependentFormes(); + pokemonOfType.removeAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + pokemonOfType.removeAll(getIrregularFormes()); + } + cachedReplacementLists.put(type, pokemonOfType); + } + if (swapMegaEvos) { + pickFrom = cachedReplacementLists.get(type) + .stream() + .filter(pickFrom::contains) + .collect(Collectors.toList()); + if (pickFrom.isEmpty()) { + pickFrom = cachedReplacementLists.get(type); + } + } else { + pickFrom = cachedReplacementLists.get(type); + } + } + + withoutBannedPokemon = pickFrom.stream().filter(pk -> !bannedList.contains(pk)).collect(Collectors.toList()); + if (!withoutBannedPokemon.isEmpty()) { + pickFrom = withoutBannedPokemon; + } + + if (usePowerLevels) { + // start with within 10% and add 5% either direction till we find + // something + int currentBST = current.bstForPowerLevels(); + int minTarget = currentBST - currentBST / 10; + int maxTarget = currentBST + currentBST / 10; + List canPick = new ArrayList<>(); + int expandRounds = 0; + while (canPick.isEmpty() || (canPick.size() < 3 && expandRounds < 2)) { + for (Pokemon pk : pickFrom) { + if (pk.bstForPowerLevels() >= minTarget + && pk.bstForPowerLevels() <= maxTarget + && (wonderGuardAllowed || (pk.ability1 != Abilities.wonderGuard + && pk.ability2 != Abilities.wonderGuard && pk.ability3 != Abilities.wonderGuard))) { + canPick.add(pk); + } + } + minTarget -= currentBST / 20; + maxTarget += currentBST / 20; + expandRounds++; + } + // If usePlacementHistory is True, then we need to do some + // extra checking to make sure the randomly chosen pokemon + // is actually below the current average placement + // if not, re-roll + + Pokemon chosenPokemon = canPick.get(this.random.nextInt(canPick.size())); + if (usePlacementHistory) { + double placementAverage = getPlacementAverage(); + List filteredPickList = canPick + .stream() + .filter(pk -> getPlacementHistory(pk) < placementAverage) + .collect(Collectors.toList()); + if (filteredPickList.isEmpty()) { + filteredPickList = canPick; + } + chosenPokemon = filteredPickList.get(this.random.nextInt(filteredPickList.size())); + } + return chosenPokemon; + } else { + if (wonderGuardAllowed) { + return pickFrom.get(this.random.nextInt(pickFrom.size())); + } else { + Pokemon pk = pickFrom.get(this.random.nextInt(pickFrom.size())); + while (pk.ability1 == Abilities.wonderGuard + || pk.ability2 == Abilities.wonderGuard + || pk.ability3 == Abilities.wonderGuard) { + pk = pickFrom.get(this.random.nextInt(pickFrom.size())); + } + return pk; + } + } + } + + private Pokemon pickWildPowerLvlReplacement(List pokemonPool, Pokemon current, boolean banSamePokemon, + List usedUp, int bstBalanceLevel) { + // start with within 10% and add 5% either direction till we find + // something + int balancedBST = bstBalanceLevel * 10 + 250; + int currentBST = Math.min(current.bstForPowerLevels(), balancedBST); + int minTarget = currentBST - currentBST / 10; + int maxTarget = currentBST + currentBST / 10; + List canPick = new ArrayList<>(); + int expandRounds = 0; + while (canPick.isEmpty() || (canPick.size() < 3 && expandRounds < 3)) { + for (Pokemon pk : pokemonPool) { + if (pk.bstForPowerLevels() >= minTarget && pk.bstForPowerLevels() <= maxTarget + && (!banSamePokemon || pk != current) && (usedUp == null || !usedUp.contains(pk)) + && !canPick.contains(pk)) { + canPick.add(pk); + } + } + minTarget -= currentBST / 20; + maxTarget += currentBST / 20; + expandRounds++; + } + return canPick.get(this.random.nextInt(canPick.size())); + } + + private void setFormeForEncounter(Encounter enc, Pokemon pk) { + boolean checkCosmetics = true; + enc.formeNumber = 0; + if (enc.pokemon.formeNumber > 0) { + enc.formeNumber = enc.pokemon.formeNumber; + enc.pokemon = enc.pokemon.baseForme; + checkCosmetics = false; + } + if (checkCosmetics && enc.pokemon.cosmeticForms > 0) { + enc.formeNumber = enc.pokemon.getCosmeticFormNumber(this.random.nextInt(enc.pokemon.cosmeticForms)); + } else if (!checkCosmetics && pk.cosmeticForms > 0) { + enc.formeNumber += pk.getCosmeticFormNumber(this.random.nextInt(pk.cosmeticForms)); + } + } + + private Map> mapZonesToEncounters(List encountersForAreas) { + Map> zonesToEncounters = new TreeMap<>(); + for (EncounterSet encountersInArea : encountersForAreas) { + if (zonesToEncounters.containsKey(encountersInArea.offset)) { + zonesToEncounters.get(encountersInArea.offset).add(encountersInArea); + } else { + List encountersForZone = new ArrayList<>(); + encountersForZone.add(encountersInArea); + zonesToEncounters.put(encountersInArea.offset, encountersForZone); + } + } + return zonesToEncounters; + } + + public Pokemon pickEntirelyRandomPokemon(boolean includeFormes, boolean noLegendaries, EncounterSet area, List banned) { + Pokemon result; + Pokemon randomNonLegendaryPokemon = includeFormes ? randomNonLegendaryPokemonInclFormes() : randomNonLegendaryPokemon(); + Pokemon randomPokemon = includeFormes ? randomPokemonInclFormes() : randomPokemon(); + result = noLegendaries ? randomNonLegendaryPokemon : randomPokemon; + while (result.actuallyCosmetic) { + randomNonLegendaryPokemon = includeFormes ? randomNonLegendaryPokemonInclFormes() : randomNonLegendaryPokemon(); + randomPokemon = includeFormes ? randomPokemonInclFormes() : randomPokemon(); + result = noLegendaries ? randomNonLegendaryPokemon : randomPokemon; + } + while (banned.contains(result) || area.bannedPokemon.contains(result)) { + randomNonLegendaryPokemon = includeFormes ? randomNonLegendaryPokemonInclFormes() : randomNonLegendaryPokemon(); + randomPokemon = includeFormes ? randomPokemonInclFormes() : randomPokemon(); + result = noLegendaries ? randomNonLegendaryPokemon : randomPokemon; + while (result.actuallyCosmetic) { + randomNonLegendaryPokemon = includeFormes ? randomNonLegendaryPokemonInclFormes() : randomNonLegendaryPokemon(); + randomPokemon = includeFormes ? randomPokemonInclFormes() : randomPokemon(); + result = noLegendaries ? randomNonLegendaryPokemon : randomPokemon; + } + } + return result; + } + + private Pokemon pickStaticPowerLvlReplacement(List pokemonPool, Pokemon current, boolean banSamePokemon, + boolean limitBST) { + // start with within 10% and add 5% either direction till we find + // something + int currentBST = current.bstForPowerLevels(); + int minTarget = limitBST ? currentBST - currentBST / 5 : currentBST - currentBST / 10; + int maxTarget = limitBST ? currentBST : currentBST + currentBST / 10; + List canPick = new ArrayList<>(); + int expandRounds = 0; + while (canPick.isEmpty() || (canPick.size() < 3 && expandRounds < 3)) { + for (Pokemon pk : pokemonPool) { + if (pk.bstForPowerLevels() >= minTarget && pk.bstForPowerLevels() <= maxTarget + && (!banSamePokemon || pk != current) && !canPick.contains(pk)) { + canPick.add(pk); + } + } + minTarget -= currentBST / 20; + maxTarget += currentBST / 20; + expandRounds++; + } + return canPick.get(this.random.nextInt(canPick.size())); + } + + @Override + public List getAbilityDependentFormes() { + List abilityDependentFormes = new ArrayList<>(); + for (int i = 0; i < mainPokemonListInclFormes.size(); i++) { + Pokemon pokemon = mainPokemonListInclFormes.get(i); + if (pokemon.baseForme != null) { + if (pokemon.baseForme.number == Species.castform) { + // All alternate Castform formes + abilityDependentFormes.add(pokemon); + } else if (pokemon.baseForme.number == Species.darmanitan && pokemon.formeNumber == 1) { + // Damanitan-Z + abilityDependentFormes.add(pokemon); + } else if (pokemon.baseForme.number == Species.aegislash) { + // Aegislash-B + abilityDependentFormes.add(pokemon); + } else if (pokemon.baseForme.number == Species.wishiwashi) { + // Wishiwashi-S + abilityDependentFormes.add(pokemon); + } + } + } + return abilityDependentFormes; + } + + @Override + public List getBannedFormesForPlayerPokemon() { + List bannedFormes = new ArrayList<>(); + for (int i = 0; i < mainPokemonListInclFormes.size(); i++) { + Pokemon pokemon = mainPokemonListInclFormes.get(i); + if (pokemon.baseForme != null) { + if (pokemon.baseForme.number == Species.giratina) { + // Giratina-O is banned because it reverts back to Altered Forme if + // equipped with any item that isn't the Griseous Orb. + bannedFormes.add(pokemon); + } else if (pokemon.baseForme.number == Species.shaymin) { + // Shaymin-S is banned because it reverts back to its original forme + // under a variety of circumstances, and can only be changed back + // with the Gracidea. + bannedFormes.add(pokemon); + } + } + } + return bannedFormes; + } + + @Override + public void randomizeTotemPokemon(Settings settings) { + boolean randomizeTotem = + settings.getTotemPokemonMod() == Settings.TotemPokemonMod.RANDOM || + settings.getTotemPokemonMod() == Settings.TotemPokemonMod.SIMILAR_STRENGTH; + boolean randomizeAllies = + settings.getAllyPokemonMod() == Settings.AllyPokemonMod.RANDOM || + settings.getAllyPokemonMod() == Settings.AllyPokemonMod.SIMILAR_STRENGTH; + boolean randomizeAuras = + settings.getAuraMod() == Settings.AuraMod.RANDOM || + settings.getAuraMod() == Settings.AuraMod.SAME_STRENGTH; + boolean similarStrengthTotem = settings.getTotemPokemonMod() == Settings.TotemPokemonMod.SIMILAR_STRENGTH; + boolean similarStrengthAllies = settings.getAllyPokemonMod() == Settings.AllyPokemonMod.SIMILAR_STRENGTH; + boolean similarStrengthAuras = settings.getAuraMod() == Settings.AuraMod.SAME_STRENGTH; + boolean randomizeHeldItems = settings.isRandomizeTotemHeldItems(); + int levelModifier = settings.isTotemLevelsModified() ? settings.getTotemLevelModifier() : 0; + boolean allowAltFormes = settings.isAllowTotemAltFormes(); + boolean banIrregularAltFormes = settings.isBanIrregularAltFormes(); + boolean abilitiesAreRandomized = settings.getAbilitiesMod() == Settings.AbilitiesMod.RANDOMIZE; + + checkPokemonRestrictions(); + List currentTotemPokemon = this.getTotemPokemon(); + List replacements = new ArrayList<>(); + List banned = this.bannedForStaticPokemon(); + if (!abilitiesAreRandomized) { + List abilityDependentFormes = getAbilityDependentFormes(); + banned.addAll(abilityDependentFormes); + } + if (banIrregularAltFormes) { + banned.addAll(getIrregularFormes()); + } + List listInclFormesExclCosmetics = + mainPokemonListInclFormes + .stream() + .filter(pk -> !pk.actuallyCosmetic) + .collect(Collectors.toList()); + List pokemonLeft = new ArrayList<>(!allowAltFormes ? mainPokemonList : listInclFormesExclCosmetics); + pokemonLeft.removeAll(banned); + for (TotemPokemon old : currentTotemPokemon) { + TotemPokemon newTotem = new TotemPokemon(); + newTotem.heldItem = old.heldItem; + if (randomizeTotem) { + Pokemon newPK; + Pokemon oldPK = old.pkmn; + if (old.forme > 0) { + oldPK = getAltFormeOfPokemon(oldPK, old.forme); + } + + if (similarStrengthTotem) { + newPK = pickStaticPowerLvlReplacement( + pokemonLeft, + oldPK, + true, + false); + } else { + newPK = pokemonLeft.remove(this.random.nextInt(pokemonLeft.size())); + } + + pokemonLeft.remove(newPK); + newTotem.pkmn = newPK; + setFormeForStaticEncounter(newTotem, newPK); + newTotem.resetMoves = true; + newTotem.level = old.level; + + if (levelModifier != 0) { + newTotem.level = Math.min(100, (int) Math.round(newTotem.level * (1 + levelModifier / 100.0))); + } + if (pokemonLeft.size() == 0) { + pokemonLeft.addAll(!allowAltFormes ? mainPokemonList : listInclFormesExclCosmetics); + pokemonLeft.removeAll(banned); + } + } else { + newTotem.pkmn = old.pkmn; + newTotem.level = old.level; + if (levelModifier != 0) { + newTotem.level = Math.min(100, (int) Math.round(newTotem.level * (1 + levelModifier / 100.0))); + } + setFormeForStaticEncounter(newTotem, newTotem.pkmn); + } + + if (randomizeAllies) { + for (Integer oldAllyIndex: old.allies.keySet()) { + StaticEncounter oldAlly = old.allies.get(oldAllyIndex); + StaticEncounter newAlly = new StaticEncounter(); + Pokemon newAllyPK; + Pokemon oldAllyPK = oldAlly.pkmn; + if (oldAlly.forme > 0) { + oldAllyPK = getAltFormeOfPokemon(oldAllyPK, oldAlly.forme); + } + if (similarStrengthAllies) { + newAllyPK = pickStaticPowerLvlReplacement( + pokemonLeft, + oldAllyPK, + true, + false); + } else { + newAllyPK = pokemonLeft.remove(this.random.nextInt(pokemonLeft.size())); + } + + pokemonLeft.remove(newAllyPK); + newAlly.pkmn = newAllyPK; + setFormeForStaticEncounter(newAlly, newAllyPK); + newAlly.resetMoves = true; + newAlly.level = oldAlly.level; + if (levelModifier != 0) { + newAlly.level = Math.min(100, (int) Math.round(newAlly.level * (1 + levelModifier / 100.0))); + } + + newTotem.allies.put(oldAllyIndex,newAlly); + if (pokemonLeft.size() == 0) { + pokemonLeft.addAll(!allowAltFormes ? mainPokemonList : listInclFormesExclCosmetics); + pokemonLeft.removeAll(banned); + } + } + } else { + newTotem.allies = old.allies; + for (StaticEncounter ally: newTotem.allies.values()) { + if (levelModifier != 0) { + ally.level = Math.min(100, (int) Math.round(ally.level * (1 + levelModifier / 100.0))); + setFormeForStaticEncounter(ally, ally.pkmn); + } + } + } + + if (randomizeAuras) { + if (similarStrengthAuras) { + newTotem.aura = Aura.randomAuraSimilarStrength(this.random, old.aura); + } else { + newTotem.aura = Aura.randomAura(this.random); + } + } else { + newTotem.aura = old.aura; + } + + if (randomizeHeldItems) { + if (old.heldItem != 0) { + List consumableList = getAllConsumableHeldItems(); + newTotem.heldItem = consumableList.get(this.random.nextInt(consumableList.size())); + } + } + + replacements.add(newTotem); + } + + // Save + this.setTotemPokemon(replacements); + } + + /* Helper methods used by subclasses and/or this class */ + + void checkPokemonRestrictions() { + if (!restrictionsSet) { + setPokemonPool(null); + } + } + + protected void applyCamelCaseNames() { + List pokes = getPokemon(); + for (Pokemon pkmn : pokes) { + if (pkmn == null) { + continue; + } + pkmn.name = RomFunctions.camelCase(pkmn.name); + } + + } + + private void setPlacementHistory(Pokemon newPK) { + Integer history = getPlacementHistory(newPK); + placementHistory.put(newPK, history + 1); + } + + private int getPlacementHistory(Pokemon newPK) { + return placementHistory.getOrDefault(newPK, 0); + } + + private double getPlacementAverage() { + return placementHistory.values().stream().mapToInt(e -> e).average().orElse(0); + } + + + private List getBelowAveragePlacements() { + // This method will return a PK if the number of times a pokemon has been + // placed is less than average of all placed pokemon's appearances + // E.g., Charmander's been placed once, but the average for all pokemon is 2.2 + // So add to list and return + + List toPlacePK = new ArrayList<>(); + List placedPK = new ArrayList<>(placementHistory.keySet()); + List allPK = cachedAllList; + int placedPKNum = 0; + for (Pokemon p : placedPK) { + placedPKNum += placementHistory.get(p); + } + float placedAverage = Math.round((float)placedPKNum / (float)placedPK.size()); + + + + if (placedAverage != placedAverage) { // this is checking for NaN, should only happen on first call + placedAverage = 1; + } + + // now we've got placement average, iterate all pokemon and see if they qualify to be placed + + for (Pokemon newPK : allPK) { + if (placedPK.contains(newPK)) { // if it's in the list of previously placed, then check its viability + if (placementHistory.get(newPK) <= placedAverage) { + toPlacePK.add(newPK); + } + } + else { + toPlacePK.add(newPK); // if not placed at all, automatically flag true for placing + + } + } + + return toPlacePK; + + } + + @Override + public void renderPlacementHistory() { + List placedPK = new ArrayList<>(placementHistory.keySet()); + for (Pokemon p : placedPK) { + System.out.println(p.name+": "+ placementHistory.get(p)); + } + } + + ///// Item functions + private void setItemPlacementHistory(int newItem) { + Integer history = getItemPlacementHistory(newItem); + // System.out.println("Current history: " + newPK.name + " : " + history); + itemPlacementHistory.put(newItem, history + 1); + } + + private int getItemPlacementHistory(int newItem) { + List placedItem = new ArrayList<>(itemPlacementHistory.keySet()); + if (placedItem.contains(newItem)) { + return itemPlacementHistory.get(newItem); + } + else { + return 0; + } + } + + private float getItemPlacementAverage() { + // This method will return an integer of average for itemPlacementHistory + // placed is less than average of all placed pokemon's appearances + // E.g., Charmander's been placed once, but the average for all pokemon is 2.2 + // So add to list and return + + List placedPK = new ArrayList<>(itemPlacementHistory.keySet()); + int placedPKNum = 0; + for (Integer p : placedPK) { + placedPKNum += itemPlacementHistory.get(p); + } + return (float)placedPKNum / (float)placedPK.size(); + } + + private void reportItemHistory() { + String[] itemNames = this.getItemNames(); + List placedItem = new ArrayList<>(itemPlacementHistory.keySet()); + for (Integer p : placedItem) { + System.out.println(itemNames[p]+": "+ itemPlacementHistory.get(p)); + } + } + + protected void log(String log) { + if (logStream != null) { + logStream.println(log); + } + } + + protected void logBlankLine() { + if (logStream != null) { + logStream.println(); + } + } + + /* Default Implementations */ + /* Used when a subclass doesn't override */ + /* + * The implication here is that these WILL be overridden by at least one + * subclass. + */ + @Override + public boolean typeInGame(Type type) { + return !type.isHackOnly && !(type == Type.FAIRY && generationOfPokemon() < 6); + } + + @Override + public String abilityName(int number) { + return ""; + } + + @Override + public List getUselessAbilities() { + return new ArrayList<>(); + } + + @Override + public int getAbilityForTrainerPokemon(TrainerPokemon tp) { + return 0; + } + + @Override + public boolean hasTimeBasedEncounters() { + // DEFAULT: no + return false; + } + + @Override + public List bannedForWildEncounters() { + return new ArrayList<>(); + } + + @Override + public List getMovesBannedFromLevelup() { + return new ArrayList<>(); + } + + @Override + public List bannedForStaticPokemon() { + return new ArrayList<>(); + } + + @Override + public boolean forceSwapStaticMegaEvos() { + return false; + } + + @Override + public int maxTrainerNameLength() { + // default: no real limit + return Integer.MAX_VALUE; + } + + @Override + public int maxSumOfTrainerNameLengths() { + // default: no real limit + return Integer.MAX_VALUE; + } + + @Override + public int maxTrainerClassNameLength() { + // default: no real limit + return Integer.MAX_VALUE; + } + + @Override + public int maxTradeNicknameLength() { + return 10; + } + + @Override + public int maxTradeOTNameLength() { + return 7; + } + + @Override + public boolean altFormesCanHaveDifferentEvolutions() { + return false; + } + + @Override + public List getGameBreakingMoves() { + // Sonicboom & Dragon Rage + return Arrays.asList(49, 82); + } + + @Override + public List getIllegalMoves() { + return new ArrayList<>(); + } + + @Override + public boolean isYellow() { + return false; + } + + @Override + public void writeCheckValueToROM(int value) { + // do nothing + } + + @Override + public int miscTweaksAvailable() { + // default: none + return 0; + } + + @Override + public void applyMiscTweaks(Settings settings) { + int selectedMiscTweaks = settings.getCurrentMiscTweaks(); + + int codeTweaksAvailable = miscTweaksAvailable(); + List tweaksToApply = new ArrayList<>(); + + for (MiscTweak mt : MiscTweak.allTweaks) { + if ((codeTweaksAvailable & mt.getValue()) > 0 && (selectedMiscTweaks & mt.getValue()) > 0) { + tweaksToApply.add(mt); + } + } + + // Sort so priority is respected in tweak ordering. + Collections.sort(tweaksToApply); + + // Now apply in order. + for (MiscTweak mt : tweaksToApply) { + applyMiscTweak(mt); + } + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + // default: do nothing + } + + @Override + public List getXItems() { + return GlobalConstants.xItems; + } + + @Override + public List getSensibleHeldItemsFor(TrainerPokemon tp, boolean consumableOnly, List moves, int[] pokeMoves) { + return Arrays.asList(0); + } + + @Override + public List getAllConsumableHeldItems() { + return Arrays.asList(0); + } + + @Override + public List getAllHeldItems() { + return Arrays.asList(0); + } + + @Override + public List getBannedFormesForTrainerPokemon() { + return new ArrayList<>(); + } + + @Override + public List getPickupItems() { + return new ArrayList<>(); + } + + @Override + public void setPickupItems(List pickupItems) { + // do nothing + } +} diff --git a/src/com/pkrandom/romhandlers/Gen1RomHandler.java b/src/com/pkrandom/romhandlers/Gen1RomHandler.java new file mode 100755 index 0000000..69cd51e --- /dev/null +++ b/src/com/pkrandom/romhandlers/Gen1RomHandler.java @@ -0,0 +1,2918 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- Gen1RomHandler.java - randomizer handler for R/B/Y. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Scanner; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.pkrandom.*; +import com.pkrandom.constants.*; +import com.pkrandom.exceptions.RandomizationException; +import com.pkrandom.exceptions.RandomizerIOException; +import com.pkrandom.pokemon.*; +import compressors.Gen1Decmp; + +public class Gen1RomHandler extends AbstractGBCRomHandler { + + public static class Factory extends RomHandler.Factory { + + @Override + public Gen1RomHandler create(Random random, PrintStream logStream) { + return new Gen1RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + long fileLength = new File(filename).length(); + if (fileLength > 8 * 1024 * 1024) { + return false; + } + byte[] loaded = loadFilePartial(filename, 0x1000); + // nope + return loaded.length != 0 && detectRomInner(loaded, (int) fileLength); + } + } + + public Gen1RomHandler(Random random) { + super(random, null); + } + + public Gen1RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + // Important RBY Data Structures + + private int[] pokeNumToRBYTable; + private int[] pokeRBYToNumTable; + private int[] moveNumToRomTable; + private int[] moveRomToNumTable; + private int pokedexCount; + + private Type idToType(int value) { + if (Gen1Constants.typeTable[value] != null) { + return Gen1Constants.typeTable[value]; + } + if (romEntry.extraTypeLookup.containsKey(value)) { + return romEntry.extraTypeLookup.get(value); + } + return null; + } + + private byte typeToByte(Type type) { + if (type == null) { + return 0x00; // revert to normal + } + if (romEntry.extraTypeReverse.containsKey(type)) { + return romEntry.extraTypeReverse.get(type).byteValue(); + } + return Gen1Constants.typeToByte(type); + } + + private static class RomEntry { + private String name; + private String romName; + private int version, nonJapanese; + private String extraTableFile; + private boolean isYellow; + private long expectedCRC32 = -1; + private int crcInHeader = -1; + private Map tweakFiles = new HashMap<>(); + private List tmTexts = new ArrayList<>(); + private Map entries = new HashMap<>(); + private Map arrayEntries = new HashMap<>(); + private List staticPokemon = new ArrayList<>(); + private int[] ghostMarowakOffsets = new int[0]; + private Map extraTypeLookup = new HashMap<>(); + private Map extraTypeReverse = new HashMap<>(); + + private int getValue(String key) { + if (!entries.containsKey(key)) { + entries.put(key, 0); + } + return entries.get(key); + } + } + + private static List roms; + + static { + loadROMInfo(); + } + + private static class TMTextEntry { + private int number; + private int offset; + private String template; + } + + private static void loadROMInfo() { + roms = new ArrayList<>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen1_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + r[0] = r[0].trim(); + // Static Pokemon? + if (r[0].equals("StaticPokemon{}")) { + current.staticPokemon.add(parseStaticPokemon(r[1])); + } else if (r[0].equals("StaticPokemonGhostMarowak{}")) { + StaticPokemon ghostMarowak = parseStaticPokemon(r[1]); + current.staticPokemon.add(ghostMarowak); + current.ghostMarowakOffsets = ghostMarowak.speciesOffsets; + } else if (r[0].equals("TMText[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] parts = r[1].substring(1, r[1].length() - 1).split(",", 3); + TMTextEntry tte = new TMTextEntry(); + tte.number = parseRIInt(parts[0]); + tte.offset = parseRIInt(parts[1]); + tte.template = parts[2]; + current.tmTexts.add(tte); + } + } else if (r[0].equals("Game")) { + current.romName = r[1]; + } else if (r[0].equals("Version")) { + current.version = parseRIInt(r[1]); + } else if (r[0].equals("NonJapanese")) { + current.nonJapanese = parseRIInt(r[1]); + } else if (r[0].equals("Type")) { + current.isYellow = r[1].equalsIgnoreCase("Yellow"); + } else if (r[0].equals("ExtraTableFile")) { + current.extraTableFile = r[1]; + } else if (r[0].equals("CRCInHeader")) { + current.crcInHeader = parseRIInt(r[1]); + } else if (r[0].equals("CRC32")) { + current.expectedCRC32 = parseRILong("0x" + r[1]); + } else if (r[0].endsWith("Tweak")) { + current.tweakFiles.put(r[0], r[1]); + } else if (r[0].equals("ExtraTypes")) { + // remove the containers + r[1] = r[1].substring(1, r[1].length() - 1); + String[] parts = r[1].split(","); + for (String part : parts) { + String[] iParts = part.split("="); + int typeId = Integer.parseInt(iParts[0], 16); + String typeName = iParts[1].trim(); + Type theType = Type.valueOf(typeName); + current.extraTypeLookup.put(typeId, theType); + current.extraTypeReverse.put(theType, typeId); + } + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.name)) { + // copy from here + boolean cSP = (current.getValue("CopyStaticPokemon") == 1); + boolean cTT = (current.getValue("CopyTMText") == 1); + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.entries.putAll(otherEntry.entries); + if (cSP) { + current.staticPokemon.addAll(otherEntry.staticPokemon); + current.ghostMarowakOffsets = otherEntry.ghostMarowakOffsets; + current.entries.put("StaticPokemonSupport", 1); + } else { + current.entries.put("StaticPokemonSupport", 0); + } + if (cTT) { + current.tmTexts.addAll(otherEntry.tmTexts); + } + current.extraTableFile = otherEntry.extraTableFile; + } + } + } else { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.arrayEntries.put(r[0], offs); + } + + } else { + int offs = parseRIInt(r[1]); + current.entries.put(r[0], offs); + } + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + System.err.println("File not found!"); + } + + } + + private static StaticPokemon parseStaticPokemon(String staticPokemonString) { + StaticPokemon sp = new StaticPokemon(); + String pattern = "[A-z]+=\\[(0x[0-9a-fA-F]+,?\\s?)+]"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(staticPokemonString); + while (m.find()) { + String[] segments = m.group().split("="); + String[] romOffsets = segments[1].substring(1, segments[1].length() - 1).split(","); + int[] offsets = new int [romOffsets.length]; + for (int i = 0; i < offsets.length; i++) { + offsets[i] = parseRIInt(romOffsets[i]); + } + switch (segments[0]) { + case "Species": + sp.speciesOffsets = offsets; + break; + case "Level": + sp.levelOffsets = offsets; + break; + } + } + return sp; + } + + private static int parseRIInt(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Integer.parseInt(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + private static long parseRILong(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Long.parseLong(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + // This ROM's data + private Pokemon[] pokes; + private List pokemonList; + private RomEntry romEntry; + private Move[] moves; + private String[] itemNames; + private String[] mapNames; + private SubMap[] maps; + private boolean xAccNerfed; + private long actualCRC32; + private boolean effectivenessUpdated; + + @Override + public boolean detectRom(byte[] rom) { + return detectRomInner(rom, rom.length); + } + + public static boolean detectRomInner(byte[] rom, int romSize) { + // size check + return romSize >= GBConstants.minRomSize && romSize <= GBConstants.maxRomSize && checkRomEntry(rom) != null; + } + + @Override + public void loadedRom() { + romEntry = checkRomEntry(this.rom); + pokeNumToRBYTable = new int[256]; + pokeRBYToNumTable = new int[256]; + moveNumToRomTable = new int[256]; + moveRomToNumTable = new int[256]; + maps = new SubMap[256]; + xAccNerfed = false; + clearTextTables(); + readTextTable("gameboy_jpn"); + if (romEntry.extraTableFile != null && !romEntry.extraTableFile.equalsIgnoreCase("none")) { + readTextTable(romEntry.extraTableFile); + } + loadPokedexOrder(); + loadPokemonStats(); + pokemonList = Arrays.asList(pokes); + loadMoves(); + loadItemNames(); + preloadMaps(); + loadMapNames(); + actualCRC32 = FileFunctions.getCRC32(rom); + } + + private void loadPokedexOrder() { + int pkmnCount = romEntry.getValue("InternalPokemonCount"); + int orderOffset = romEntry.getValue("PokedexOrder"); + pokedexCount = 0; + for (int i = 1; i <= pkmnCount; i++) { + int pokedexNum = rom[orderOffset + i - 1] & 0xFF; + pokeRBYToNumTable[i] = pokedexNum; + if (pokedexNum != 0 && pokeNumToRBYTable[pokedexNum] == 0) { + pokeNumToRBYTable[pokedexNum] = i; + } + pokedexCount = Math.max(pokedexCount, pokedexNum); + } + } + + private static RomEntry checkRomEntry(byte[] rom) { + int version = rom[GBConstants.versionOffset] & 0xFF; + int nonjap = rom[GBConstants.jpFlagOffset] & 0xFF; + // Check for specific CRC first + int crcInHeader = ((rom[GBConstants.crcOffset] & 0xFF) << 8) | (rom[GBConstants.crcOffset + 1] & 0xFF); + for (RomEntry re : roms) { + if (romSig(rom, re.romName) && re.version == version && re.nonJapanese == nonjap + && re.crcInHeader == crcInHeader) { + return re; + } + } + // Now check for non-specific-CRC entries + for (RomEntry re : roms) { + if (romSig(rom, re.romName) && re.version == version && re.nonJapanese == nonjap && re.crcInHeader == -1) { + return re; + } + } + // Not found + return null; + } + + @Override + public void savingRom() { + savePokemonStats(); + saveMoves(); + } + + private String[] readMoveNames() { + int moveCount = romEntry.getValue("MoveCount"); + int offset = romEntry.getValue("MoveNamesOffset"); + String[] moveNames = new String[moveCount + 1]; + for (int i = 1; i <= moveCount; i++) { + moveNames[i] = readVariableLengthString(offset, false); + offset += lengthOfStringAt(offset, false) + 1; + } + return moveNames; + } + + private void loadMoves() { + String[] moveNames = readMoveNames(); + int moveCount = romEntry.getValue("MoveCount"); + int movesOffset = romEntry.getValue("MoveDataOffset"); + // check real move count + int trueMoveCount = 0; + for (int i = 1; i <= moveCount; i++) { + // temp hack for Brown + if (rom[movesOffset + (i - 1) * 6] != 0 && !moveNames[i].equals("Nothing")) { + trueMoveCount++; + } + } + moves = new Move[trueMoveCount + 1]; + int trueMoveIndex = 0; + + for (int i = 1; i <= moveCount; i++) { + int anim = rom[movesOffset + (i - 1) * 6] & 0xFF; + // another temp hack for brown + if (anim > 0 && !moveNames[i].equals("Nothing")) { + trueMoveIndex++; + moveNumToRomTable[trueMoveIndex] = i; + moveRomToNumTable[i] = trueMoveIndex; + moves[trueMoveIndex] = new Move(); + moves[trueMoveIndex].name = moveNames[i]; + moves[trueMoveIndex].internalId = i; + moves[trueMoveIndex].number = trueMoveIndex; + moves[trueMoveIndex].effectIndex = rom[movesOffset + (i - 1) * 6 + 1] & 0xFF; + moves[trueMoveIndex].hitratio = ((rom[movesOffset + (i - 1) * 6 + 4] & 0xFF)) / 255.0 * 100; + moves[trueMoveIndex].power = rom[movesOffset + (i - 1) * 6 + 2] & 0xFF; + moves[trueMoveIndex].pp = rom[movesOffset + (i - 1) * 6 + 5] & 0xFF; + moves[trueMoveIndex].type = idToType(rom[movesOffset + (i - 1) * 6 + 3] & 0xFF); + moves[trueMoveIndex].category = GBConstants.physicalTypes.contains(moves[trueMoveIndex].type) ? MoveCategory.PHYSICAL : MoveCategory.SPECIAL; + if (moves[trueMoveIndex].power == 0 && !GlobalConstants.noPowerNonStatusMoves.contains(trueMoveIndex)) { + moves[trueMoveIndex].category = MoveCategory.STATUS; + } + + if (moves[trueMoveIndex].name.equals("Swift")) { + perfectAccuracy = (int)moves[trueMoveIndex].hitratio; + } + + if (GlobalConstants.normalMultihitMoves.contains(i)) { + moves[trueMoveIndex].hitCount = 3; + } else if (GlobalConstants.doubleHitMoves.contains(i)) { + moves[trueMoveIndex].hitCount = 2; + } + + loadStatChangesFromEffect(moves[trueMoveIndex]); + loadStatusFromEffect(moves[trueMoveIndex]); + loadMiscMoveInfoFromEffect(moves[trueMoveIndex]); + } + } + } + + private void loadStatChangesFromEffect(Move move) { + switch (move.effectIndex) { + case Gen1Constants.noDamageAtkPlusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 1; + break; + case Gen1Constants.noDamageDefPlusOneEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = 1; + break; + case Gen1Constants.noDamageSpecialPlusOneEffect: + move.statChanges[0].type = StatChangeType.SPECIAL; + move.statChanges[0].stages = 1; + break; + case Gen1Constants.noDamageEvasionPlusOneEffect: + move.statChanges[0].type = StatChangeType.EVASION; + move.statChanges[0].stages = 1; + break; + case Gen1Constants.noDamageAtkMinusOneEffect: + case Gen1Constants.damageAtkMinusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = -1; + break; + case Gen1Constants.noDamageDefMinusOneEffect: + case Gen1Constants.damageDefMinusOneEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = -1; + break; + case Gen1Constants.noDamageSpeMinusOneEffect: + case Gen1Constants.damageSpeMinusOneEffect: + move.statChanges[0].type = StatChangeType.SPEED; + move.statChanges[0].stages = -1; + break; + case Gen1Constants.noDamageAccuracyMinusOneEffect: + move.statChanges[0].type = StatChangeType.ACCURACY; + move.statChanges[0].stages = -1; + break; + case Gen1Constants.noDamageAtkPlusTwoEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 2; + break; + case Gen1Constants.noDamageDefPlusTwoEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = 2; + break; + case Gen1Constants.noDamageSpePlusTwoEffect: + move.statChanges[0].type = StatChangeType.SPEED; + move.statChanges[0].stages = 2; + break; + case Gen1Constants.noDamageSpecialPlusTwoEffect: + move.statChanges[0].type = StatChangeType.SPECIAL; + move.statChanges[0].stages = 2; + break; + case Gen1Constants.noDamageDefMinusTwoEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = -2; + break; + case Gen1Constants.damageSpecialMinusOneEffect: + move.statChanges[0].type = StatChangeType.SPECIAL; + move.statChanges[0].stages = -1; + break; + default: + // Move does not have a stat-changing effect + return; + } + + switch (move.effectIndex) { + case Gen1Constants.noDamageAtkPlusOneEffect: + case Gen1Constants.noDamageDefPlusOneEffect: + case Gen1Constants.noDamageSpecialPlusOneEffect: + case Gen1Constants.noDamageEvasionPlusOneEffect: + case Gen1Constants.noDamageAtkMinusOneEffect: + case Gen1Constants.noDamageDefMinusOneEffect: + case Gen1Constants.noDamageSpeMinusOneEffect: + case Gen1Constants.noDamageAccuracyMinusOneEffect: + case Gen1Constants.noDamageAtkPlusTwoEffect: + case Gen1Constants.noDamageDefPlusTwoEffect: + case Gen1Constants.noDamageSpePlusTwoEffect: + case Gen1Constants.noDamageSpecialPlusTwoEffect: + case Gen1Constants.noDamageDefMinusTwoEffect: + if (move.statChanges[0].stages < 0) { + move.statChangeMoveType = StatChangeMoveType.NO_DAMAGE_TARGET; + } else { + move.statChangeMoveType = StatChangeMoveType.NO_DAMAGE_USER; + } + break; + + case Gen1Constants.damageAtkMinusOneEffect: + case Gen1Constants.damageDefMinusOneEffect: + case Gen1Constants.damageSpeMinusOneEffect: + case Gen1Constants.damageSpecialMinusOneEffect: + move.statChangeMoveType = StatChangeMoveType.DAMAGE_TARGET; + break; + } + + if (move.statChangeMoveType == StatChangeMoveType.DAMAGE_TARGET) { + for (int i = 0; i < move.statChanges.length; i++) { + if (move.statChanges[i].type != StatChangeType.NONE) { + move.statChanges[i].percentChance = 85 / 256.0; + } + } + } + } + + private void loadStatusFromEffect(Move move) { + switch (move.effectIndex) { + case Gen1Constants.noDamageSleepEffect: + case Gen1Constants.noDamageConfusionEffect: + case Gen1Constants.noDamagePoisonEffect: + case Gen1Constants.noDamageParalyzeEffect: + move.statusMoveType = StatusMoveType.NO_DAMAGE; + break; + + case Gen1Constants.damagePoison20PercentEffect: + case Gen1Constants.damageBurn10PercentEffect: + case Gen1Constants.damageFreeze10PercentEffect: + case Gen1Constants.damageParalyze10PercentEffect: + case Gen1Constants.damagePoison40PercentEffect: + case Gen1Constants.damageBurn30PercentEffect: + case Gen1Constants.damageFreeze30PercentEffect: + case Gen1Constants.damageParalyze30PercentEffect: + case Gen1Constants.damageConfusionEffect: + case Gen1Constants.twineedleEffect: + move.statusMoveType = StatusMoveType.DAMAGE; + break; + + default: + // Move does not have a status effect + return; + } + + switch (move.effectIndex) { + case Gen1Constants.noDamageSleepEffect: + move.statusType = StatusType.SLEEP; + break; + case Gen1Constants.damagePoison20PercentEffect: + case Gen1Constants.damagePoison40PercentEffect: + case Gen1Constants.noDamagePoisonEffect: + case Gen1Constants.twineedleEffect: + move.statusType = StatusType.POISON; + if (move.number == Moves.toxic) { + move.statusType = StatusType.TOXIC_POISON; + } + break; + case Gen1Constants.damageBurn10PercentEffect: + case Gen1Constants.damageBurn30PercentEffect: + move.statusType = StatusType.BURN; + break; + case Gen1Constants.damageFreeze10PercentEffect: + case Gen1Constants.damageFreeze30PercentEffect: + move.statusType = StatusType.FREEZE; + break; + case Gen1Constants.damageParalyze10PercentEffect: + case Gen1Constants.damageParalyze30PercentEffect: + case Gen1Constants.noDamageParalyzeEffect: + move.statusType = StatusType.PARALYZE; + break; + case Gen1Constants.noDamageConfusionEffect: + case Gen1Constants.damageConfusionEffect: + move.statusType = StatusType.CONFUSION; + break; + } + + if (move.statusMoveType == StatusMoveType.DAMAGE) { + switch (move.effectIndex) { + case Gen1Constants.damageBurn10PercentEffect: + case Gen1Constants.damageFreeze10PercentEffect: + case Gen1Constants.damageParalyze10PercentEffect: + case Gen1Constants.damageConfusionEffect: + move.statusPercentChance = 10.0; + break; + case Gen1Constants.damagePoison20PercentEffect: + case Gen1Constants.twineedleEffect: + move.statusPercentChance = 20.0; + break; + case Gen1Constants.damageBurn30PercentEffect: + case Gen1Constants.damageFreeze30PercentEffect: + case Gen1Constants.damageParalyze30PercentEffect: + move.statusPercentChance = 30.0; + break; + case Gen1Constants.damagePoison40PercentEffect: + move.statusPercentChance = 40.0; + break; + } + } + } + + private void loadMiscMoveInfoFromEffect(Move move) { + switch (move.effectIndex) { + case Gen1Constants.flinch10PercentEffect: + move.flinchPercentChance = 10.0; + break; + + case Gen1Constants.flinch30PercentEffect: + move.flinchPercentChance = 30.0; + break; + + case Gen1Constants.damageAbsorbEffect: + case Gen1Constants.dreamEaterEffect: + move.absorbPercent = 50; + break; + + case Gen1Constants.damageRecoilEffect: + move.recoilPercent = 25; + break; + + case Gen1Constants.chargeEffect: + case Gen1Constants.flyEffect: + move.isChargeMove = true; + break; + + case Gen1Constants.hyperBeamEffect: + move.isRechargeMove = true; + break; + } + + if (Gen1Constants.increasedCritMoves.contains(move.number)) { + move.criticalChance = CriticalChance.INCREASED; + } + } + + private void saveMoves() { + int movesOffset = romEntry.getValue("MoveDataOffset"); + for (Move m : moves) { + if (m != null) { + int i = m.internalId; + rom[movesOffset + (i - 1) * 6 + 1] = (byte) m.effectIndex; + rom[movesOffset + (i - 1) * 6 + 2] = (byte) m.power; + rom[movesOffset + (i - 1) * 6 + 3] = typeToByte(m.type); + int hitratio = (int) Math.round(m.hitratio * 2.55); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 255) { + hitratio = 255; + } + rom[movesOffset + (i - 1) * 6 + 4] = (byte) hitratio; + rom[movesOffset + (i - 1) * 6 + 5] = (byte) m.pp; + } + } + } + + public List getMoves() { + return Arrays.asList(moves); + } + + private void loadPokemonStats() { + pokes = new Gen1Pokemon[pokedexCount + 1]; + // Fetch our names + String[] pokeNames = readPokemonNames(); + // Get base stats + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + for (int i = 1; i <= pokedexCount; i++) { + pokes[i] = new Gen1Pokemon(); + pokes[i].number = i; + if (i != Species.mew || romEntry.isYellow) { + loadBasicPokeStats(pokes[i], pokeStatsOffset + (i - 1) * Gen1Constants.baseStatsEntrySize); + } + // Name? + pokes[i].name = pokeNames[pokeNumToRBYTable[i]]; + } + + // Mew override for R/B + if (!romEntry.isYellow) { + loadBasicPokeStats(pokes[Species.mew], romEntry.getValue("MewStatsOffset")); + } + + // Evolutions + populateEvolutions(); + + } + + private void savePokemonStats() { + // Write pokemon names + int offs = romEntry.getValue("PokemonNamesOffset"); + int nameLength = romEntry.getValue("PokemonNamesLength"); + for (int i = 1; i <= pokedexCount; i++) { + int rbynum = pokeNumToRBYTable[i]; + int stringOffset = offs + (rbynum - 1) * nameLength; + writeFixedLengthString(pokes[i].name, stringOffset, nameLength); + } + // Write pokemon stats + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + for (int i = 1; i <= pokedexCount; i++) { + if (i == Species.mew) { + continue; + } + saveBasicPokeStats(pokes[i], pokeStatsOffset + (i - 1) * Gen1Constants.baseStatsEntrySize); + } + // Write MEW + int mewOffset = romEntry.isYellow ? pokeStatsOffset + (Species.mew - 1) + * Gen1Constants.baseStatsEntrySize : romEntry.getValue("MewStatsOffset"); + saveBasicPokeStats(pokes[Species.mew], mewOffset); + + // Write evolutions + writeEvosAndMovesLearnt(true, null); + } + + private void loadBasicPokeStats(Pokemon pkmn, int offset) { + pkmn.hp = rom[offset + Gen1Constants.bsHPOffset] & 0xFF; + pkmn.attack = rom[offset + Gen1Constants.bsAttackOffset] & 0xFF; + pkmn.defense = rom[offset + Gen1Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = rom[offset + Gen1Constants.bsSpeedOffset] & 0xFF; + pkmn.special = rom[offset + Gen1Constants.bsSpecialOffset] & 0xFF; + // Type + pkmn.primaryType = idToType(rom[offset + Gen1Constants.bsPrimaryTypeOffset] & 0xFF); + pkmn.secondaryType = idToType(rom[offset + Gen1Constants.bsSecondaryTypeOffset] & 0xFF); + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + + pkmn.catchRate = rom[offset + Gen1Constants.bsCatchRateOffset] & 0xFF; + pkmn.expYield = rom[offset + Gen1Constants.bsExpYieldOffset] & 0xFF; + pkmn.growthCurve = ExpCurve.fromByte(rom[offset + Gen1Constants.bsGrowthCurveOffset]); + pkmn.frontSpritePointer = readWord(offset + Gen1Constants.bsFrontSpriteOffset); + + pkmn.guaranteedHeldItem = -1; + pkmn.commonHeldItem = -1; + pkmn.rareHeldItem = -1; + pkmn.darkGrassHeldItem = -1; + } + + private void saveBasicPokeStats(Pokemon pkmn, int offset) { + rom[offset + Gen1Constants.bsHPOffset] = (byte) pkmn.hp; + rom[offset + Gen1Constants.bsAttackOffset] = (byte) pkmn.attack; + rom[offset + Gen1Constants.bsDefenseOffset] = (byte) pkmn.defense; + rom[offset + Gen1Constants.bsSpeedOffset] = (byte) pkmn.speed; + rom[offset + Gen1Constants.bsSpecialOffset] = (byte) pkmn.special; + rom[offset + Gen1Constants.bsPrimaryTypeOffset] = typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + rom[offset + Gen1Constants.bsSecondaryTypeOffset] = rom[offset + Gen1Constants.bsPrimaryTypeOffset]; + } else { + rom[offset + Gen1Constants.bsSecondaryTypeOffset] = typeToByte(pkmn.secondaryType); + } + rom[offset + Gen1Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + rom[offset + Gen1Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + rom[offset + Gen1Constants.bsExpYieldOffset] = (byte) pkmn.expYield; + } + + private String[] readPokemonNames() { + int offs = romEntry.getValue("PokemonNamesOffset"); + int nameLength = romEntry.getValue("PokemonNamesLength"); + int pkmnCount = romEntry.getValue("InternalPokemonCount"); + String[] names = new String[pkmnCount + 1]; + for (int i = 1; i <= pkmnCount; i++) { + names[i] = readFixedLengthString(offs + (i - 1) * nameLength, nameLength); + } + return names; + } + + @Override + public List getStarters() { + // Get the starters + List starters = new ArrayList<>(); + starters.add(pokes[pokeRBYToNumTable[rom[romEntry.arrayEntries.get("StarterOffsets1")[0]] & 0xFF]]); + starters.add(pokes[pokeRBYToNumTable[rom[romEntry.arrayEntries.get("StarterOffsets2")[0]] & 0xFF]]); + if (!romEntry.isYellow) { + starters.add(pokes[pokeRBYToNumTable[rom[romEntry.arrayEntries.get("StarterOffsets3")[0]] & 0xFF]]); + } + return starters; + } + + @Override + public boolean setStarters(List newStarters) { + // Amount? + int starterAmount = 2; + if (!romEntry.isYellow) { + starterAmount = 3; + } + + // Basic checks + if (newStarters.size() != starterAmount) { + return false; + } + + // Patch starter bytes + for (int i = 0; i < starterAmount; i++) { + byte starter = (byte) pokeNumToRBYTable[newStarters.get(i).number]; + int[] offsets = romEntry.arrayEntries.get("StarterOffsets" + (i + 1)); + for (int offset : offsets) { + rom[offset] = starter; + } + } + + // Special stuff for non-Yellow only + + if (!romEntry.isYellow) { + + // Starter text + if (romEntry.getValue("CanChangeStarterText") > 0) { + int[] starterTextOffsets = romEntry.arrayEntries.get("StarterTextOffsets"); + for (int i = 0; i < 3 && i < starterTextOffsets.length; i++) { + writeVariableLengthString(String.format("So! You want\\n%s?\\e", newStarters.get(i).name), + starterTextOffsets[i], true); + } + } + + // Patch starter pokedex routine? + // Can only do in 1M roms because of size concerns + if (romEntry.getValue("PatchPokedex") > 0) { + + // Starter pokedex required RAM values + // RAM offset => value + // Allows for multiple starters in the same RAM byte + Map onValues = new TreeMap<>(); + for (int i = 0; i < 3; i++) { + int pkDexNum = newStarters.get(i).number; + int ramOffset = (pkDexNum - 1) / 8 + romEntry.getValue("PokedexRamOffset"); + int bitShift = (pkDexNum - 1) % 8; + int writeValue = 1 << bitShift; + if (onValues.containsKey(ramOffset)) { + onValues.put(ramOffset, onValues.get(ramOffset) | writeValue); + } else { + onValues.put(ramOffset, writeValue); + } + } + + // Starter pokedex offset/pointer calculations + + int pkDexOnOffset = romEntry.getValue("StarterPokedexOnOffset"); + int pkDexOffOffset = romEntry.getValue("StarterPokedexOffOffset"); + + int sizeForOnRoutine = 5 * onValues.size() + 3; + int writeOnRoutineTo = romEntry.getValue("StarterPokedexBranchOffset"); + int writeOffRoutineTo = writeOnRoutineTo + sizeForOnRoutine; + int offsetForOnRoutine = makeGBPointer(writeOnRoutineTo); + int offsetForOffRoutine = makeGBPointer(writeOffRoutineTo); + int retOnOffset = makeGBPointer(pkDexOnOffset + 5); + int retOffOffset = makeGBPointer(pkDexOffOffset + 4); + + // Starter pokedex + // Branch to our new routine(s) + + // Turn bytes on + rom[pkDexOnOffset] = GBConstants.gbZ80Jump; + writeWord(pkDexOnOffset + 1, offsetForOnRoutine); + rom[pkDexOnOffset + 3] = GBConstants.gbZ80Nop; + rom[pkDexOnOffset + 4] = GBConstants.gbZ80Nop; + + // Turn bytes off + rom[pkDexOffOffset] = GBConstants.gbZ80Jump; + writeWord(pkDexOffOffset + 1, offsetForOffRoutine); + rom[pkDexOffOffset + 3] = GBConstants.gbZ80Nop; + + // Put together the two scripts + rom[writeOffRoutineTo] = GBConstants.gbZ80XorA; + int turnOnOffset = writeOnRoutineTo; + int turnOffOffset = writeOffRoutineTo + 1; + for (int ramOffset : onValues.keySet()) { + int onValue = onValues.get(ramOffset); + // Turn on code + rom[turnOnOffset++] = GBConstants.gbZ80LdA; + rom[turnOnOffset++] = (byte) onValue; + // Turn on code for ram writing + rom[turnOnOffset++] = GBConstants.gbZ80LdAToFar; + rom[turnOnOffset++] = (byte) (ramOffset % 0x100); + rom[turnOnOffset++] = (byte) (ramOffset / 0x100); + // Turn off code for ram writing + rom[turnOffOffset++] = GBConstants.gbZ80LdAToFar; + rom[turnOffOffset++] = (byte) (ramOffset % 0x100); + rom[turnOffOffset++] = (byte) (ramOffset / 0x100); + } + // Jump back + rom[turnOnOffset++] = GBConstants.gbZ80Jump; + writeWord(turnOnOffset, retOnOffset); + + rom[turnOffOffset++] = GBConstants.gbZ80Jump; + writeWord(turnOffOffset, retOffOffset); + } + + } + + // If we're changing the player's starter for Yellow, then the player can't get the + // Bulbasaur gift unless they randomly stumble into a Pikachu somewhere else. This is + // because you need a certain amount of Pikachu happiness to acquire this gift, and + // happiness only accumulates if you have a Pikachu. Instead, just patch out this check. + if (romEntry.entries.containsKey("PikachuHappinessCheckOffset") && newStarters.get(0).number != Species.pikachu) { + int offset = romEntry.getValue("PikachuHappinessCheckOffset"); + + // The code looks like this: + // ld a, [wPikachuHappiness] + // cp 147 + // jr c, .asm_1cfb3 <- this is where "offset" is + // Write two nops to patch out the jump + rom[offset] = GBConstants.gbZ80Nop; + rom[offset + 1] = GBConstants.gbZ80Nop; + } + + return true; + + } + + @Override + public boolean hasStarterAltFormes() { + return false; + } + + @Override + public int starterCount() { + return isYellow() ? 2 : 3; + } + + @Override + public Map getUpdatedPokemonStats(int generation) { + Map map = GlobalConstants.getStatChanges(generation); + switch(generation) { + case 6: + map.put(12,new StatChange(Stat.SPECIAL.val,90)); + map.put(36,new StatChange(Stat.SPECIAL.val,95)); + map.put(45,new StatChange(Stat.SPECIAL.val,110)); + break; + default: + break; + } + return map; + } + + @Override + public boolean supportsStarterHeldItems() { + // No held items in Gen 1 + return false; + } + + @Override + public List getStarterHeldItems() { + // do nothing + return new ArrayList<>(); + } + + @Override + public void setStarterHeldItems(List items) { + // do nothing + } + + @Override + public List getEvolutionItems() { + return null; + } + + @Override + public List getEncounters(boolean useTimeOfDay) { + List encounters = new ArrayList<>(); + + Pokemon ghostMarowak = pokes[Species.marowak]; + if (canChangeStaticPokemon()) { + ghostMarowak = pokes[pokeRBYToNumTable[rom[romEntry.ghostMarowakOffsets[0]] & 0xFF]]; + } + + // grass & water + List usedOffsets = new ArrayList<>(); + int tableOffset = romEntry.getValue("WildPokemonTableOffset"); + int tableBank = bankOf(tableOffset); + int mapID = -1; + + while (readWord(tableOffset) != Gen1Constants.encounterTableEnd) { + mapID++; + int offset = calculateOffset(tableBank, readWord(tableOffset)); + int rootOffset = offset; + if (!usedOffsets.contains(offset)) { + usedOffsets.add(offset); + // grass and water are exactly the same + for (int a = 0; a < 2; a++) { + int rate = rom[offset++] & 0xFF; + if (rate > 0) { + // there is data here + EncounterSet thisSet = new EncounterSet(); + thisSet.rate = rate; + thisSet.offset = rootOffset; + thisSet.displayName = (a == 1 ? "Surfing" : "Grass/Cave") + " on " + mapNames[mapID]; + if (mapID >= Gen1Constants.towerMapsStartIndex && mapID <= Gen1Constants.towerMapsEndIndex) { + thisSet.bannedPokemon.add(ghostMarowak); + } + for (int slot = 0; slot < Gen1Constants.encounterTableSize; slot++) { + Encounter enc = new Encounter(); + enc.level = rom[offset] & 0xFF; + enc.pokemon = pokes[pokeRBYToNumTable[rom[offset + 1] & 0xFF]]; + thisSet.encounters.add(enc); + offset += 2; + } + encounters.add(thisSet); + } + } + } else { + for (EncounterSet es : encounters) { + if (es.offset == offset) { + es.displayName += ", " + mapNames[mapID]; + } + } + } + tableOffset += 2; + } + + // old rod + int oldRodOffset = romEntry.getValue("OldRodOffset"); + EncounterSet oldRodSet = new EncounterSet(); + oldRodSet.displayName = "Old Rod Fishing"; + Encounter oldRodEnc = new Encounter(); + oldRodEnc.level = rom[oldRodOffset + 2] & 0xFF; + oldRodEnc.pokemon = pokes[pokeRBYToNumTable[rom[oldRodOffset + 1] & 0xFF]]; + oldRodSet.encounters.add(oldRodEnc); + oldRodSet.bannedPokemon.add(ghostMarowak); + encounters.add(oldRodSet); + + // good rod + int goodRodOffset = romEntry.getValue("GoodRodOffset"); + EncounterSet goodRodSet = new EncounterSet(); + goodRodSet.displayName = "Good Rod Fishing"; + for (int grSlot = 0; grSlot < 2; grSlot++) { + Encounter enc = new Encounter(); + enc.level = rom[goodRodOffset + grSlot * 2] & 0xFF; + enc.pokemon = pokes[pokeRBYToNumTable[rom[goodRodOffset + grSlot * 2 + 1] & 0xFF]]; + goodRodSet.encounters.add(enc); + } + goodRodSet.bannedPokemon.add(ghostMarowak); + encounters.add(goodRodSet); + + // super rod + if (romEntry.isYellow) { + int superRodOffset = romEntry.getValue("SuperRodTableOffset"); + while ((rom[superRodOffset] & 0xFF) != 0xFF) { + int map = rom[superRodOffset++] & 0xFF; + EncounterSet thisSet = new EncounterSet(); + thisSet.displayName = "Super Rod Fishing on " + mapNames[map]; + for (int encN = 0; encN < Gen1Constants.yellowSuperRodTableSize; encN++) { + Encounter enc = new Encounter(); + enc.level = rom[superRodOffset + 1] & 0xFF; + enc.pokemon = pokes[pokeRBYToNumTable[rom[superRodOffset] & 0xFF]]; + thisSet.encounters.add(enc); + superRodOffset += 2; + } + thisSet.bannedPokemon.add(ghostMarowak); + encounters.add(thisSet); + } + } else { + // red/blue + int superRodOffset = romEntry.getValue("SuperRodTableOffset"); + int superRodBank = bankOf(superRodOffset); + List usedSROffsets = new ArrayList<>(); + while ((rom[superRodOffset] & 0xFF) != 0xFF) { + int map = rom[superRodOffset++] & 0xFF; + int setOffset = calculateOffset(superRodBank, readWord(superRodOffset)); + superRodOffset += 2; + if (!usedSROffsets.contains(setOffset)) { + usedSROffsets.add(setOffset); + EncounterSet thisSet = new EncounterSet(); + thisSet.displayName = "Super Rod Fishing on " + mapNames[map]; + thisSet.offset = setOffset; + int pokesInSet = rom[setOffset++] & 0xFF; + for (int encN = 0; encN < pokesInSet; encN++) { + Encounter enc = new Encounter(); + enc.level = rom[setOffset] & 0xFF; + enc.pokemon = pokes[pokeRBYToNumTable[rom[setOffset + 1] & 0xFF]]; + thisSet.encounters.add(enc); + setOffset += 2; + } + thisSet.bannedPokemon.add(ghostMarowak); + encounters.add(thisSet); + } else { + for (EncounterSet es : encounters) { + if (es.offset == setOffset) { + es.displayName += ", " + mapNames[map]; + } + } + } + } + } + + return encounters; + } + + @Override + public void setEncounters(boolean useTimeOfDay, List encounters) { + Iterator encsetit = encounters.iterator(); + + // grass & water + List usedOffsets = new ArrayList<>(); + int tableOffset = romEntry.getValue("WildPokemonTableOffset"); + int tableBank = bankOf(tableOffset); + + while (readWord(tableOffset) != Gen1Constants.encounterTableEnd) { + int offset = calculateOffset(tableBank, readWord(tableOffset)); + if (!usedOffsets.contains(offset)) { + usedOffsets.add(offset); + // grass and water are exactly the same + for (int a = 0; a < 2; a++) { + int rate = rom[offset++] & 0xFF; + if (rate > 0) { + // there is data here + EncounterSet thisSet = encsetit.next(); + for (int slot = 0; slot < Gen1Constants.encounterTableSize; slot++) { + Encounter enc = thisSet.encounters.get(slot); + rom[offset] = (byte) enc.level; + rom[offset + 1] = (byte) pokeNumToRBYTable[enc.pokemon.number]; + offset += 2; + } + } + } + } + tableOffset += 2; + } + + // old rod + int oldRodOffset = romEntry.getValue("OldRodOffset"); + EncounterSet oldRodSet = encsetit.next(); + Encounter oldRodEnc = oldRodSet.encounters.get(0); + rom[oldRodOffset + 2] = (byte) oldRodEnc.level; + rom[oldRodOffset + 1] = (byte) pokeNumToRBYTable[oldRodEnc.pokemon.number]; + + // good rod + int goodRodOffset = romEntry.getValue("GoodRodOffset"); + EncounterSet goodRodSet = encsetit.next(); + for (int grSlot = 0; grSlot < 2; grSlot++) { + Encounter enc = goodRodSet.encounters.get(grSlot); + rom[goodRodOffset + grSlot * 2] = (byte) enc.level; + rom[goodRodOffset + grSlot * 2 + 1] = (byte) pokeNumToRBYTable[enc.pokemon.number]; + } + + // super rod + if (romEntry.isYellow) { + int superRodOffset = romEntry.getValue("SuperRodTableOffset"); + while ((rom[superRodOffset] & 0xFF) != 0xFF) { + superRodOffset++; + EncounterSet thisSet = encsetit.next(); + for (int encN = 0; encN < Gen1Constants.yellowSuperRodTableSize; encN++) { + Encounter enc = thisSet.encounters.get(encN); + rom[superRodOffset + 1] = (byte) enc.level; + rom[superRodOffset] = (byte) pokeNumToRBYTable[enc.pokemon.number]; + superRodOffset += 2; + } + } + } else { + // red/blue + int superRodOffset = romEntry.getValue("SuperRodTableOffset"); + int superRodBank = bankOf(superRodOffset); + List usedSROffsets = new ArrayList<>(); + while ((rom[superRodOffset] & 0xFF) != 0xFF) { + superRodOffset++; + int setOffset = calculateOffset(superRodBank, readWord(superRodOffset)); + superRodOffset += 2; + if (!usedSROffsets.contains(setOffset)) { + usedSROffsets.add(setOffset); + int pokesInSet = rom[setOffset++] & 0xFF; + EncounterSet thisSet = encsetit.next(); + for (int encN = 0; encN < pokesInSet; encN++) { + Encounter enc = thisSet.encounters.get(encN); + rom[setOffset] = (byte) enc.level; + rom[setOffset + 1] = (byte) pokeNumToRBYTable[enc.pokemon.number]; + setOffset += 2; + } + } + } + } + } + + @Override + public boolean hasWildAltFormes() { + return false; + } + + @Override + public List getPokemon() { + return pokemonList; + } + + @Override + public List getPokemonInclFormes() { + return pokemonList; + } + + @Override + public List getAltFormes() { + return new ArrayList<>(); + } + + @Override + public List getMegaEvolutions() { + return new ArrayList<>(); + } + + @Override + public Pokemon getAltFormeOfPokemon(Pokemon pk, int forme) { + return pk; + } + + @Override + public List getIrregularFormes() { + return new ArrayList<>(); + } + + @Override + public boolean hasFunctionalFormes() { + return false; + } + + public List getTrainers() { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = Gen1Constants.trainerClassCount; + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount + 1]; + for (int i = 1; i <= traineramount; i++) { + int tPointer = readWord(traineroffset + (i - 1) * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), tPointer); + } + + List tcnames = getTrainerClassesForText(); + + List allTrainers = new ArrayList<>(); + int index = 0; + for (int i = 1; i <= traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + String tcname = tcnames.get(i - 1); + for (int trnum = 0; trnum < limit; trnum++) { + index++; + Trainer tr = new Trainer(); + tr.offset = offs; + tr.index = index; + tr.trainerclass = i; + tr.fullDisplayName = tcname; + int dataType = rom[offs] & 0xFF; + if (dataType == 0xFF) { + // "Special" trainer + tr.poketype = 1; + offs++; + while (rom[offs] != 0x0) { + TrainerPokemon tpk = new TrainerPokemon(); + tpk.level = rom[offs] & 0xFF; + tpk.pokemon = pokes[pokeRBYToNumTable[rom[offs + 1] & 0xFF]]; + tr.pokemon.add(tpk); + offs += 2; + } + } else { + tr.poketype = 0; + offs++; + while (rom[offs] != 0x0) { + TrainerPokemon tpk = new TrainerPokemon(); + tpk.level = dataType; + tpk.pokemon = pokes[pokeRBYToNumTable[rom[offs] & 0xFF]]; + tr.pokemon.add(tpk); + offs++; + } + } + offs++; + allTrainers.add(tr); + } + } + Gen1Constants.tagTrainersUniversal(allTrainers); + if (romEntry.isYellow) { + Gen1Constants.tagTrainersYellow(allTrainers); + } else { + Gen1Constants.tagTrainersRB(allTrainers); + } + return allTrainers; + } + + @Override + public List getMainPlaythroughTrainers() { + return new ArrayList<>(); // Not implemented + } + + @Override + public List getEliteFourTrainers(boolean isChallengeMode) { + return new ArrayList<>(); + } + + public void setTrainers(List trainerData, boolean doubleBattleMode) { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = Gen1Constants.trainerClassCount; + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount + 1]; + for (int i = 1; i <= traineramount; i++) { + int tPointer = readWord(traineroffset + (i - 1) * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), tPointer); + } + + Iterator allTrainers = trainerData.iterator(); + for (int i = 1; i <= traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + for (int trnum = 0; trnum < limit; trnum++) { + Trainer tr = allTrainers.next(); + if (tr.trainerclass != i) { + System.err.println("Trainer mismatch: " + tr.name); + } + Iterator tPokes = tr.pokemon.iterator(); + // Write their pokemon based on poketype + if (tr.poketype == 0) { + // Regular trainer + int fixedLevel = tr.pokemon.get(0).level; + rom[offs] = (byte) fixedLevel; + offs++; + while (tPokes.hasNext()) { + TrainerPokemon tpk = tPokes.next(); + rom[offs] = (byte) pokeNumToRBYTable[tpk.pokemon.number]; + offs++; + } + } else { + // Special trainer + rom[offs] = (byte) 0xFF; + offs++; + while (tPokes.hasNext()) { + TrainerPokemon tpk = tPokes.next(); + rom[offs] = (byte) tpk.level; + rom[offs + 1] = (byte) pokeNumToRBYTable[tpk.pokemon.number]; + offs += 2; + } + } + rom[offs] = 0; + offs++; + } + } + + // Custom Moves AI Table + // Zero it out entirely. + rom[romEntry.getValue("ExtraTrainerMovesTableOffset")] = (byte) 0xFF; + + // Champion Rival overrides in Red/Blue + if (!isYellow()) { + // hacky relative offset (very likely to work but maybe not always) + int champRivalJump = romEntry.getValue("GymLeaderMovesTableOffset") + - Gen1Constants.champRivalOffsetFromGymLeaderMoves; + // nop out this jump + rom[champRivalJump] = GBConstants.gbZ80Nop; + rom[champRivalJump + 1] = GBConstants.gbZ80Nop; + } + + } + + @Override + public boolean hasRivalFinalBattle() { + return true; + } + + @Override + public boolean isYellow() { + return romEntry.isYellow; + } + + @Override + public boolean typeInGame(Type type) { + if (!type.isHackOnly && (type != Type.DARK && type != Type.STEEL && type != Type.FAIRY)) { + return true; + } + return romEntry.extraTypeReverse.containsKey(type); + } + + @Override + public List getMovesBannedFromLevelup() { + return Gen1Constants.bannedLevelupMoves; + } + + private void updateTypeEffectiveness() { + List typeEffectivenessTable = readTypeEffectivenessTable(); + log("--Updating Type Effectiveness--"); + for (TypeRelationship relationship : typeEffectivenessTable) { + // Change Poison 2x against bug (should be neutral) to Ice 0.5x against Fire (is currently neutral) + if (relationship.attacker == Type.POISON && relationship.defender == Type.BUG) { + relationship.attacker = Type.ICE; + relationship.defender = Type.FIRE; + relationship.effectiveness = Effectiveness.HALF; + log("Replaced: Poison super effective vs Bug => Ice not very effective vs Fire"); + } + + // Change Bug 2x against Poison to Bug 0.5x against Poison + else if (relationship.attacker == Type.BUG && relationship.defender == Type.POISON) { + relationship.effectiveness = Effectiveness.HALF; + log("Changed: Bug super effective vs Poison => Bug not very effective vs Poison"); + } + + // Change Ghost 0x against Psychic to Ghost 2x against Psychic + else if (relationship.attacker == Type.GHOST && relationship.defender == Type.PSYCHIC) { + relationship.effectiveness = Effectiveness.DOUBLE; + log("Changed: Psychic immune to Ghost => Ghost super effective vs Psychic"); + } + } + logBlankLine(); + writeTypeEffectivenessTable(typeEffectivenessTable); + effectivenessUpdated = true; + } + + private List readTypeEffectivenessTable() { + List typeEffectivenessTable = new ArrayList<>(); + int currentOffset = romEntry.getValue("TypeEffectivenessOffset"); + int attackingType = rom[currentOffset]; + while (attackingType != (byte) 0xFF) { + int defendingType = rom[currentOffset + 1]; + int effectivenessInternal = rom[currentOffset + 2]; + Type attacking = Gen1Constants.typeTable[attackingType]; + Type defending = Gen1Constants.typeTable[defendingType]; + Effectiveness effectiveness = null; + switch (effectivenessInternal) { + case 20: + effectiveness = Effectiveness.DOUBLE; + break; + case 10: + effectiveness = Effectiveness.NEUTRAL; + break; + case 5: + effectiveness = Effectiveness.HALF; + break; + case 0: + effectiveness = Effectiveness.ZERO; + break; + } + if (effectiveness != null) { + TypeRelationship relationship = new TypeRelationship(attacking, defending, effectiveness); + typeEffectivenessTable.add(relationship); + } + currentOffset += 3; + attackingType = rom[currentOffset]; + } + return typeEffectivenessTable; + } + + private void writeTypeEffectivenessTable(List typeEffectivenessTable) { + int currentOffset = romEntry.getValue("TypeEffectivenessOffset"); + for (TypeRelationship relationship : typeEffectivenessTable) { + rom[currentOffset] = Gen1Constants.typeToByte(relationship.attacker); + rom[currentOffset + 1] = Gen1Constants.typeToByte(relationship.defender); + byte effectivenessInternal = 0; + switch (relationship.effectiveness) { + case DOUBLE: + effectivenessInternal = 20; + break; + case NEUTRAL: + effectivenessInternal = 10; + break; + case HALF: + effectivenessInternal = 5; + break; + case ZERO: + effectivenessInternal = 0; + break; + } + rom[currentOffset + 2] = effectivenessInternal; + currentOffset += 3; + } + } + + @Override + public Map> getMovesLearnt() { + Map> movesets = new TreeMap<>(); + int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + int pkmnCount = romEntry.getValue("InternalPokemonCount"); + for (int i = 1; i <= pkmnCount; i++) { + int pointer = readWord(pointersOffset + (i - 1) * 2); + int realPointer = calculateOffset(bankOf(pointersOffset), pointer); + if (pokeRBYToNumTable[i] != 0) { + Pokemon pkmn = pokes[pokeRBYToNumTable[i]]; + int statsOffset; + if (pokeRBYToNumTable[i] == Species.mew && !romEntry.isYellow) { + // Mewww + statsOffset = romEntry.getValue("MewStatsOffset"); + } else { + statsOffset = (pokeRBYToNumTable[i] - 1) * 0x1C + pokeStatsOffset; + } + List ourMoves = new ArrayList<>(); + for (int delta = Gen1Constants.bsLevel1MovesOffset; delta < Gen1Constants.bsLevel1MovesOffset + 4; delta++) { + if (rom[statsOffset + delta] != 0x00) { + MoveLearnt learnt = new MoveLearnt(); + learnt.level = 1; + learnt.move = moveRomToNumTable[rom[statsOffset + delta] & 0xFF]; + ourMoves.add(learnt); + } + } + // Skip over evolution data + while (rom[realPointer] != 0) { + if (rom[realPointer] == 1) { + realPointer += 3; + } else if (rom[realPointer] == 2) { + realPointer += 4; + } else if (rom[realPointer] == 3) { + realPointer += 3; + } + } + realPointer++; + while (rom[realPointer] != 0) { + MoveLearnt learnt = new MoveLearnt(); + learnt.level = rom[realPointer] & 0xFF; + learnt.move = moveRomToNumTable[rom[realPointer + 1] & 0xFF]; + ourMoves.add(learnt); + realPointer += 2; + } + movesets.put(pkmn.number, ourMoves); + } + } + return movesets; + } + + @Override + public void setMovesLearnt(Map> movesets) { + // new method for moves learnt + writeEvosAndMovesLearnt(false, movesets); + } + + @Override + public Map> getEggMoves() { + // Gen 1 does not have egg moves + return new TreeMap<>(); + } + + @Override + public void setEggMoves(Map> eggMoves) { + // Gen 1 does not have egg moves + } + + private static class StaticPokemon { + protected int[] speciesOffsets; + protected int[] levelOffsets; + + public StaticPokemon() { + this.speciesOffsets = new int[0]; + this.levelOffsets = new int[0]; + } + + public Pokemon getPokemon(Gen1RomHandler rh) { + return rh.pokes[rh.pokeRBYToNumTable[rh.rom[speciesOffsets[0]] & 0xFF]]; + } + + public void setPokemon(Gen1RomHandler rh, Pokemon pkmn) { + for (int offset : speciesOffsets) { + rh.rom[offset] = (byte) rh.pokeNumToRBYTable[pkmn.number]; + } + } + + public int getLevel(byte[] rom, int i) { + if (levelOffsets.length <= i) { + return 1; + } + return rom[levelOffsets[i]]; + } + + public void setLevel(byte[] rom, int level, int i) { + rom[levelOffsets[i]] = (byte) level; + } + } + + @Override + public List getStaticPokemon() { + List statics = new ArrayList<>(); + if (romEntry.getValue("StaticPokemonSupport") > 0) { + for (StaticPokemon sp : romEntry.staticPokemon) { + StaticEncounter se = new StaticEncounter(); + se.pkmn = sp.getPokemon(this); + se.level = sp.getLevel(rom, 0); + statics.add(se); + } + } + return statics; + } + + @Override + public boolean setStaticPokemon(List staticPokemon) { + if (romEntry.getValue("StaticPokemonSupport") == 0) { + return false; + } + for (int i = 0; i < romEntry.staticPokemon.size(); i++) { + StaticEncounter se = staticPokemon.get(i); + StaticPokemon sp = romEntry.staticPokemon.get(i); + sp.setPokemon(this, se.pkmn); + sp.setLevel(rom, se.level, 0); + } + + return true; + } + + @Override + public boolean canChangeStaticPokemon() { + return (romEntry.getValue("StaticPokemonSupport") > 0); + } + + @Override + public boolean hasStaticAltFormes() { + return false; + } + + @Override + public boolean hasMainGameLegendaries() { + return false; + } + + @Override + public List getMainGameLegendaries() { + return new ArrayList<>(); + } + + @Override + public List getSpecialMusicStatics() { + return new ArrayList<>(); + } + + @Override + public void applyCorrectStaticMusic(Map specialMusicStaticChanges) { + + } + + @Override + public boolean hasStaticMusicFix() { + return false; + } + + @Override + public List getTotemPokemon() { + return new ArrayList<>(); + } + + @Override + public void setTotemPokemon(List totemPokemon) { + + } + + @Override + public List getTMMoves() { + List tms = new ArrayList<>(); + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen1Constants.tmCount; i++) { + tms.add(moveRomToNumTable[rom[offset + (i - 1)] & 0xFF]); + } + return tms; + } + + @Override + public List getHMMoves() { + List hms = new ArrayList<>(); + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen1Constants.hmCount; i++) { + hms.add(moveRomToNumTable[rom[offset + Gen1Constants.tmCount + (i - 1)] & 0xFF]); + } + return hms; + } + + @Override + public void setTMMoves(List moveIndexes) { + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen1Constants.tmCount; i++) { + rom[offset + (i - 1)] = (byte) moveNumToRomTable[moveIndexes.get(i - 1)]; + } + + // Gym Leader TM Moves (RB only) + if (!romEntry.isYellow) { + int[] tms = Gen1Constants.gymLeaderTMs; + int glMovesOffset = romEntry.getValue("GymLeaderMovesTableOffset"); + for (int i = 0; i < tms.length; i++) { + // Set the special move used by gym (i+1) to + // the move we just wrote to TM tms[i] + rom[glMovesOffset + i * 2] = (byte) moveNumToRomTable[moveIndexes.get(tms[i] - 1)]; + } + } + + // TM Text + String[] moveNames = readMoveNames(); + for (TMTextEntry tte : romEntry.tmTexts) { + String moveName = moveNames[moveNumToRomTable[moveIndexes.get(tte.number - 1)]]; + String text = tte.template.replace("%m", moveName); + writeVariableLengthString(text, tte.offset, true); + } + } + + @Override + public int getTMCount() { + return Gen1Constants.tmCount; + } + + @Override + public int getHMCount() { + return Gen1Constants.hmCount; + } + + @Override + public Map getTMHMCompatibility() { + Map compat = new TreeMap<>(); + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + for (int i = 1; i <= pokedexCount; i++) { + int baseStatsOffset = (romEntry.isYellow || i != Species.mew) ? (pokeStatsOffset + (i - 1) + * Gen1Constants.baseStatsEntrySize) : romEntry.getValue("MewStatsOffset"); + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen1Constants.tmCount + Gen1Constants.hmCount + 1]; + for (int j = 0; j < 7; j++) { + readByteIntoFlags(flags, j * 8 + 1, baseStatsOffset + Gen1Constants.bsTMHMCompatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map compatData) { + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + for (Map.Entry compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int baseStatsOffset = (romEntry.isYellow || pkmn.number != Species.mew) ? (pokeStatsOffset + (pkmn.number - 1) + * Gen1Constants.baseStatsEntrySize) + : romEntry.getValue("MewStatsOffset"); + for (int j = 0; j < 7; j++) { + rom[baseStatsOffset + Gen1Constants.bsTMHMCompatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public boolean hasMoveTutors() { + return false; + } + + @Override + public List getMoveTutorMoves() { + return new ArrayList<>(); + } + + @Override + public void setMoveTutorMoves(List moves) { + // Do nothing + } + + @Override + public Map getMoveTutorCompatibility() { + return new TreeMap<>(); + } + + @Override + public void setMoveTutorCompatibility(Map compatData) { + // Do nothing + } + + @Override + public String getROMName() { + return "Pokemon " + romEntry.name; + } + + @Override + public String getROMCode() { + return romEntry.romName + " (" + romEntry.version + "/" + romEntry.nonJapanese + ")"; + } + + @Override + public String getSupportLevel() { + return (romEntry.getValue("StaticPokemonSupport") > 0) ? "Complete" : "No Static Pokemon"; + } + + private static int find(byte[] haystack, String hexString) { + if (hexString.length() % 2 != 0) { + return -3; // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + List found = RomFunctions.search(haystack, searchFor); + if (found.size() == 0) { + return -1; // not found + } else if (found.size() > 1) { + return -2; // not unique + } else { + return found.get(0); + } + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); + + int pkmnCount = romEntry.getValue("InternalPokemonCount"); + for (int i = 1; i <= pkmnCount; i++) { + int pointer = readWord(pointersOffset + (i - 1) * 2); + int realPointer = calculateOffset(bankOf(pointersOffset), pointer); + if (pokeRBYToNumTable[i] != 0) { + int thisPoke = pokeRBYToNumTable[i]; + Pokemon pkmn = pokes[thisPoke]; + while (rom[realPointer] != 0) { + int method = rom[realPointer]; + EvolutionType type = EvolutionType.fromIndex(1, method); + int otherPoke = pokeRBYToNumTable[rom[realPointer + 2 + (type == EvolutionType.STONE ? 1 : 0)] & 0xFF]; + int extraInfo = rom[realPointer + 1] & 0xFF; + Evolution evo = new Evolution(pkmn, pokes[otherPoke], true, type, extraInfo); + if (!pkmn.evolutionsFrom.contains(evo)) { + pkmn.evolutionsFrom.add(evo); + if (pokes[otherPoke] != null) { + pokes[otherPoke].evolutionsTo.add(evo); + } + } + realPointer += (type == EvolutionType.STONE ? 4 : 3); + } + // split evos don't carry stats + if (pkmn.evolutionsFrom.size() > 1) { + for (Evolution e : pkmn.evolutionsFrom) { + e.carryStats = false; + } + } + } + } + } + + @Override + public void removeImpossibleEvolutions(Settings settings) { + // Gen 1: only regular trade evos + // change them all to evolve at level 37 + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + for (Evolution evo : pkmn.evolutionsFrom) { + if (evo.type == EvolutionType.TRADE) { + // change + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + addEvoUpdateLevel(impossibleEvolutionUpdates,evo); + } + } + } + } + } + + @Override + public void makeEvolutionsEasier(Settings settings) { + // No such thing + } + + @Override + public void removeTimeBasedEvolutions() { + // No such thing + } + + @Override + public boolean hasShopRandomization() { + return false; + } + + @Override + public Map getShopItems() { + return null; // Not implemented + } + + @Override + public void setShopItems(Map shopItems) { + // Not implemented + } + + @Override + public void setShopPrices() { + // Not implemented + } + + private List getTrainerClassesForText() { + int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); + List tcNames = new ArrayList<>(); + int offset = offsets[offsets.length - 1]; + for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { + String name = readVariableLengthString(offset, false); + offset += lengthOfStringAt(offset, false) + 1; + tcNames.add(name); + } + return tcNames; + } + + @Override + public boolean canChangeTrainerText() { + return romEntry.getValue("CanChangeTrainerText") > 0; + } + + @Override + public List getDoublesTrainerClasses() { + return Collections.emptyList(); + } + + @Override + public List getTrainerNames() { + int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); + List trainerNames = new ArrayList<>(); + int offset = offsets[offsets.length - 1]; + for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { + String name = readVariableLengthString(offset, false); + offset += lengthOfStringAt(offset, false) + 1; + if (Gen1Constants.singularTrainers.contains(j)) { + trainerNames.add(name); + } + } + return trainerNames; + } + + @Override + public void setTrainerNames(List trainerNames) { + if (romEntry.getValue("CanChangeTrainerText") > 0) { + int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); + Iterator trainerNamesI = trainerNames.iterator(); + int offset = offsets[offsets.length - 1]; + for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { + int oldLength = lengthOfStringAt(offset, false) + 1; + if (Gen1Constants.singularTrainers.contains(j)) { + String newName = trainerNamesI.next(); + writeFixedLengthString(newName, offset, oldLength); + } + offset += oldLength; + } + } + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.SAME_LENGTH; + } + + @Override + public List getTCNameLengthsByTrainer() { + // not needed + return new ArrayList<>(); + } + + @Override + public List getTrainerClassNames() { + int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); + List trainerClassNames = new ArrayList<>(); + if (offsets.length == 2) { + for (int i = 0; i < offsets.length; i++) { + int offset = offsets[i]; + for (int j = 0; j < Gen1Constants.tclassesCounts[i]; j++) { + String name = readVariableLengthString(offset, false); + offset += lengthOfStringAt(offset, false) + 1; + if (i == 0 || !Gen1Constants.singularTrainers.contains(j)) { + trainerClassNames.add(name); + } + } + } + } else { + int offset = offsets[0]; + for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { + String name = readVariableLengthString(offset, false); + offset += lengthOfStringAt(offset, false) + 1; + if (!Gen1Constants.singularTrainers.contains(j)) { + trainerClassNames.add(name); + } + } + } + return trainerClassNames; + } + + @Override + public void setTrainerClassNames(List trainerClassNames) { + if (romEntry.getValue("CanChangeTrainerText") > 0) { + int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); + Iterator tcNamesIter = trainerClassNames.iterator(); + if (offsets.length == 2) { + for (int i = 0; i < offsets.length; i++) { + int offset = offsets[i]; + for (int j = 0; j < Gen1Constants.tclassesCounts[i]; j++) { + int oldLength = lengthOfStringAt(offset, false) + 1; + if (i == 0 || !Gen1Constants.singularTrainers.contains(j)) { + String newName = tcNamesIter.next(); + writeFixedLengthString(newName, offset, oldLength); + } + offset += oldLength; + } + } + } else { + int offset = offsets[0]; + for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { + int oldLength = lengthOfStringAt(offset, false) + 1; + if (!Gen1Constants.singularTrainers.contains(j)) { + String newName = tcNamesIter.next(); + writeFixedLengthString(newName, offset, oldLength); + } + offset += oldLength; + } + } + } + + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return true; + } + + @Override + public String getDefaultExtension() { + return "gbc"; + } + + @Override + public int abilitiesPerPokemon() { + return 0; + } + + @Override + public int highestAbilityIndex() { + return 0; + } + + @Override + public Map> getAbilityVariations() { + return new HashMap<>(); + } + + @Override + public boolean hasMegaEvolutions() { + return false; + } + + @Override + public int internalStringLength(String string) { + return translateString(string).length; + } + + @Override + public int miscTweaksAvailable() { + int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); + available |= MiscTweak.UPDATE_TYPE_EFFECTIVENESS.getValue(); + + if (romEntry.tweakFiles.get("BWXPTweak") != null) { + available |= MiscTweak.BW_EXP_PATCH.getValue(); + } + if (romEntry.tweakFiles.get("XAccNerfTweak") != null) { + available |= MiscTweak.NERF_X_ACCURACY.getValue(); + } + if (romEntry.tweakFiles.get("CritRateTweak") != null) { + available |= MiscTweak.FIX_CRIT_RATE.getValue(); + } + if (romEntry.getValue("TextDelayFunctionOffset") != 0) { + available |= MiscTweak.FASTEST_TEXT.getValue(); + } + if (romEntry.getValue("PCPotionOffset") != 0) { + available |= MiscTweak.RANDOMIZE_PC_POTION.getValue(); + } + if (romEntry.getValue("PikachuEvoJumpOffset") != 0) { + available |= MiscTweak.ALLOW_PIKACHU_EVOLUTION.getValue(); + } + if (romEntry.getValue("CatchingTutorialMonOffset") != 0) { + available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); + } + + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.BW_EXP_PATCH) { + applyBWEXPPatch(); + } else if (tweak == MiscTweak.NERF_X_ACCURACY) { + applyXAccNerfPatch(); + } else if (tweak == MiscTweak.FIX_CRIT_RATE) { + applyCritRatePatch(); + } else if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestTextPatch(); + } else if (tweak == MiscTweak.RANDOMIZE_PC_POTION) { + randomizePCPotion(); + } else if (tweak == MiscTweak.ALLOW_PIKACHU_EVOLUTION) { + applyPikachuEvoPatch(); + } else if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { + applyCamelCaseNames(); + } else if (tweak == MiscTweak.UPDATE_TYPE_EFFECTIVENESS) { + updateTypeEffectiveness(); + } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { + randomizeCatchingTutorial(); + } + } + + @Override + public boolean isEffectivenessUpdated() { + return effectivenessUpdated; + } + + private void applyBWEXPPatch() { + genericIPSPatch("BWXPTweak"); + } + + private void applyXAccNerfPatch() { + xAccNerfed = genericIPSPatch("XAccNerfTweak"); + } + + private void applyCritRatePatch() { + genericIPSPatch("CritRateTweak"); + } + + private void applyFastestTextPatch() { + if (romEntry.getValue("TextDelayFunctionOffset") != 0) { + rom[romEntry.getValue("TextDelayFunctionOffset")] = GBConstants.gbZ80Ret; + } + } + + private void randomizePCPotion() { + if (romEntry.getValue("PCPotionOffset") != 0) { + rom[romEntry.getValue("PCPotionOffset")] = (byte) this.getNonBadItems().randomNonTM(this.random); + } + } + + private void applyPikachuEvoPatch() { + if (romEntry.getValue("PikachuEvoJumpOffset") != 0) { + rom[romEntry.getValue("PikachuEvoJumpOffset")] = GBConstants.gbZ80JumpRelative; + } + } + + private void randomizeCatchingTutorial() { + if (romEntry.getValue("CatchingTutorialMonOffset") != 0) { + rom[romEntry.getValue("CatchingTutorialMonOffset")] = (byte) pokeNumToRBYTable[this.randomPokemon().number]; + } + } + + @Override + public void enableGuaranteedPokemonCatching() { + int offset = find(rom, Gen1Constants.guaranteedCatchPrefix); + if (offset > 0) { + offset += Gen1Constants.guaranteedCatchPrefix.length() / 2; // because it was a prefix + + // The game ensures that the Master Ball always catches a Pokemon by running the following code: + // ; Get the item ID. + // ld hl, wcf91 + // ld a, [hl] + // + // ; The Master Ball always succeeds. + // cp MASTER_BALL + // jp z, .captured + // By making the jump here unconditional, we can ensure that catching always succeeds no + // matter the ball type. We check that the original condition is present just for safety. + if (rom[offset] == (byte)0xCA) { + rom[offset] = (byte)0xC3; + } + } + } + + private boolean genericIPSPatch(String ctName) { + String patchName = romEntry.tweakFiles.get(ctName); + if (patchName == null) { + return false; + } + + try { + FileFunctions.applyPatch(rom, patchName); + return true; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getGameBreakingMoves() { + // Sonicboom & drage & OHKO moves + // 160 add spore + // also remove OHKO if xacc nerfed + if (xAccNerfed) { + return Gen1Constants.bannedMovesWithXAccBanned; + } else { + return Gen1Constants.bannedMovesWithoutXAccBanned; + } + } + + @Override + public List getFieldMoves() { + // cut, fly, surf, strength, flash, + // dig, teleport (NOT softboiled) + return Gen1Constants.fieldMoves; + } + + @Override + public List getEarlyRequiredHMMoves() { + // just cut + return Gen1Constants.earlyRequiredHMs; + } + + @Override + public void randomizeIntroPokemon() { + // First off, intro Pokemon + // 160 add yellow intro random + int introPokemon = pokeNumToRBYTable[this.randomPokemon().number]; + rom[romEntry.getValue("IntroPokemonOffset")] = (byte) introPokemon; + rom[romEntry.getValue("IntroCryOffset")] = (byte) introPokemon; + + } + + @Override + public ItemList getAllowedItems() { + return Gen1Constants.allowedItems; + } + + @Override + public ItemList getNonBadItems() { + // Gen 1 has no bad items Kappa + return Gen1Constants.allowedItems; + } + + @Override + public List getUniqueNoSellItems() { + return new ArrayList<>(); + } + + @Override + public List getRegularShopItems() { + return null; // Not implemented + } + + @Override + public List getOPShopItems() { + return null; // Not implemented + } + + private void loadItemNames() { + itemNames = new String[256]; + itemNames[0] = "glitch"; + // trying to emulate pretty much what the game does here + // normal items + int origOffset = romEntry.getValue("ItemNamesOffset"); + int itemNameOffset = origOffset; + for (int index = 1; index <= 0x100; index++) { + if (itemNameOffset / GBConstants.bankSize > origOffset / GBConstants.bankSize) { + // the game would continue making its merry way into VRAM here, + // but we don't have VRAM to simulate. + // just give up. + break; + } + int startOfText = itemNameOffset; + while ((rom[itemNameOffset] & 0xFF) != GBConstants.stringTerminator) { + itemNameOffset++; + } + itemNameOffset++; + itemNames[index % 256] = readFixedLengthString(startOfText, 20); + } + // hms override + for (int index = Gen1Constants.hmsStartIndex; index < Gen1Constants.tmsStartIndex; index++) { + itemNames[index] = String.format("HM%02d", index - Gen1Constants.hmsStartIndex + 1); + } + // tms override + for (int index = Gen1Constants.tmsStartIndex; index < 0x100; index++) { + itemNames[index] = String.format("TM%02d", index - Gen1Constants.tmsStartIndex + 1); + } + } + + @Override + public String[] getItemNames() { + return itemNames; + } + + private static class SubMap { + private int id; + private int addr; + private int bank; + private MapHeader header; + private Connection[] cons; + private int n_cons; + private int obj_addr; + private List itemOffsets; + } + + private static class MapHeader { + private int tileset_id; // u8 + private int map_h, map_w; // u8 + private int map_ptr, text_ptr, script_ptr; // u16 + private int connect_byte; // u8 + // 10 bytes + } + + private static class Connection { + private int index; // u8 + private int connected_map; // u16 + private int current_map; // u16 + private int bigness; // u8 + private int map_width; // u8 + private int y_align; // u8 + private int x_align; // u8 + private int window; // u16 + // 11 bytes + } + + private void preloadMaps() { + int mapBanks = romEntry.getValue("MapBanks"); + int mapAddresses = romEntry.getValue("MapAddresses"); + + preloadMap(mapBanks, mapAddresses, 0); + } + + private void preloadMap(int mapBanks, int mapAddresses, int mapID) { + + if (maps[mapID] != null || mapID == 0xED || mapID == 0xFF) { + return; + } + + SubMap map = new SubMap(); + maps[mapID] = map; + + map.id = mapID; + map.addr = calculateOffset(rom[mapBanks + mapID] & 0xFF, readWord(mapAddresses + mapID * 2)); + map.bank = bankOf(map.addr); + + map.header = new MapHeader(); + map.header.tileset_id = rom[map.addr] & 0xFF; + map.header.map_h = rom[map.addr + 1] & 0xFF; + map.header.map_w = rom[map.addr + 2] & 0xFF; + map.header.map_ptr = calculateOffset(map.bank, readWord(map.addr + 3)); + map.header.text_ptr = calculateOffset(map.bank, readWord(map.addr + 5)); + map.header.script_ptr = calculateOffset(map.bank, readWord(map.addr + 7)); + map.header.connect_byte = rom[map.addr + 9] & 0xFF; + + int cb = map.header.connect_byte; + map.n_cons = ((cb & 8) >> 3) + ((cb & 4) >> 2) + ((cb & 2) >> 1) + (cb & 1); + + int cons_offset = map.addr + 10; + + map.cons = new Connection[map.n_cons]; + for (int i = 0; i < map.n_cons; i++) { + int tcon_offs = cons_offset + i * 11; + Connection con = new Connection(); + con.index = rom[tcon_offs] & 0xFF; + con.connected_map = readWord(tcon_offs + 1); + con.current_map = readWord(tcon_offs + 3); + con.bigness = rom[tcon_offs + 5] & 0xFF; + con.map_width = rom[tcon_offs + 6] & 0xFF; + con.y_align = rom[tcon_offs + 7] & 0xFF; + con.x_align = rom[tcon_offs + 8] & 0xFF; + con.window = readWord(tcon_offs + 9); + map.cons[i] = con; + preloadMap(mapBanks, mapAddresses, con.index); + } + map.obj_addr = calculateOffset(map.bank, readWord(cons_offset + map.n_cons * 11)); + + // Read objects + // +0 is the border tile (ignore) + // +1 is warp count + + int n_warps = rom[map.obj_addr + 1] & 0xFF; + int offs = map.obj_addr + 2; + for (int i = 0; i < n_warps; i++) { + // track this warp + int to_map = rom[offs + 3] & 0xFF; + preloadMap(mapBanks, mapAddresses, to_map); + offs += 4; + } + + // Now we're pointing to sign count + int n_signs = rom[offs++] & 0xFF; + offs += n_signs * 3; + + // Finally, entities, which contain the items + map.itemOffsets = new ArrayList<>(); + int n_entities = rom[offs++] & 0xFF; + for (int i = 0; i < n_entities; i++) { + // Read text ID + int tid = rom[offs + 5] & 0xFF; + if ((tid & (1 << 6)) > 0) { + // trainer + offs += 8; + } else if ((tid & (1 << 7)) > 0 && (rom[offs + 6] != 0x00)) { + // item + map.itemOffsets.add(offs + 6); + offs += 7; + } else { + // generic + offs += 6; + } + } + } + + private void loadMapNames() { + mapNames = new String[256]; + int mapNameTableOffset = romEntry.getValue("MapNameTableOffset"); + int mapNameBank = bankOf(mapNameTableOffset); + // external names + List usedExternal = new ArrayList<>(); + for (int i = 0; i < 0x25; i++) { + int externalOffset = calculateOffset(mapNameBank, readWord(mapNameTableOffset + 1)); + usedExternal.add(externalOffset); + mapNames[i] = readVariableLengthString(externalOffset, false); + mapNameTableOffset += 3; + } + + // internal names + int lastMaxMap = 0x25; + Map previousMapCounts = new HashMap<>(); + while ((rom[mapNameTableOffset] & 0xFF) != 0xFF) { + int maxMap = rom[mapNameTableOffset] & 0xFF; + int nameOffset = calculateOffset(mapNameBank, readWord(mapNameTableOffset + 2)); + String actualName = readVariableLengthString(nameOffset, false).trim(); + if (usedExternal.contains(nameOffset)) { + for (int i = lastMaxMap; i < maxMap; i++) { + if (maps[i] != null) { + mapNames[i] = actualName + " (Building)"; + } + } + } else { + int mapCount = 0; + if (previousMapCounts.containsKey(nameOffset)) { + mapCount = previousMapCounts.get(nameOffset); + } + for (int i = lastMaxMap; i < maxMap; i++) { + if (maps[i] != null) { + mapCount++; + mapNames[i] = actualName + " (" + mapCount + ")"; + } + } + previousMapCounts.put(nameOffset, mapCount); + } + lastMaxMap = maxMap; + mapNameTableOffset += 4; + } + } + + private List getItemOffsets() { + + List itemOffs = new ArrayList<>(); + + for (SubMap map : maps) { + if (map != null) { + itemOffs.addAll(map.itemOffsets); + } + } + + int hiRoutine = romEntry.getValue("HiddenItemRoutine"); + int spclTable = romEntry.getValue("SpecialMapPointerTable"); + int spclBank = bankOf(spclTable); + + if (!isYellow()) { + + int lOffs = romEntry.getValue("SpecialMapList"); + int idx = 0; + + while ((rom[lOffs] & 0xFF) != 0xFF) { + + int spclOffset = calculateOffset(spclBank, readWord(spclTable + idx)); + + while ((rom[spclOffset] & 0xFF) != 0xFF) { + if (calculateOffset(rom[spclOffset + 3] & 0xFF, readWord(spclOffset + 4)) == hiRoutine) { + itemOffs.add(spclOffset + 2); + } + spclOffset += 6; + } + lOffs++; + idx += 2; + } + } else { + + int lOffs = spclTable; + + while ((rom[lOffs] & 0xFF) != 0xFF) { + + int spclOffset = calculateOffset(spclBank, readWord(lOffs + 1)); + + while ((rom[spclOffset] & 0xFF) != 0xFF) { + if (calculateOffset(rom[spclOffset + 3] & 0xFF, readWord(spclOffset + 4)) == hiRoutine) { + itemOffs.add(spclOffset + 2); + } + spclOffset += 6; + } + lOffs += 3; + } + } + + return itemOffs; + } + + @Override + public List getRequiredFieldTMs() { + return Gen1Constants.requiredFieldTMs; + } + + @Override + public List getCurrentFieldTMs() { + List itemOffsets = getItemOffsets(); + List fieldTMs = new ArrayList<>(); + + for (int offset : itemOffsets) { + int itemHere = rom[offset] & 0xFF; + if (Gen1Constants.allowedItems.isTM(itemHere)) { + fieldTMs.add(itemHere - Gen1Constants.tmsStartIndex + 1); // TM + // offset + } + } + return fieldTMs; + } + + @Override + public void setFieldTMs(List fieldTMs) { + List itemOffsets = getItemOffsets(); + Iterator iterTMs = fieldTMs.iterator(); + + for (int offset : itemOffsets) { + int itemHere = rom[offset] & 0xFF; + if (Gen1Constants.allowedItems.isTM(itemHere)) { + // Replace this with a TM from the list + rom[offset] = (byte) (iterTMs.next() + Gen1Constants.tmsStartIndex - 1); + } + } + } + + @Override + public List getRegularFieldItems() { + List itemOffsets = getItemOffsets(); + List fieldItems = new ArrayList<>(); + + for (int offset : itemOffsets) { + int itemHere = rom[offset] & 0xFF; + if (Gen1Constants.allowedItems.isAllowed(itemHere) && !(Gen1Constants.allowedItems.isTM(itemHere))) { + fieldItems.add(itemHere); + } + } + return fieldItems; + } + + @Override + public void setRegularFieldItems(List items) { + List itemOffsets = getItemOffsets(); + Iterator iterItems = items.iterator(); + + for (int offset : itemOffsets) { + int itemHere = rom[offset] & 0xFF; + if (Gen1Constants.allowedItems.isAllowed(itemHere) && !(Gen1Constants.allowedItems.isTM(itemHere))) { + // Replace it + rom[offset] = (byte) (iterItems.next().intValue()); + } + } + + } + + @Override + public List getIngameTrades() { + List trades = new ArrayList<>(); + + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int nicknameLength = romEntry.getValue("TradeNameLength"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = nicknameLength + 3; + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = new IngameTrade(); + int entryOffset = tableOffset + entry * entryLength; + trade.requestedPokemon = pokes[pokeRBYToNumTable[rom[entryOffset] & 0xFF]]; + trade.givenPokemon = pokes[pokeRBYToNumTable[rom[entryOffset + 1] & 0xFF]]; + trade.nickname = readString(entryOffset + 3, nicknameLength, false); + trades.add(trade); + } + + return trades; + } + + @Override + public void setIngameTrades(List trades) { + + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int nicknameLength = romEntry.getValue("TradeNameLength"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = nicknameLength + 3; + int tradeOffset = 0; + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = trades.get(tradeOffset++); + int entryOffset = tableOffset + entry * entryLength; + rom[entryOffset] = (byte) pokeNumToRBYTable[trade.requestedPokemon.number]; + rom[entryOffset + 1] = (byte) pokeNumToRBYTable[trade.givenPokemon.number]; + if (romEntry.getValue("CanChangeTrainerText") > 0) { + writeFixedLengthString(trade.nickname, entryOffset + 3, nicknameLength); + } + } + } + + @Override + public boolean hasDVs() { + return true; + } + + @Override + public int generationOfPokemon() { + return 1; + } + + @Override + public void removeEvosForPokemonPool() { + // gen1 doesn't have this functionality anyway + } + + @Override + public boolean supportsFourStartingMoves() { + return true; + } + + private void writeEvosAndMovesLearnt(boolean writeEvos, Map> movesets) { + // we assume a few things here: + // 1) evos & moves learnt are stored directly after their pointer table + // 2) PokemonMovesetsExtraSpaceOffset is in the same bank, and + // points to the start of the free space at the end of the bank + // (if set to 0, disabled from being used) + // 3) PokemonMovesetsDataSize is from the start of actual data to + // the start of engine/battle/e_2.asm in pokered (aka code we can't + // overwrite) + // it appears that in yellow, this code is moved + // so we can write the evos/movesets in one continuous block + // until the end of the bank. + // so for yellow, extraspace is disabled. + // specify null to either argument to copy old values + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + int movesEvosStart = romEntry.getValue("PokemonMovesetsTableOffset"); + int movesEvosBank = bankOf(movesEvosStart); + int pkmnCount = romEntry.getValue("InternalPokemonCount"); + byte[] pointerTable = new byte[pkmnCount * 2]; + int mainDataBlockSize = romEntry.getValue("PokemonMovesetsDataSize"); + int mainDataBlockOffset = movesEvosStart + pointerTable.length; + byte[] mainDataBlock = new byte[mainDataBlockSize]; + int offsetInMainData = 0; + int extraSpaceOffset = romEntry.getValue("PokemonMovesetsExtraSpaceOffset"); + int extraSpaceBank = bankOf(extraSpaceOffset); + boolean extraSpaceEnabled = false; + byte[] extraDataBlock = null; + int offsetInExtraData = 0; + int extraSpaceSize = 0; + if (movesEvosBank == extraSpaceBank && extraSpaceOffset != 0) { + extraSpaceEnabled = true; + int startOfNextBank = ((extraSpaceOffset / GBConstants.bankSize) + 1) * GBConstants.bankSize; + extraSpaceSize = startOfNextBank - extraSpaceOffset; + extraDataBlock = new byte[extraSpaceSize]; + } + int nullEntryPointer = -1; + + for (int i = 1; i <= pkmnCount; i++) { + byte[] writeData = null; + int oldDataOffset = calculateOffset(movesEvosBank, readWord(movesEvosStart + (i - 1) * 2)); + boolean setNullEntryPointerHere = false; + if (pokeRBYToNumTable[i] == 0) { + // null entry + if (nullEntryPointer == -1) { + // make the null entry + writeData = new byte[] { 0, 0 }; + setNullEntryPointerHere = true; + } else { + writeWord(pointerTable, (i - 1) * 2, nullEntryPointer); + } + } else { + int pokeNum = pokeRBYToNumTable[i]; + Pokemon pkmn = pokes[pokeNum]; + ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); + // Evolutions + if (!writeEvos) { + // copy old + int evoOffset = oldDataOffset; + while (rom[evoOffset] != 0x00) { + int method = rom[evoOffset] & 0xFF; + int limiter = (method == 2) ? 4 : 3; + for (int b = 0; b < limiter; b++) { + dataStream.write(rom[evoOffset++] & 0xFF); + } + } + } else { + for (Evolution evo : pkmn.evolutionsFrom) { + // write evos for this poke + dataStream.write(evo.type.toIndex(1)); + if (evo.type == EvolutionType.LEVEL) { + dataStream.write(evo.extraInfo); // min lvl + } else if (evo.type == EvolutionType.STONE) { + dataStream.write(evo.extraInfo); // stone item + dataStream.write(1); // minimum level + } else if (evo.type == EvolutionType.TRADE) { + dataStream.write(1); // minimum level + } + int pokeIndexTo = pokeNumToRBYTable[evo.to.number]; + dataStream.write(pokeIndexTo); // species + } + } + // write terminator for evos + dataStream.write(0); + + // Movesets + if (movesets == null) { + // copy old + int movesOffset = oldDataOffset; + // move past evos + while (rom[movesOffset] != 0x00) { + int method = rom[movesOffset] & 0xFF; + movesOffset += (method == 2) ? 4 : 3; + } + movesOffset++; + // copy moves + while (rom[movesOffset] != 0x00) { + dataStream.write(rom[movesOffset++] & 0xFF); + dataStream.write(rom[movesOffset++] & 0xFF); + } + } else { + List ourMoves = movesets.get(pkmn.number); + int statsOffset; + if (pokeNum == Species.mew && !romEntry.isYellow) { + // Mewww + statsOffset = romEntry.getValue("MewStatsOffset"); + } else { + statsOffset = (pokeNum - 1) * Gen1Constants.baseStatsEntrySize + pokeStatsOffset; + } + int movenum = 0; + while (movenum < 4 && ourMoves.size() > movenum && ourMoves.get(movenum).level == 1) { + rom[statsOffset + Gen1Constants.bsLevel1MovesOffset + movenum] = (byte) moveNumToRomTable[ourMoves + .get(movenum).move]; + movenum++; + } + // Write out the rest of zeroes + for (int mn = movenum; mn < 4; mn++) { + rom[statsOffset + Gen1Constants.bsLevel1MovesOffset + mn] = 0; + } + // Add the non level 1 moves to the data stream + while (movenum < ourMoves.size()) { + dataStream.write(ourMoves.get(movenum).level); + dataStream.write(moveNumToRomTable[ourMoves.get(movenum).move]); + movenum++; + } + } + // terminator + dataStream.write(0); + + // done, set writeData + writeData = dataStream.toByteArray(); + try { + dataStream.close(); + } catch (IOException e) { + } + } + + // write data and set pointer? + if (writeData != null) { + int lengthToFit = writeData.length; + int pointerToWrite; + // compression of leading & trailing 0s: + // every entry ends in a 0 (end of move list). + // if a block already has data in it, and the data + // we want to write starts with a 0 (no evolutions) + // we can compress it into the end of the last entry + // this saves a decent amount of space overall. + if ((offsetInMainData + lengthToFit <= mainDataBlockSize) + || (writeData[0] == 0 && offsetInMainData > 0 && offsetInMainData + lengthToFit == mainDataBlockSize + 1)) { + // place in main storage + if (writeData[0] == 0 && offsetInMainData > 0) { + int writtenDataOffset = mainDataBlockOffset + offsetInMainData - 1; + pointerToWrite = makeGBPointer(writtenDataOffset); + System.arraycopy(writeData, 1, mainDataBlock, offsetInMainData, lengthToFit - 1); + offsetInMainData += lengthToFit - 1; + } else { + int writtenDataOffset = mainDataBlockOffset + offsetInMainData; + pointerToWrite = makeGBPointer(writtenDataOffset); + System.arraycopy(writeData, 0, mainDataBlock, offsetInMainData, lengthToFit); + offsetInMainData += lengthToFit; + } + } else if (extraSpaceEnabled + && ((offsetInExtraData + lengthToFit <= extraSpaceSize) || (writeData[0] == 0 + && offsetInExtraData > 0 && offsetInExtraData + lengthToFit == extraSpaceSize + 1))) { + // place in extra space + if (writeData[0] == 0 && offsetInExtraData > 0) { + int writtenDataOffset = extraSpaceOffset + offsetInExtraData - 1; + pointerToWrite = makeGBPointer(writtenDataOffset); + System.arraycopy(writeData, 1, extraDataBlock, offsetInExtraData, lengthToFit - 1); + offsetInExtraData += lengthToFit - 1; + } else { + int writtenDataOffset = extraSpaceOffset + offsetInExtraData; + pointerToWrite = makeGBPointer(writtenDataOffset); + System.arraycopy(writeData, 0, extraDataBlock, offsetInExtraData, lengthToFit); + offsetInExtraData += lengthToFit; + } + } else { + // this should never happen, but if not, uh oh + throw new RandomizationException("Unable to save moves/evolutions, out of space"); + } + if (pointerToWrite >= 0) { + writeWord(pointerTable, (i - 1) * 2, pointerToWrite); + if (setNullEntryPointerHere) { + nullEntryPointer = pointerToWrite; + } + } + } + } + + // Done, write final results to ROM + System.arraycopy(pointerTable, 0, rom, movesEvosStart, pointerTable.length); + System.arraycopy(mainDataBlock, 0, rom, mainDataBlockOffset, mainDataBlock.length); + if (extraSpaceEnabled) { + System.arraycopy(extraDataBlock, 0, rom, extraSpaceOffset, extraDataBlock.length); + } + } + + @Override + public boolean isRomValid() { + return romEntry.expectedCRC32 == actualCRC32; + } + + @Override + public BufferedImage getMascotImage() { + Pokemon mascot = randomPokemon(); + int idx = pokeNumToRBYTable[mascot.number]; + int fsBank; + // define (by index number) the bank that a pokemon's image is in + // using pokered code + if (mascot.number == Species.mew && !romEntry.isYellow) { + fsBank = 1; + } else if (idx < 0x1F) { + fsBank = 0x9; + } else if (idx < 0x4A) { + fsBank = 0xA; + } else if (idx < 0x74 || idx == 0x74 && mascot.frontSpritePointer > 0x7000) { + fsBank = 0xB; + } else if (idx < 0x99 || idx == 0x99 && mascot.frontSpritePointer > 0x7000) { + fsBank = 0xC; + } else { + fsBank = 0xD; + } + + int fsOffset = calculateOffset(fsBank, mascot.frontSpritePointer); + Gen1Decmp mscSprite = new Gen1Decmp(rom, fsOffset); + mscSprite.decompress(); + mscSprite.transpose(); + int w = mscSprite.getWidth(); + int h = mscSprite.getHeight(); + + // Palette? + int[] palette; + if (romEntry.getValue("MonPaletteIndicesOffset") > 0 && romEntry.getValue("SGBPalettesOffset") > 0) { + int palIndex = rom[romEntry.getValue("MonPaletteIndicesOffset") + mascot.number] & 0xFF; + int palOffset = romEntry.getValue("SGBPalettesOffset") + palIndex * 8; + if (romEntry.isYellow && romEntry.nonJapanese == 1) { + // Non-japanese Yellow can use GBC palettes instead. + // Stored directly after regular SGB palettes. + palOffset += 320; + } + palette = new int[4]; + for (int i = 0; i < 4; i++) { + palette[i] = GFXFunctions.conv16BitColorToARGB(readWord(palOffset + i * 2)); + } + } else { + palette = new int[] { 0xFFFFFFFF, 0xFFAAAAAA, 0xFF666666, 0xFF000000 }; + } + + byte[] data = mscSprite.getFlattenedData(); + + BufferedImage bim = GFXFunctions.drawTiledImage(data, palette, w, h, 8); + GFXFunctions.pseudoTransparency(bim, palette[0]); + + return bim; + } + +} diff --git a/src/com/pkrandom/romhandlers/Gen2RomHandler.java b/src/com/pkrandom/romhandlers/Gen2RomHandler.java new file mode 100755 index 0000000..2cf4a77 --- /dev/null +++ b/src/com/pkrandom/romhandlers/Gen2RomHandler.java @@ -0,0 +1,2999 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- Gen2RomHandler.java - randomizer handler for G/S/C. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.pkrandom.*; +import com.pkrandom.constants.*; +import com.pkrandom.exceptions.RandomizerIOException; +import com.pkrandom.pokemon.*; +import compressors.Gen2Decmp; + +public class Gen2RomHandler extends AbstractGBCRomHandler { + + public static class Factory extends RomHandler.Factory { + + @Override + public Gen2RomHandler create(Random random, PrintStream logStream) { + return new Gen2RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + long fileLength = new File(filename).length(); + if (fileLength > 8 * 1024 * 1024) { + return false; + } + byte[] loaded = loadFilePartial(filename, 0x1000); + // nope + return loaded.length != 0 && detectRomInner(loaded, (int) fileLength); + } + } + + public Gen2RomHandler(Random random) { + super(random, null); + } + + public Gen2RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + private static class RomEntry { + private String name; + private String romCode; + private int version, nonJapanese; + private String extraTableFile; + private boolean isCrystal; + private long expectedCRC32 = -1; + private int crcInHeader = -1; + private Map codeTweaks = new HashMap<>(); + private List tmTexts = new ArrayList<>(); + private Map entries = new HashMap<>(); + private Map arrayEntries = new HashMap<>(); + private Map strings = new HashMap<>(); + private List staticPokemon = new ArrayList<>(); + + private int getValue(String key) { + if (!entries.containsKey(key)) { + entries.put(key, 0); + } + return entries.get(key); + } + + private String getString(String key) { + if (!strings.containsKey(key)) { + strings.put(key, ""); + } + return strings.get(key); + } + } + + private static class TMTextEntry { + private int number; + private int offset; + private String template; + } + + private static List roms; + + static { + loadROMInfo(); + } + + private static void loadROMInfo() { + roms = new ArrayList<>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen2_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + r[0] = r[0].trim(); + if (r[0].equals("StaticPokemon{}")) { + current.staticPokemon.add(parseStaticPokemon(r[1], false)); + } else if (r[0].equals("StaticPokemonGameCorner{}")) { + current.staticPokemon.add(parseStaticPokemon(r[1], true)); + } else if (r[0].equals("TMText[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] parts = r[1].substring(1, r[1].length() - 1).split(",", 3); + TMTextEntry tte = new TMTextEntry(); + tte.number = parseRIInt(parts[0]); + tte.offset = parseRIInt(parts[1]); + tte.template = parts[2]; + current.tmTexts.add(tte); + } + } else if (r[0].equals("Game")) { + current.romCode = r[1]; + } else if (r[0].equals("Version")) { + current.version = parseRIInt(r[1]); + } else if (r[0].equals("NonJapanese")) { + current.nonJapanese = parseRIInt(r[1]); + } else if (r[0].equals("Type")) { + current.isCrystal = r[1].equalsIgnoreCase("Crystal"); + } else if (r[0].equals("ExtraTableFile")) { + current.extraTableFile = r[1]; + } else if (r[0].equals("CRCInHeader")) { + current.crcInHeader = parseRIInt(r[1]); + } else if (r[0].equals("CRC32")) { + current.expectedCRC32 = parseRILong("0x" + r[1]); + } else if (r[0].endsWith("Tweak")) { + current.codeTweaks.put(r[0], r[1]); + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.name)) { + // copy from here + boolean cSP = (current.getValue("CopyStaticPokemon") == 1); + boolean cTT = (current.getValue("CopyTMText") == 1); + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.entries.putAll(otherEntry.entries); + current.strings.putAll(otherEntry.strings); + if (cSP) { + current.staticPokemon.addAll(otherEntry.staticPokemon); + current.entries.put("StaticPokemonSupport", 1); + } else { + current.entries.put("StaticPokemonSupport", 0); + current.entries.remove("StaticPokemonOddEggOffset"); + current.entries.remove("StaticPokemonOddEggDataSize"); + } + if (cTT) { + current.tmTexts.addAll(otherEntry.tmTexts); + } + current.extraTableFile = otherEntry.extraTableFile; + } + } + } else if (r[0].endsWith("Locator") || r[0].endsWith("Prefix")) { + current.strings.put(r[0], r[1]); + } else { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.arrayEntries.put(r[0], offs); + } + } else { + int offs = parseRIInt(r[1]); + current.entries.put(r[0], offs); + } + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + System.err.println("File not found!"); + } + + } + + private static StaticPokemon parseStaticPokemon(String staticPokemonString, boolean isGameCorner) { + StaticPokemon sp; + if (isGameCorner) { + sp = new StaticPokemonGameCorner(); + } else { + sp = new StaticPokemon(); + } + String pattern = "[A-z]+=\\[(0x[0-9a-fA-F]+,?\\s?)+]"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(staticPokemonString); + while (m.find()) { + String[] segments = m.group().split("="); + String[] romOffsets = segments[1].substring(1, segments[1].length() - 1).split(","); + int[] offsets = new int [romOffsets.length]; + for (int i = 0; i < offsets.length; i++) { + offsets[i] = parseRIInt(romOffsets[i]); + } + switch (segments[0]) { + case "Species": + sp.speciesOffsets = offsets; + break; + case "Level": + sp.levelOffsets = offsets; + break; + } + } + return sp; + } + + private static int parseRIInt(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Integer.parseInt(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + private static long parseRILong(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Long.parseLong(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + // This ROM's data + private Pokemon[] pokes; + private List pokemonList; + private RomEntry romEntry; + private Move[] moves; + private boolean havePatchedFleeing; + private String[] itemNames; + private List itemOffs; + private String[][] mapNames; + private String[] landmarkNames; + private boolean isVietCrystal; + private ItemList allowedItems, nonBadItems; + private long actualCRC32; + private boolean effectivenessUpdated; + + @Override + public boolean detectRom(byte[] rom) { + return detectRomInner(rom, rom.length); + } + + private static boolean detectRomInner(byte[] rom, int romSize) { + // size check + return romSize >= GBConstants.minRomSize && romSize <= GBConstants.maxRomSize && checkRomEntry(rom) != null; + } + + @Override + public void loadedRom() { + romEntry = checkRomEntry(this.rom); + clearTextTables(); + readTextTable("gameboy_jpn"); + if (romEntry.extraTableFile != null && !romEntry.extraTableFile.equalsIgnoreCase("none")) { + readTextTable(romEntry.extraTableFile); + } + // VietCrystal override + if (romEntry.name.equals("Crystal (J)") + && rom[Gen2Constants.vietCrystalCheckOffset] == Gen2Constants.vietCrystalCheckValue) { + readTextTable("vietcrystal"); + isVietCrystal = true; + } else { + isVietCrystal = false; + } + havePatchedFleeing = false; + loadPokemonStats(); + pokemonList = Arrays.asList(pokes); + loadMoves(); + loadLandmarkNames(); + preprocessMaps(); + loadItemNames(); + allowedItems = Gen2Constants.allowedItems.copy(); + nonBadItems = Gen2Constants.nonBadItems.copy(); + actualCRC32 = FileFunctions.getCRC32(rom); + // VietCrystal: exclude Burn Heal, Calcium, TwistedSpoon, and Elixir + // crashes your game if used, glitches out your inventory if carried + if (isVietCrystal) { + allowedItems.banSingles(Gen2Items.burnHeal, Gen2Items.calcium, Gen2Items.elixer, Gen2Items.twistedSpoon); + } + } + + private static RomEntry checkRomEntry(byte[] rom) { + int version = rom[GBConstants.versionOffset] & 0xFF; + int nonjap = rom[GBConstants.jpFlagOffset] & 0xFF; + // Check for specific CRC first + int crcInHeader = ((rom[GBConstants.crcOffset] & 0xFF) << 8) | (rom[GBConstants.crcOffset + 1] & 0xFF); + for (RomEntry re : roms) { + if (romCode(rom, re.romCode) && re.version == version && re.nonJapanese == nonjap + && re.crcInHeader == crcInHeader) { + return re; + } + } + // Now check for non-specific-CRC entries + for (RomEntry re : roms) { + if (romCode(rom, re.romCode) && re.version == version && re.nonJapanese == nonjap && re.crcInHeader == -1) { + return re; + } + } + // Not found + return null; + } + + @Override + public void savingRom() { + savePokemonStats(); + saveMoves(); + } + + private void loadPokemonStats() { + pokes = new Pokemon[Gen2Constants.pokemonCount + 1]; + // Fetch our names + String[] pokeNames = readPokemonNames(); + int offs = romEntry.getValue("PokemonStatsOffset"); + // Get base stats + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i], offs + (i - 1) * Gen2Constants.baseStatsEntrySize); + // Name? + pokes[i].name = pokeNames[i]; + } + + // Get evolutions + populateEvolutions(); + + } + + private void savePokemonStats() { + // Write pokemon names + int offs = romEntry.getValue("PokemonNamesOffset"); + int len = romEntry.getValue("PokemonNamesLength"); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int stringOffset = offs + (i - 1) * len; + writeFixedLengthString(pokes[i].name, stringOffset, len); + } + // Write pokemon stats + int offs2 = romEntry.getValue("PokemonStatsOffset"); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + saveBasicPokeStats(pokes[i], offs2 + (i - 1) * Gen2Constants.baseStatsEntrySize); + } + // Write evolutions + writeEvosAndMovesLearnt(true, null); + } + + private String[] readMoveNames() { + int offset = romEntry.getValue("MoveNamesOffset"); + String[] moveNames = new String[Gen2Constants.moveCount + 1]; + for (int i = 1; i <= Gen2Constants.moveCount; i++) { + moveNames[i] = readVariableLengthString(offset, false); + offset += lengthOfStringAt(offset, false) + 1; + } + return moveNames; + } + + private void loadMoves() { + moves = new Move[Gen2Constants.moveCount + 1]; + String[] moveNames = readMoveNames(); + int offs = romEntry.getValue("MoveDataOffset"); + for (int i = 1; i <= Gen2Constants.moveCount; i++) { + moves[i] = new Move(); + moves[i].name = moveNames[i]; + moves[i].number = i; + moves[i].internalId = i; + moves[i].effectIndex = rom[offs + (i - 1) * 7 + 1] & 0xFF; + moves[i].hitratio = ((rom[offs + (i - 1) * 7 + 4] & 0xFF)) / 255.0 * 100; + moves[i].power = rom[offs + (i - 1) * 7 + 2] & 0xFF; + moves[i].pp = rom[offs + (i - 1) * 7 + 5] & 0xFF; + moves[i].type = Gen2Constants.typeTable[rom[offs + (i - 1) * 7 + 3]]; + moves[i].category = GBConstants.physicalTypes.contains(moves[i].type) ? MoveCategory.PHYSICAL : MoveCategory.SPECIAL; + if (moves[i].power == 0 && !GlobalConstants.noPowerNonStatusMoves.contains(i)) { + moves[i].category = MoveCategory.STATUS; + } + + if (i == Moves.swift) { + perfectAccuracy = (int)moves[i].hitratio; + } + + if (GlobalConstants.normalMultihitMoves.contains(i)) { + moves[i].hitCount = 3; + } else if (GlobalConstants.doubleHitMoves.contains(i)) { + moves[i].hitCount = 2; + } else if (i == Moves.tripleKick) { + moves[i].hitCount = 2.71; // this assumes the first hit lands + } + + // Values taken from effect_priorities.asm from the Gen 2 disassemblies. + if (moves[i].effectIndex == Gen2Constants.priorityHitEffectIndex) { + moves[i].priority = 2; + } else if (moves[i].effectIndex == Gen2Constants.protectEffectIndex || + moves[i].effectIndex == Gen2Constants.endureEffectIndex) { + moves[i].priority = 3; + } else if (moves[i].effectIndex == Gen2Constants.forceSwitchEffectIndex || + moves[i].effectIndex == Gen2Constants.counterEffectIndex || + moves[i].effectIndex == Gen2Constants.mirrorCoatEffectIndex) { + moves[i].priority = 0; + } else { + moves[i].priority = 1; + } + + double secondaryEffectChance = ((rom[offs + (i - 1) * 7 + 6] & 0xFF)) / 255.0 * 100; + loadStatChangesFromEffect(moves[i], secondaryEffectChance); + loadStatusFromEffect(moves[i], secondaryEffectChance); + loadMiscMoveInfoFromEffect(moves[i], secondaryEffectChance); + } + } + + private void loadStatChangesFromEffect(Move move, double secondaryEffectChance) { + switch (move.effectIndex) { + case Gen2Constants.noDamageAtkPlusOneEffect: + case Gen2Constants.damageUserAtkPlusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 1; + break; + case Gen2Constants.noDamageDefPlusOneEffect: + case Gen2Constants.damageUserDefPlusOneEffect: + case Gen2Constants.defenseCurlEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = 1; + break; + case Gen2Constants.noDamageSpAtkPlusOneEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_ATTACK; + move.statChanges[0].stages = 1; + break; + case Gen2Constants.noDamageEvasionPlusOneEffect: + move.statChanges[0].type = StatChangeType.EVASION; + move.statChanges[0].stages = 1; + break; + case Gen2Constants.noDamageAtkMinusOneEffect: + case Gen2Constants.damageAtkMinusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = -1; + break; + case Gen2Constants.noDamageDefMinusOneEffect: + case Gen2Constants.damageDefMinusOneEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = -1; + break; + case Gen2Constants.noDamageSpeMinusOneEffect: + case Gen2Constants.damageSpeMinusOneEffect: + move.statChanges[0].type = StatChangeType.SPEED; + move.statChanges[0].stages = -1; + break; + case Gen2Constants.noDamageAccuracyMinusOneEffect: + case Gen2Constants.damageAccuracyMinusOneEffect: + move.statChanges[0].type = StatChangeType.ACCURACY; + move.statChanges[0].stages = -1; + break; + case Gen2Constants.noDamageEvasionMinusOneEffect: + move.statChanges[0].type = StatChangeType.EVASION; + move.statChanges[0].stages = -1; + break; + case Gen2Constants.noDamageAtkPlusTwoEffect: + case Gen2Constants.swaggerEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 2; + break; + case Gen2Constants.noDamageDefPlusTwoEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = 2; + break; + case Gen2Constants.noDamageSpePlusTwoEffect: + move.statChanges[0].type = StatChangeType.SPEED; + move.statChanges[0].stages = 2; + break; + case Gen2Constants.noDamageSpDefPlusTwoEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[0].stages = 2; + break; + case Gen2Constants.noDamageAtkMinusTwoEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = -2; + break; + case Gen2Constants.noDamageDefMinusTwoEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = -2; + break; + case Gen2Constants.noDamageSpeMinusTwoEffect: + move.statChanges[0].type = StatChangeType.SPEED; + move.statChanges[0].stages = -2; + break; + case Gen2Constants.noDamageSpDefMinusTwoEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[0].stages = -2; + break; + case Gen2Constants.damageSpDefMinusOneEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[0].stages = -1; + break; + case Gen2Constants.damageUserAllPlusOneEffect: + move.statChanges[0].type = StatChangeType.ALL; + move.statChanges[0].stages = 1; + break; + default: + // Move does not have a stat-changing effect + return; + } + + switch (move.effectIndex) { + case Gen2Constants.noDamageAtkPlusOneEffect: + case Gen2Constants.noDamageDefPlusOneEffect: + case Gen2Constants.noDamageSpAtkPlusOneEffect: + case Gen2Constants.noDamageEvasionPlusOneEffect: + case Gen2Constants.noDamageAtkMinusOneEffect: + case Gen2Constants.noDamageDefMinusOneEffect: + case Gen2Constants.noDamageSpeMinusOneEffect: + case Gen2Constants.noDamageAccuracyMinusOneEffect: + case Gen2Constants.noDamageEvasionMinusOneEffect: + case Gen2Constants.noDamageAtkPlusTwoEffect: + case Gen2Constants.noDamageDefPlusTwoEffect: + case Gen2Constants.noDamageSpePlusTwoEffect: + case Gen2Constants.noDamageSpDefPlusTwoEffect: + case Gen2Constants.noDamageAtkMinusTwoEffect: + case Gen2Constants.noDamageDefMinusTwoEffect: + case Gen2Constants.noDamageSpeMinusTwoEffect: + case Gen2Constants.noDamageSpDefMinusTwoEffect: + case Gen2Constants.swaggerEffect: + case Gen2Constants.defenseCurlEffect: + if (move.statChanges[0].stages < 0 || move.effectIndex == Gen2Constants.swaggerEffect) { + move.statChangeMoveType = StatChangeMoveType.NO_DAMAGE_TARGET; + } else { + move.statChangeMoveType = StatChangeMoveType.NO_DAMAGE_USER; + } + break; + + case Gen2Constants.damageAtkMinusOneEffect: + case Gen2Constants.damageDefMinusOneEffect: + case Gen2Constants.damageSpeMinusOneEffect: + case Gen2Constants.damageSpDefMinusOneEffect: + case Gen2Constants.damageAccuracyMinusOneEffect: + move.statChangeMoveType = StatChangeMoveType.DAMAGE_TARGET; + break; + + case Gen2Constants.damageUserDefPlusOneEffect: + case Gen2Constants.damageUserAtkPlusOneEffect: + case Gen2Constants.damageUserAllPlusOneEffect: + move.statChangeMoveType = StatChangeMoveType.DAMAGE_USER; + break; + } + + if (move.statChangeMoveType == StatChangeMoveType.DAMAGE_TARGET || move.statChangeMoveType == StatChangeMoveType.DAMAGE_USER) { + for (int i = 0; i < move.statChanges.length; i++) { + if (move.statChanges[i].type != StatChangeType.NONE) { + move.statChanges[i].percentChance = secondaryEffectChance; + if (move.statChanges[i].percentChance == 0.0) { + move.statChanges[i].percentChance = 100.0; + } + } + } + } + } + + private void loadStatusFromEffect(Move move, double secondaryEffectChance) { + switch (move.effectIndex) { + case Gen2Constants.noDamageSleepEffect: + case Gen2Constants.toxicEffect: + case Gen2Constants.noDamageConfusionEffect: + case Gen2Constants.noDamagePoisonEffect: + case Gen2Constants.noDamageParalyzeEffect: + case Gen2Constants.swaggerEffect: + move.statusMoveType = StatusMoveType.NO_DAMAGE; + break; + + case Gen2Constants.damagePoisonEffect: + case Gen2Constants.damageBurnEffect: + case Gen2Constants.damageFreezeEffect: + case Gen2Constants.damageParalyzeEffect: + case Gen2Constants.damageConfusionEffect: + case Gen2Constants.twineedleEffect: + case Gen2Constants.damageBurnAndThawUserEffect: + case Gen2Constants.thunderEffect: + move.statusMoveType = StatusMoveType.DAMAGE; + break; + + default: + // Move does not have a status effect + return; + } + + switch (move.effectIndex) { + case Gen2Constants.noDamageSleepEffect: + move.statusType = StatusType.SLEEP; + break; + case Gen2Constants.damagePoisonEffect: + case Gen2Constants.noDamagePoisonEffect: + case Gen2Constants.twineedleEffect: + move.statusType = StatusType.POISON; + break; + case Gen2Constants.damageBurnEffect: + case Gen2Constants.damageBurnAndThawUserEffect: + move.statusType = StatusType.BURN; + break; + case Gen2Constants.damageFreezeEffect: + move.statusType = StatusType.FREEZE; + break; + case Gen2Constants.damageParalyzeEffect: + case Gen2Constants.noDamageParalyzeEffect: + case Gen2Constants.thunderEffect: + move.statusType = StatusType.PARALYZE; + break; + case Gen2Constants.toxicEffect: + move.statusType = StatusType.TOXIC_POISON; + break; + case Gen2Constants.noDamageConfusionEffect: + case Gen2Constants.damageConfusionEffect: + case Gen2Constants.swaggerEffect: + move.statusType = StatusType.CONFUSION; + break; + } + + if (move.statusMoveType == StatusMoveType.DAMAGE) { + move.statusPercentChance = secondaryEffectChance; + if (move.statusPercentChance == 0.0) { + move.statusPercentChance = 100.0; + } + } + } + + private void loadMiscMoveInfoFromEffect(Move move, double secondaryEffectChance) { + switch (move.effectIndex) { + case Gen2Constants.flinchEffect: + case Gen2Constants.snoreEffect: + case Gen2Constants.twisterEffect: + case Gen2Constants.stompEffect: + move.flinchPercentChance = secondaryEffectChance; + break; + + case Gen2Constants.damageAbsorbEffect: + case Gen2Constants.dreamEaterEffect: + move.absorbPercent = 50; + break; + + case Gen2Constants.damageRecoilEffect: + move.recoilPercent = 25; + break; + + case Gen2Constants.flailAndReversalEffect: + case Gen2Constants.futureSightEffect: + move.criticalChance = CriticalChance.NONE; + break; + + case Gen2Constants.bindingEffect: + case Gen2Constants.trappingEffect: + move.isTrapMove = true; + break; + + case Gen2Constants.razorWindEffect: + case Gen2Constants.skyAttackEffect: + case Gen2Constants.skullBashEffect: + case Gen2Constants.solarbeamEffect: + case Gen2Constants.semiInvulnerableEffect: + move.isChargeMove = true; + break; + + case Gen2Constants.hyperBeamEffect: + move.isRechargeMove = true; + break; + } + + if (Gen2Constants.increasedCritMoves.contains(move.number)) { + move.criticalChance = CriticalChance.INCREASED; + } + } + + private void saveMoves() { + int offs = romEntry.getValue("MoveDataOffset"); + for (int i = 1; i <= 251; i++) { + rom[offs + (i - 1) * 7 + 1] = (byte) moves[i].effectIndex; + rom[offs + (i - 1) * 7 + 2] = (byte) moves[i].power; + rom[offs + (i - 1) * 7 + 3] = Gen2Constants.typeToByte(moves[i].type); + int hitratio = (int) Math.round(moves[i].hitratio * 2.55); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 255) { + hitratio = 255; + } + rom[offs + (i - 1) * 7 + 4] = (byte) hitratio; + rom[offs + (i - 1) * 7 + 5] = (byte) moves[i].pp; + } + } + + public List getMoves() { + return Arrays.asList(moves); + } + + private void loadBasicPokeStats(Pokemon pkmn, int offset) { + pkmn.hp = rom[offset + Gen2Constants.bsHPOffset] & 0xFF; + pkmn.attack = rom[offset + Gen2Constants.bsAttackOffset] & 0xFF; + pkmn.defense = rom[offset + Gen2Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = rom[offset + Gen2Constants.bsSpeedOffset] & 0xFF; + pkmn.spatk = rom[offset + Gen2Constants.bsSpAtkOffset] & 0xFF; + pkmn.spdef = rom[offset + Gen2Constants.bsSpDefOffset] & 0xFF; + // Type + pkmn.primaryType = Gen2Constants.typeTable[rom[offset + Gen2Constants.bsPrimaryTypeOffset] & 0xFF]; + pkmn.secondaryType = Gen2Constants.typeTable[rom[offset + Gen2Constants.bsSecondaryTypeOffset] & 0xFF]; + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + pkmn.catchRate = rom[offset + Gen2Constants.bsCatchRateOffset] & 0xFF; + pkmn.guaranteedHeldItem = -1; + pkmn.commonHeldItem = rom[offset + Gen2Constants.bsCommonHeldItemOffset] & 0xFF; + pkmn.rareHeldItem = rom[offset + Gen2Constants.bsRareHeldItemOffset] & 0xFF; + pkmn.darkGrassHeldItem = -1; + pkmn.growthCurve = ExpCurve.fromByte(rom[offset + Gen2Constants.bsGrowthCurveOffset]); + pkmn.picDimensions = rom[offset + Gen2Constants.bsPicDimensionsOffset] & 0xFF; + + } + + private void saveBasicPokeStats(Pokemon pkmn, int offset) { + rom[offset + Gen2Constants.bsHPOffset] = (byte) pkmn.hp; + rom[offset + Gen2Constants.bsAttackOffset] = (byte) pkmn.attack; + rom[offset + Gen2Constants.bsDefenseOffset] = (byte) pkmn.defense; + rom[offset + Gen2Constants.bsSpeedOffset] = (byte) pkmn.speed; + rom[offset + Gen2Constants.bsSpAtkOffset] = (byte) pkmn.spatk; + rom[offset + Gen2Constants.bsSpDefOffset] = (byte) pkmn.spdef; + rom[offset + Gen2Constants.bsPrimaryTypeOffset] = Gen2Constants.typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + rom[offset + Gen2Constants.bsSecondaryTypeOffset] = rom[offset + Gen2Constants.bsPrimaryTypeOffset]; + } else { + rom[offset + Gen2Constants.bsSecondaryTypeOffset] = Gen2Constants.typeToByte(pkmn.secondaryType); + } + rom[offset + Gen2Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + + rom[offset + Gen2Constants.bsCommonHeldItemOffset] = (byte) pkmn.commonHeldItem; + rom[offset + Gen2Constants.bsRareHeldItemOffset] = (byte) pkmn.rareHeldItem; + rom[offset + Gen2Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + } + + private String[] readPokemonNames() { + int offs = romEntry.getValue("PokemonNamesOffset"); + int len = romEntry.getValue("PokemonNamesLength"); + String[] names = new String[Gen2Constants.pokemonCount + 1]; + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + names[i] = readFixedLengthString(offs + (i - 1) * len, len); + } + return names; + } + + @Override + public List getStarters() { + // Get the starters + List starters = new ArrayList<>(); + starters.add(pokes[rom[romEntry.arrayEntries.get("StarterOffsets1")[0]] & 0xFF]); + starters.add(pokes[rom[romEntry.arrayEntries.get("StarterOffsets2")[0]] & 0xFF]); + starters.add(pokes[rom[romEntry.arrayEntries.get("StarterOffsets3")[0]] & 0xFF]); + return starters; + } + + @Override + public boolean setStarters(List newStarters) { + if (newStarters.size() != 3) { + return false; + } + + // Actually write + + for (int i = 0; i < 3; i++) { + byte starter = (byte) newStarters.get(i).number; + int[] offsets = romEntry.arrayEntries.get("StarterOffsets" + (i + 1)); + for (int offset : offsets) { + rom[offset] = starter; + } + } + + // Attempt to replace text + if (romEntry.getValue("CanChangeStarterText") > 0) { + int[] starterTextOffsets = romEntry.arrayEntries.get("StarterTextOffsets"); + for (int i = 0; i < 3 && i < starterTextOffsets.length; i++) { + writeVariableLengthString(String.format("%s?\\e", newStarters.get(i).name), starterTextOffsets[i], true); + } + } + return true; + } + + @Override + public boolean hasStarterAltFormes() { + return false; + } + + @Override + public int starterCount() { + return 3; + } + + @Override + public Map getUpdatedPokemonStats(int generation) { + return GlobalConstants.getStatChanges(generation); + } + + @Override + public boolean supportsStarterHeldItems() { + return true; + } + + @Override + public List getStarterHeldItems() { + List sHeldItems = new ArrayList<>(); + int[] shiOffsets = romEntry.arrayEntries.get("StarterHeldItems"); + for (int offset : shiOffsets) { + sHeldItems.add(rom[offset] & 0xFF); + } + return sHeldItems; + } + + @Override + public void setStarterHeldItems(List items) { + int[] shiOffsets = romEntry.arrayEntries.get("StarterHeldItems"); + if (items.size() != shiOffsets.length) { + return; + } + Iterator sHeldItems = items.iterator(); + for (int offset : shiOffsets) { + rom[offset] = sHeldItems.next().byteValue(); + } + } + + @Override + public List getEncounters(boolean useTimeOfDay) { + int offset = romEntry.getValue("WildPokemonOffset"); + List areas = new ArrayList<>(); + offset = readLandEncounters(offset, areas, useTimeOfDay); // Johto + offset = readSeaEncounters(offset, areas); // Johto + offset = readLandEncounters(offset, areas, useTimeOfDay); // Kanto + offset = readSeaEncounters(offset, areas); // Kanto + offset = readLandEncounters(offset, areas, useTimeOfDay); // Specials + offset = readSeaEncounters(offset, areas); // Specials + + // Fishing Data + offset = romEntry.getValue("FishingWildsOffset"); + int rootOffset = offset; + for (int k = 0; k < Gen2Constants.fishingGroupCount; k++) { + EncounterSet es = new EncounterSet(); + es.displayName = "Fishing Group " + (k + 1); + for (int i = 0; i < Gen2Constants.pokesPerFishingGroup; i++) { + offset++; + int pokeNum = rom[offset++] & 0xFF; + int level = rom[offset++] & 0xFF; + if (pokeNum == 0) { + if (!useTimeOfDay) { + // read the encounter they put here for DAY + int specialOffset = rootOffset + Gen2Constants.fishingGroupEntryLength + * Gen2Constants.pokesPerFishingGroup * Gen2Constants.fishingGroupCount + level * 4 + 2; + Encounter enc = new Encounter(); + enc.pokemon = pokes[rom[specialOffset] & 0xFF]; + enc.level = rom[specialOffset + 1] & 0xFF; + es.encounters.add(enc); + } + // else will be handled by code below + } else { + Encounter enc = new Encounter(); + enc.pokemon = pokes[pokeNum]; + enc.level = level; + es.encounters.add(enc); + } + } + areas.add(es); + } + if (useTimeOfDay) { + for (int k = 0; k < Gen2Constants.timeSpecificFishingGroupCount; k++) { + EncounterSet es = new EncounterSet(); + es.displayName = "Time-Specific Fishing " + (k + 1); + for (int i = 0; i < Gen2Constants.pokesPerTSFishingGroup; i++) { + int pokeNum = rom[offset++] & 0xFF; + int level = rom[offset++] & 0xFF; + Encounter enc = new Encounter(); + enc.pokemon = pokes[pokeNum]; + enc.level = level; + es.encounters.add(enc); + } + areas.add(es); + } + } + + // Headbutt Data + offset = romEntry.getValue("HeadbuttWildsOffset"); + int limit = romEntry.getValue("HeadbuttTableSize"); + for (int i = 0; i < limit; i++) { + EncounterSet es = new EncounterSet(); + es.displayName = "Headbutt Trees Set " + (i + 1); + while ((rom[offset] & 0xFF) != 0xFF) { + offset++; + int pokeNum = rom[offset++] & 0xFF; + int level = rom[offset++] & 0xFF; + Encounter enc = new Encounter(); + enc.pokemon = pokes[pokeNum]; + enc.level = level; + es.encounters.add(enc); + } + offset++; + areas.add(es); + } + + // Bug Catching Contest Data + offset = romEntry.getValue("BCCWildsOffset"); + EncounterSet bccES = new EncounterSet(); + bccES.displayName = "Bug Catching Contest"; + while ((rom[offset] & 0xFF) != 0xFF) { + offset++; + Encounter enc = new Encounter(); + enc.pokemon = pokes[rom[offset++] & 0xFF]; + enc.level = rom[offset++] & 0xFF; + enc.maxLevel = rom[offset++] & 0xFF; + bccES.encounters.add(enc); + } + // Unown is banned for Bug Catching Contest (5/8/2016) + bccES.bannedPokemon.add(pokes[Species.unown]); + areas.add(bccES); + + return areas; + } + + private int readLandEncounters(int offset, List areas, boolean useTimeOfDay) { + String[] todNames = new String[] { "Morning", "Day", "Night" }; + while ((rom[offset] & 0xFF) != 0xFF) { + int mapBank = rom[offset] & 0xFF; + int mapNumber = rom[offset + 1] & 0xFF; + String mapName = mapNames[mapBank][mapNumber]; + if (useTimeOfDay) { + for (int i = 0; i < 3; i++) { + EncounterSet encset = new EncounterSet(); + encset.rate = rom[offset + 2 + i] & 0xFF; + encset.displayName = mapName + " Grass/Cave (" + todNames[i] + ")"; + for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { + Encounter enc = new Encounter(); + enc.level = rom[offset + 5 + (i * Gen2Constants.landEncounterSlots * 2) + (j * 2)] & 0xFF; + enc.maxLevel = 0; + enc.pokemon = pokes[rom[offset + 5 + (i * Gen2Constants.landEncounterSlots * 2) + (j * 2) + 1] & 0xFF]; + encset.encounters.add(enc); + } + areas.add(encset); + } + } else { + // Use Day only + EncounterSet encset = new EncounterSet(); + encset.rate = rom[offset + 3] & 0xFF; + encset.displayName = mapName + " Grass/Cave"; + for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { + Encounter enc = new Encounter(); + enc.level = rom[offset + 5 + Gen2Constants.landEncounterSlots * 2 + (j * 2)] & 0xFF; + enc.maxLevel = 0; + enc.pokemon = pokes[rom[offset + 5 + Gen2Constants.landEncounterSlots * 2 + (j * 2) + 1] & 0xFF]; + encset.encounters.add(enc); + } + areas.add(encset); + } + offset += 5 + 6 * Gen2Constants.landEncounterSlots; + } + return offset + 1; + } + + private int readSeaEncounters(int offset, List areas) { + while ((rom[offset] & 0xFF) != 0xFF) { + int mapBank = rom[offset] & 0xFF; + int mapNumber = rom[offset + 1] & 0xFF; + String mapName = mapNames[mapBank][mapNumber]; + EncounterSet encset = new EncounterSet(); + encset.rate = rom[offset + 2] & 0xFF; + encset.displayName = mapName + " Surfing"; + for (int j = 0; j < Gen2Constants.seaEncounterSlots; j++) { + Encounter enc = new Encounter(); + enc.level = rom[offset + 3 + (j * 2)] & 0xFF; + enc.maxLevel = 0; + enc.pokemon = pokes[rom[offset + 3 + (j * 2) + 1] & 0xFF]; + encset.encounters.add(enc); + } + areas.add(encset); + offset += 3 + Gen2Constants.seaEncounterSlots * 2; + } + return offset + 1; + } + + @Override + public void setEncounters(boolean useTimeOfDay, List encounters) { + if (!havePatchedFleeing) { + patchFleeing(); + } + int offset = romEntry.getValue("WildPokemonOffset"); + Iterator areas = encounters.iterator(); + offset = writeLandEncounters(offset, areas, useTimeOfDay); // Johto + offset = writeSeaEncounters(offset, areas); // Johto + offset = writeLandEncounters(offset, areas, useTimeOfDay); // Kanto + offset = writeSeaEncounters(offset, areas); // Kanto + offset = writeLandEncounters(offset, areas, useTimeOfDay); // Specials + offset = writeSeaEncounters(offset, areas); // Specials + + // Fishing Data + offset = romEntry.getValue("FishingWildsOffset"); + for (int k = 0; k < Gen2Constants.fishingGroupCount; k++) { + EncounterSet es = areas.next(); + Iterator encs = es.encounters.iterator(); + for (int i = 0; i < Gen2Constants.pokesPerFishingGroup; i++) { + offset++; + if (rom[offset] == 0) { + if (!useTimeOfDay) { + // overwrite with a static encounter + Encounter enc = encs.next(); + rom[offset++] = (byte) enc.pokemon.number; + rom[offset++] = (byte) enc.level; + } else { + // else handle below + offset += 2; + } + } else { + Encounter enc = encs.next(); + rom[offset++] = (byte) enc.pokemon.number; + rom[offset++] = (byte) enc.level; + } + } + } + if (useTimeOfDay) { + for (int k = 0; k < Gen2Constants.timeSpecificFishingGroupCount; k++) { + EncounterSet es = areas.next(); + Iterator encs = es.encounters.iterator(); + for (int i = 0; i < Gen2Constants.pokesPerTSFishingGroup; i++) { + Encounter enc = encs.next(); + rom[offset++] = (byte) enc.pokemon.number; + rom[offset++] = (byte) enc.level; + } + } + } + + // Headbutt Data + offset = romEntry.getValue("HeadbuttWildsOffset"); + int limit = romEntry.getValue("HeadbuttTableSize"); + for (int i = 0; i < limit; i++) { + EncounterSet es = areas.next(); + Iterator encs = es.encounters.iterator(); + while ((rom[offset] & 0xFF) != 0xFF) { + Encounter enc = encs.next(); + offset++; + rom[offset++] = (byte) enc.pokemon.number; + rom[offset++] = (byte) enc.level; + } + offset++; + } + + // Bug Catching Contest Data + offset = romEntry.getValue("BCCWildsOffset"); + EncounterSet bccES = areas.next(); + Iterator bccEncs = bccES.encounters.iterator(); + while ((rom[offset] & 0xFF) != 0xFF) { + offset++; + Encounter enc = bccEncs.next(); + rom[offset++] = (byte) enc.pokemon.number; + rom[offset++] = (byte) enc.level; + rom[offset++] = (byte) enc.maxLevel; + } + + } + + private int writeLandEncounters(int offset, Iterator areas, boolean useTimeOfDay) { + while ((rom[offset] & 0xFF) != 0xFF) { + if (useTimeOfDay) { + for (int i = 0; i < 3; i++) { + EncounterSet encset = areas.next(); + Iterator encountersHere = encset.encounters.iterator(); + for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { + Encounter enc = encountersHere.next(); + rom[offset + 5 + (i * Gen2Constants.landEncounterSlots * 2) + (j * 2)] = (byte) enc.level; + rom[offset + 5 + (i * Gen2Constants.landEncounterSlots * 2) + (j * 2) + 1] = (byte) enc.pokemon.number; + } + } + } else { + // Write the set to all 3 equally + EncounterSet encset = areas.next(); + for (int i = 0; i < 3; i++) { + Iterator encountersHere = encset.encounters.iterator(); + for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { + Encounter enc = encountersHere.next(); + rom[offset + 5 + (i * Gen2Constants.landEncounterSlots * 2) + (j * 2)] = (byte) enc.level; + rom[offset + 5 + (i * Gen2Constants.landEncounterSlots * 2) + (j * 2) + 1] = (byte) enc.pokemon.number; + } + } + } + offset += 5 + 6 * Gen2Constants.landEncounterSlots; + } + return offset + 1; + } + + private int writeSeaEncounters(int offset, Iterator areas) { + while ((rom[offset] & 0xFF) != 0xFF) { + EncounterSet encset = areas.next(); + Iterator encountersHere = encset.encounters.iterator(); + for (int j = 0; j < Gen2Constants.seaEncounterSlots; j++) { + Encounter enc = encountersHere.next(); + rom[offset + 3 + (j * 2)] = (byte) enc.level; + rom[offset + 3 + (j * 2) + 1] = (byte) enc.pokemon.number; + } + offset += 3 + Gen2Constants.seaEncounterSlots * 2; + } + return offset + 1; + } + + @Override + public List getTrainers() { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = romEntry.getValue("TrainerClassAmount"); + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount]; + for (int i = 0; i < traineramount; i++) { + int pointer = readWord(traineroffset + i * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), pointer); + } + + List tcnames = this.getTrainerClassNames(); + + List allTrainers = new ArrayList<>(); + int index = 0; + for (int i = 0; i < traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + for (int trnum = 0; trnum < limit; trnum++) { + index++; + Trainer tr = new Trainer(); + tr.offset = offs; + tr.index = index; + tr.trainerclass = i; + String name = readVariableLengthString(offs, false); + tr.name = name; + tr.fullDisplayName = tcnames.get(i) + " " + name; + offs += lengthOfStringAt(offs, false) + 1; + int dataType = rom[offs] & 0xFF; + tr.poketype = dataType; + offs++; + while ((rom[offs] & 0xFF) != 0xFF) { + TrainerPokemon tp = new TrainerPokemon(); + tp.level = rom[offs] & 0xFF; + tp.pokemon = pokes[rom[offs + 1] & 0xFF]; + offs += 2; + if ((dataType & 2) == 2) { + tp.heldItem = rom[offs] & 0xFF; + offs++; + } + if ((dataType & 1) == 1) { + for (int move = 0; move < 4; move++) { + tp.moves[move] = rom[offs + move] & 0xFF; + } + offs += 4; + } + tr.pokemon.add(tp); + } + allTrainers.add(tr); + offs++; + } + } + + Gen2Constants.universalTrainerTags(allTrainers); + if (romEntry.isCrystal) { + Gen2Constants.crystalTags(allTrainers); + } else { + Gen2Constants.goldSilverTags(allTrainers); + } + + return allTrainers; + } + + @Override + public List getMainPlaythroughTrainers() { + return new ArrayList<>(); // Not implemented + } + + @Override + public List getEliteFourTrainers(boolean isChallengeMode) { + return new ArrayList<>(); + } + + @Override + public void setTrainers(List trainerData, boolean doubleBattleMode) { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = romEntry.getValue("TrainerClassAmount"); + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount]; + for (int i = 0; i < traineramount; i++) { + int pointer = readWord(traineroffset + i * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), pointer); + } + + // Get current movesets in case we need to reset them for certain + // trainer mons. + Map> movesets = this.getMovesLearnt(); + + Iterator allTrainers = trainerData.iterator(); + for (int i = 0; i < traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + for (int trnum = 0; trnum < limit; trnum++) { + Trainer tr = allTrainers.next(); + if (tr.trainerclass != i) { + System.err.println("Trainer mismatch: " + tr.name); + } + // Write their name + int trnamelen = internalStringLength(tr.name); + writeFixedLengthString(tr.name, offs, trnamelen + 1); + offs += trnamelen + 1; + // Write out new trainer data + rom[offs++] = (byte) tr.poketype; + Iterator tPokes = tr.pokemon.iterator(); + for (int tpnum = 0; tpnum < tr.pokemon.size(); tpnum++) { + TrainerPokemon tp = tPokes.next(); + rom[offs] = (byte) tp.level; + rom[offs + 1] = (byte) tp.pokemon.number; + offs += 2; + if (tr.pokemonHaveItems()) { + rom[offs] = (byte) tp.heldItem; + offs++; + } + if (tr.pokemonHaveCustomMoves()) { + if (tp.resetMoves) { + int[] pokeMoves = RomFunctions.getMovesAtLevel(tp.pokemon.number, movesets, tp.level); + for (int m = 0; m < 4; m++) { + rom[offs + m] = (byte) pokeMoves[m]; + } + } else { + rom[offs] = (byte) tp.moves[0]; + rom[offs + 1] = (byte) tp.moves[1]; + rom[offs + 2] = (byte) tp.moves[2]; + rom[offs + 3] = (byte) tp.moves[3]; + } + offs += 4; + } + } + rom[offs] = (byte) 0xFF; + offs++; + } + } + + } + + @Override + public List getPokemon() { + return pokemonList; + } + + @Override + public List getPokemonInclFormes() { + return pokemonList; + } + + @Override + public List getAltFormes() { + return new ArrayList<>(); + } + + @Override + public List getMegaEvolutions() { + return new ArrayList<>(); + } + + @Override + public Pokemon getAltFormeOfPokemon(Pokemon pk, int forme) { + return pk; + } + + @Override + public List getIrregularFormes() { + return new ArrayList<>(); + } + + @Override + public boolean hasFunctionalFormes() { + return false; + } + + @Override + public Map> getMovesLearnt() { + Map> movesets = new TreeMap<>(); + int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int pointer = readWord(pointersOffset + (i - 1) * 2); + int realPointer = calculateOffset(bankOf(pointersOffset), pointer); + Pokemon pkmn = pokes[i]; + // Skip over evolution data + while (rom[realPointer] != 0) { + if (rom[realPointer] == 5) { + realPointer += 4; + } else { + realPointer += 3; + } + } + List ourMoves = new ArrayList<>(); + realPointer++; + while (rom[realPointer] != 0) { + MoveLearnt learnt = new MoveLearnt(); + learnt.level = rom[realPointer] & 0xFF; + learnt.move = rom[realPointer + 1] & 0xFF; + ourMoves.add(learnt); + realPointer += 2; + } + movesets.put(pkmn.number, ourMoves); + } + return movesets; + } + + @Override + public void setMovesLearnt(Map> movesets) { + writeEvosAndMovesLearnt(false, movesets); + } + + @Override + public List getMovesBannedFromLevelup() { + return Gen2Constants.bannedLevelupMoves; + } + + @Override + public Map> getEggMoves() { + Map> eggMoves = new TreeMap<>(); + int pointersOffset = romEntry.getValue("EggMovesTableOffset"); + int baseOffset = (pointersOffset / 0x1000) * 0x1000; + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int eggMovePointer = FileFunctions.read2ByteInt(rom, pointersOffset + ((i - 1) * 2)); + int eggMoveOffset = baseOffset + (eggMovePointer % 0x1000); + List moves = new ArrayList<>(); + int val = rom[eggMoveOffset] & 0xFF; + while (val != 0xFF) { + moves.add(val); + eggMoveOffset++; + val = rom[eggMoveOffset] & 0xFF; + } + if (moves.size() > 0) { + eggMoves.put(i, moves); + } + } + return eggMoves; + } + + @Override + public void setEggMoves(Map> eggMoves) { + int pointersOffset = romEntry.getValue("EggMovesTableOffset"); + int baseOffset = (pointersOffset / 0x1000) * 0x1000; + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int eggMovePointer = FileFunctions.read2ByteInt(rom, pointersOffset + ((i - 1) * 2)); + int eggMoveOffset = baseOffset + (eggMovePointer % 0x1000); + if (eggMoves.containsKey(i)) { + List moves = eggMoves.get(i); + for (int move: moves) { + rom[eggMoveOffset] = (byte) move; + eggMoveOffset++; + } + } + } + } + + private static class StaticPokemon { + protected int[] speciesOffsets; + protected int[] levelOffsets; + + public StaticPokemon() { + this.speciesOffsets = new int[0]; + this.levelOffsets = new int[0]; + } + + public Pokemon getPokemon(Gen2RomHandler rh) { + return rh.pokes[rh.rom[speciesOffsets[0]] & 0xFF]; + } + + public void setPokemon(Gen2RomHandler rh, Pokemon pkmn) { + for (int offset : speciesOffsets) { + rh.rom[offset] = (byte) pkmn.number; + } + } + + public int getLevel(byte[] rom, int i) { + if (levelOffsets.length <= i) { + return 1; + } + return rom[levelOffsets[i]]; + } + + public void setLevel(byte[] rom, int level, int i) { + if (levelOffsets.length > i) { // Might not have a level entry e.g., it's an egg + rom[levelOffsets[i]] = (byte) level; + } + } + } + + private static class StaticPokemonGameCorner extends StaticPokemon { + @Override + public void setPokemon(Gen2RomHandler rh, Pokemon pkmn) { + // Last offset is a pointer to the name + int offsetSize = speciesOffsets.length; + for (int i = 0; i < offsetSize - 1; i++) { + rh.rom[speciesOffsets[i]] = (byte) pkmn.number; + } + rh.writePaddedPokemonName(pkmn.name, rh.romEntry.getValue("GameCornerPokemonNameLength"), + speciesOffsets[offsetSize - 1]); + } + } + + @Override + public List getStaticPokemon() { + List statics = new ArrayList<>(); + int[] staticEggOffsets = new int[0]; + if (romEntry.arrayEntries.containsKey("StaticEggPokemonOffsets")) { + staticEggOffsets = romEntry.arrayEntries.get("StaticEggPokemonOffsets"); + } + if (romEntry.getValue("StaticPokemonSupport") > 0) { + for (int i = 0; i < romEntry.staticPokemon.size(); i++) { + int currentOffset = i; + StaticPokemon sp = romEntry.staticPokemon.get(i); + StaticEncounter se = new StaticEncounter(); + se.pkmn = sp.getPokemon(this); + se.level = sp.getLevel(rom, 0); + se.isEgg = Arrays.stream(staticEggOffsets).anyMatch(x-> x == currentOffset); + statics.add(se); + } + } + if (romEntry.getValue("StaticPokemonOddEggOffset") > 0) { + int oeOffset = romEntry.getValue("StaticPokemonOddEggOffset"); + int oeSize = romEntry.getValue("StaticPokemonOddEggDataSize"); + for (int i = 0; i < Gen2Constants.oddEggPokemonCount; i++) { + StaticEncounter se = new StaticEncounter(); + se.pkmn = pokes[rom[oeOffset + i * oeSize] & 0xFF]; + se.isEgg = true; + statics.add(se); + } + } + return statics; + } + + @Override + public boolean setStaticPokemon(List staticPokemon) { + if (romEntry.getValue("StaticPokemonSupport") == 0) { + return false; + } + if (!havePatchedFleeing) { + patchFleeing(); + } + + int desiredSize = romEntry.staticPokemon.size(); + if (romEntry.getValue("StaticPokemonOddEggOffset") > 0) { + desiredSize += Gen2Constants.oddEggPokemonCount; + } + + if (staticPokemon.size() != desiredSize) { + return false; + } + + Iterator statics = staticPokemon.iterator(); + for (int i = 0; i < romEntry.staticPokemon.size(); i++) { + StaticEncounter currentStatic = statics.next(); + StaticPokemon sp = romEntry.staticPokemon.get(i); + sp.setPokemon(this, currentStatic.pkmn); + sp.setLevel(rom, currentStatic.level, 0); + } + + if (romEntry.getValue("StaticPokemonOddEggOffset") > 0) { + int oeOffset = romEntry.getValue("StaticPokemonOddEggOffset"); + int oeSize = romEntry.getValue("StaticPokemonOddEggDataSize"); + for (int i = 0; i < Gen2Constants.oddEggPokemonCount; i++) { + int oddEggPokemonNumber = statics.next().pkmn.number; + rom[oeOffset + i * oeSize] = (byte) oddEggPokemonNumber; + setMovesForOddEggPokemon(oddEggPokemonNumber, oeOffset + i * oeSize); + } + } + + return true; + } + + // This method depends on movesets being randomized before static Pokemon. This is currently true, + // but may not *always* be true, so take care. + private void setMovesForOddEggPokemon(int oddEggPokemonNumber, int oddEggPokemonOffset) { + // Determine the level 5 moveset, minus Dizzy Punch + Map> movesets = this.getMovesLearnt(); + List moves = this.getMoves(); + List moveset = movesets.get(oddEggPokemonNumber); + Queue level5Moveset = new LinkedList<>(); + int currentMoveIndex = 0; + while (moveset.size() > currentMoveIndex && moveset.get(currentMoveIndex).level <= 5) { + if (level5Moveset.size() == 4) { + level5Moveset.remove(); + } + level5Moveset.add(moveset.get(currentMoveIndex).move); + currentMoveIndex++; + } + + // Now add Dizzy Punch and write the moveset and PP + if (level5Moveset.size() == 4) { + level5Moveset.remove(); + } + level5Moveset.add(Moves.dizzyPunch); + for (int i = 0; i < 4; i++) { + int move = 0; + int pp = 0; + if (level5Moveset.size() > 0) { + move = level5Moveset.remove(); + pp = moves.get(move).pp; // This assumes the ordering of moves matches the internal order + } + rom[oddEggPokemonOffset + 2 + i] = (byte) move; + rom[oddEggPokemonOffset + 23 + i] = (byte) pp; + } + } + + @Override + public boolean canChangeStaticPokemon() { + return (romEntry.getValue("StaticPokemonSupport") > 0); + } + + @Override + public boolean hasStaticAltFormes() { + return false; + } + + @Override + public List bannedForWildEncounters() { + // Ban Unown because they don't show up unless you complete a puzzle in the Ruins of Alph. + return new ArrayList<>(Collections.singletonList(pokes[Species.unown])); + } + + @Override + public List bannedForStaticPokemon() { + return Collections.singletonList(pokes[Species.unown]); // Unown banned + } + + @Override + public boolean hasMainGameLegendaries() { + return false; + } + + @Override + public List getMainGameLegendaries() { + return new ArrayList<>(); + } + + @Override + public List getSpecialMusicStatics() { + return new ArrayList<>(); + } + + @Override + public void applyCorrectStaticMusic(Map specialMusicStaticChanges) { + + } + + @Override + public boolean hasStaticMusicFix() { + return false; + } + + @Override + public List getTotemPokemon() { + return new ArrayList<>(); + } + + @Override + public void setTotemPokemon(List totemPokemon) { + + } + + private void writePaddedPokemonName(String name, int length, int offset) { + String paddedName = String.format("%-" + length + "s", name); + byte[] rawData = translateString(paddedName); + System.arraycopy(rawData, 0, rom, offset, length); + } + + @Override + public List getTMMoves() { + List tms = new ArrayList<>(); + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen2Constants.tmCount; i++) { + tms.add(rom[offset + (i - 1)] & 0xFF); + } + return tms; + } + + @Override + public List getHMMoves() { + List hms = new ArrayList<>(); + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen2Constants.hmCount; i++) { + hms.add(rom[offset + Gen2Constants.tmCount + (i - 1)] & 0xFF); + } + return hms; + } + + @Override + public void setTMMoves(List moveIndexes) { + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen2Constants.tmCount; i++) { + rom[offset + (i - 1)] = moveIndexes.get(i - 1).byteValue(); + } + + // TM Text + String[] moveNames = readMoveNames(); + for (TMTextEntry tte : romEntry.tmTexts) { + String moveName = moveNames[moveIndexes.get(tte.number - 1)]; + String text = tte.template.replace("%m", moveName); + writeVariableLengthString(text, tte.offset, true); + } + } + + @Override + public int getTMCount() { + return Gen2Constants.tmCount; + } + + @Override + public int getHMCount() { + return Gen2Constants.hmCount; + } + + @Override + public Map getTMHMCompatibility() { + Map compat = new TreeMap<>(); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") + (i - 1) * Gen2Constants.baseStatsEntrySize; + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen2Constants.tmCount + Gen2Constants.hmCount + 1]; + for (int j = 0; j < 8; j++) { + readByteIntoFlags(flags, j * 8 + 1, baseStatsOffset + Gen2Constants.bsTMHMCompatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map compatData) { + for (Map.Entry compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") + (pkmn.number - 1) + * Gen2Constants.baseStatsEntrySize; + for (int j = 0; j < 8; j++) { + if (!romEntry.isCrystal || j != 7) { + rom[baseStatsOffset + Gen2Constants.bsTMHMCompatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } else { + // Move tutor data + // bits 1,2,3 of byte 7 + int changedByte = getByteFromFlags(flags, j * 8 + 1) & 0xFF; + int currentByte = rom[baseStatsOffset + Gen2Constants.bsTMHMCompatOffset + j]; + changedByte |= ((currentByte >> 1) & 0x01) << 1; + changedByte |= ((currentByte >> 2) & 0x01) << 2; + changedByte |= ((currentByte >> 3) & 0x01) << 3; + rom[baseStatsOffset + 0x18 + j] = (byte) changedByte; + } + } + } + } + + @Override + public boolean hasMoveTutors() { + return romEntry.isCrystal; + } + + @Override + public List getMoveTutorMoves() { + if (romEntry.isCrystal) { + List mtMoves = new ArrayList<>(); + for (int offset : romEntry.arrayEntries.get("MoveTutorMoves")) { + mtMoves.add(rom[offset] & 0xFF); + } + return mtMoves; + } + return new ArrayList<>(); + } + + @Override + public void setMoveTutorMoves(List moves) { + if (!romEntry.isCrystal) { + return; + } + if (moves.size() != 3) { + return; + } + Iterator mvList = moves.iterator(); + for (int offset : romEntry.arrayEntries.get("MoveTutorMoves")) { + rom[offset] = mvList.next().byteValue(); + } + + // Construct a new menu + if (romEntry.getValue("MoveTutorMenuOffset") > 0 && romEntry.getValue("MoveTutorMenuNewSpace") > 0) { + String[] moveNames = readMoveNames(); + String[] names = new String[] { moveNames[moves.get(0)], moveNames[moves.get(1)], moveNames[moves.get(2)], + Gen2Constants.mtMenuCancelString }; + int menuOffset = romEntry.getValue("MoveTutorMenuNewSpace"); + rom[menuOffset++] = Gen2Constants.mtMenuInitByte; + rom[menuOffset++] = 0x4; + for (int i = 0; i < 4; i++) { + byte[] trans = translateString(names[i]); + System.arraycopy(trans, 0, rom, menuOffset, trans.length); + menuOffset += trans.length; + rom[menuOffset++] = GBConstants.stringTerminator; + } + int pointerOffset = romEntry.getValue("MoveTutorMenuOffset"); + writeWord(pointerOffset, makeGBPointer(romEntry.getValue("MoveTutorMenuNewSpace"))); + } + } + + @Override + public Map getMoveTutorCompatibility() { + if (!romEntry.isCrystal) { + return new TreeMap<>(); + } + Map compat = new TreeMap<>(); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") + (i - 1) * Gen2Constants.baseStatsEntrySize; + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[4]; + int mtByte = rom[baseStatsOffset + Gen2Constants.bsMTCompatOffset] & 0xFF; + for (int j = 1; j <= 3; j++) { + flags[j] = ((mtByte >> j) & 0x01) > 0; + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setMoveTutorCompatibility(Map compatData) { + if (!romEntry.isCrystal) { + return; + } + for (Map.Entry compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") + (pkmn.number - 1) + * Gen2Constants.baseStatsEntrySize; + int origMtByte = rom[baseStatsOffset + Gen2Constants.bsMTCompatOffset] & 0xFF; + int mtByte = origMtByte & 0x01; + for (int j = 1; j <= 3; j++) { + mtByte |= flags[j] ? (1 << j) : 0; + } + rom[baseStatsOffset + Gen2Constants.bsMTCompatOffset] = (byte) mtByte; + } + } + + @Override + public String getROMName() { + if (isVietCrystal) { + return Gen2Constants.vietCrystalROMName; + } + return "Pokemon " + romEntry.name; + } + + @Override + public String getROMCode() { + return romEntry.romCode; + } + + @Override + public String getSupportLevel() { + return "Complete"; + } + + private static int find(byte[] haystack, String hexString) { + if (hexString.length() % 2 != 0) { + return -3; // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + List found = RomFunctions.search(haystack, searchFor); + if (found.size() == 0) { + return -1; // not found + } else if (found.size() > 1) { + return -2; // not unique + } else { + return found.get(0); + } + } + + @Override + public boolean hasTimeBasedEncounters() { + return true; // All GSC do + } + + @Override + public boolean hasWildAltFormes() { + return false; + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int pointer = readWord(pointersOffset + (i - 1) * 2); + int realPointer = calculateOffset(bankOf(pointersOffset), pointer); + Pokemon pkmn = pokes[i]; + while (rom[realPointer] != 0) { + int method = rom[realPointer] & 0xFF; + int otherPoke = rom[realPointer + 2 + (method == 5 ? 1 : 0)] & 0xFF; + EvolutionType type = EvolutionType.fromIndex(2, method); + int extraInfo = 0; + if (type == EvolutionType.TRADE) { + int itemNeeded = rom[realPointer + 1] & 0xFF; + if (itemNeeded != 0xFF) { + type = EvolutionType.TRADE_ITEM; + extraInfo = itemNeeded; + } + } else if (type == EvolutionType.LEVEL_ATTACK_HIGHER) { + int tyrogueCond = rom[realPointer + 2] & 0xFF; + if (tyrogueCond == 2) { + type = EvolutionType.LEVEL_DEFENSE_HIGHER; + } else if (tyrogueCond == 3) { + type = EvolutionType.LEVEL_ATK_DEF_SAME; + } + extraInfo = rom[realPointer + 1] & 0xFF; + } else if (type == EvolutionType.HAPPINESS) { + int happCond = rom[realPointer + 1] & 0xFF; + if (happCond == 2) { + type = EvolutionType.HAPPINESS_DAY; + } else if (happCond == 3) { + type = EvolutionType.HAPPINESS_NIGHT; + } + } else { + extraInfo = rom[realPointer + 1] & 0xFF; + } + Evolution evo = new Evolution(pokes[i], pokes[otherPoke], true, type, extraInfo); + if (!pkmn.evolutionsFrom.contains(evo)) { + pkmn.evolutionsFrom.add(evo); + pokes[otherPoke].evolutionsTo.add(evo); + } + realPointer += (method == 5 ? 4 : 3); + } + // split evos don't carry stats + if (pkmn.evolutionsFrom.size() > 1) { + for (Evolution e : pkmn.evolutionsFrom) { + e.carryStats = false; + } + } + } + } + + @Override + public void removeImpossibleEvolutions(Settings settings) { + // no move evos, so no need to check for those + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + for (Evolution evol : pkmn.evolutionsFrom) { + if (evol.type == EvolutionType.TRADE || evol.type == EvolutionType.TRADE_ITEM) { + // change + if (evol.from.number == Species.slowpoke) { + // Slowpoke: Make water stone => Slowking + evol.type = EvolutionType.STONE; + evol.extraInfo = Gen2Items.waterStone; + addEvoUpdateStone(impossibleEvolutionUpdates, evol, itemNames[24]); + } else if (evol.from.number == Species.seadra) { + // Seadra: level 40 + evol.type = EvolutionType.LEVEL; + evol.extraInfo = 40; // level + addEvoUpdateLevel(impossibleEvolutionUpdates, evol); + } else if (evol.from.number == Species.poliwhirl || evol.type == EvolutionType.TRADE) { + // Poliwhirl or any of the original 4 trade evos + // Level 37 + evol.type = EvolutionType.LEVEL; + evol.extraInfo = 37; // level + addEvoUpdateLevel(impossibleEvolutionUpdates, evol); + } else { + // A new trade evo of a single stage Pokemon + // level 30 + evol.type = EvolutionType.LEVEL; + evol.extraInfo = 30; // level + addEvoUpdateLevel(impossibleEvolutionUpdates, evol); + } + } + } + } + } + + } + + @Override + public void makeEvolutionsEasier(Settings settings) { + // Reduce the amount of happiness required to evolve. + int offset = find(rom, Gen2Constants.friendshipValueForEvoLocator); + if (offset > 0) { + // The thing we're looking at is actually one byte before what we + // want to change; this makes it work in both G/S and Crystal. + offset++; + + // Amount of required happiness for all happiness evolutions. + if (rom[offset] == (byte)220) { + rom[offset] = (byte)160; + } + } + } + + @Override + public void removeTimeBasedEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + for (Evolution evol : pkmn.evolutionsFrom) { + // In Gen 2, only Eevee has a time-based evolution. + if (evol.type == EvolutionType.HAPPINESS_DAY) { + // Eevee: Make sun stone => Espeon + evol.type = EvolutionType.STONE; + evol.extraInfo = Gen2Items.sunStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evol, itemNames[169]); + } else if (evol.type == EvolutionType.HAPPINESS_NIGHT) { + // Eevee: Make moon stone => Umbreon + evol.type = EvolutionType.STONE; + evol.extraInfo = Gen2Items.moonStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evol, itemNames[8]); + } + } + } + } + + } + + @Override + public boolean hasShopRandomization() { + return false; + } + + @Override + public Map getShopItems() { + return null; // Not implemented + } + + @Override + public void setShopItems(Map shopItems) { + // Not implemented + } + + @Override + public void setShopPrices() { + // Not implemented + } + + @Override + public boolean canChangeTrainerText() { + return romEntry.getValue("CanChangeTrainerText") > 0; + } + + @Override + public List getTrainerNames() { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = romEntry.getValue("TrainerClassAmount"); + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount]; + for (int i = 0; i < traineramount; i++) { + int pointer = readWord(traineroffset + i * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), pointer); + } + + List allTrainers = new ArrayList<>(); + for (int i = 0; i < traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + for (int trnum = 0; trnum < limit; trnum++) { + String name = readVariableLengthString(offs, false); + allTrainers.add(name); + offs += lengthOfStringAt(offs, false) + 1; + int dataType = rom[offs] & 0xFF; + offs++; + while ((rom[offs] & 0xFF) != 0xFF) { + offs += 2; + if (dataType == 2 || dataType == 3) { + offs++; + } + if (dataType % 2 == 1) { + offs += 4; + } + } + offs++; + } + } + return allTrainers; + } + + @Override + public void setTrainerNames(List trainerNames) { + if (romEntry.getValue("CanChangeTrainerText") != 0) { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = romEntry.getValue("TrainerClassAmount"); + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount]; + for (int i = 0; i < traineramount; i++) { + int pointer = readWord(traineroffset + i * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), pointer); + } + + // Build up new trainer data using old as a guideline. + int[] offsetsInNew = new int[traineramount]; + int oInNewCurrent = 0; + Iterator allTrainers = trainerNames.iterator(); + ByteArrayOutputStream newData = new ByteArrayOutputStream(); + try { + for (int i = 0; i < traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + offsetsInNew[i] = oInNewCurrent; + for (int trnum = 0; trnum < limit; trnum++) { + String newName = allTrainers.next(); + + // The game uses 0xFF as a signifier for the end of the trainer data. + // It ALSO uses 0xFF to encode the character "9". If a trainer name has + // "9" in it, this causes strange side effects where certain trainers + // effectively get skipped when parsing trainer data. Silently strip out + // "9"s from trainer names to prevent this from happening. + newName = newName.replace("9", "").trim(); + + byte[] newNameStr = translateString(newName); + newData.write(newNameStr); + newData.write(GBConstants.stringTerminator); + oInNewCurrent += newNameStr.length + 1; + offs += lengthOfStringAt(offs, false) + 1; + int dataType = rom[offs] & 0xFF; + offs++; + newData.write(dataType); + oInNewCurrent++; + while ((rom[offs] & 0xFF) != 0xFF) { + newData.write(rom, offs, 2); + oInNewCurrent += 2; + offs += 2; + if (dataType == 2 || dataType == 3) { + newData.write(rom, offs, 1); + oInNewCurrent++; + offs++; + } + if (dataType % 2 == 1) { + newData.write(rom, offs, 4); + oInNewCurrent += 4; + offs += 4; + } + } + newData.write(0xFF); + oInNewCurrent++; + offs++; + } + } + + // Copy new data into ROM + byte[] newTrainerData = newData.toByteArray(); + int tdBase = pointers[0]; + System.arraycopy(newTrainerData, 0, rom, pointers[0], newTrainerData.length); + + // Finally, update the pointers + for (int i = 1; i < traineramount; i++) { + int newOffset = tdBase + offsetsInNew[i]; + writeWord(traineroffset + i * 2, makeGBPointer(newOffset)); + } + } catch (IOException ex) { + // This should never happen, but abort if it does. + } + } + + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.MAX_LENGTH_WITH_CLASS; + } + + @Override + public int maxTrainerNameLength() { + // line size minus one for space + return Gen2Constants.maxTrainerNameLength; + } + + @Override + public int maxSumOfTrainerNameLengths() { + return romEntry.getValue("MaxSumOfTrainerNameLengths"); + } + + @Override + public List getTCNameLengthsByTrainer() { + int traineramount = romEntry.getValue("TrainerClassAmount"); + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + List tcNames = this.getTrainerClassNames(); + List tcLengthsByT = new ArrayList<>(); + + for (int i = 0; i < traineramount; i++) { + int len = internalStringLength(tcNames.get(i)); + for (int k = 0; k < trainerclasslimits[i]; k++) { + tcLengthsByT.add(len); + } + } + + return tcLengthsByT; + } + + @Override + public List getTrainerClassNames() { + int amount = romEntry.getValue("TrainerClassAmount"); + int offset = romEntry.getValue("TrainerClassNamesOffset"); + List trainerClassNames = new ArrayList<>(); + for (int j = 0; j < amount; j++) { + String name = readVariableLengthString(offset, false); + offset += lengthOfStringAt(offset, false) + 1; + trainerClassNames.add(name); + } + return trainerClassNames; + } + + @Override + public List getEvolutionItems() { + return null; + } + + @Override + public void setTrainerClassNames(List trainerClassNames) { + if (romEntry.getValue("CanChangeTrainerText") != 0) { + int amount = romEntry.getValue("TrainerClassAmount"); + int offset = romEntry.getValue("TrainerClassNamesOffset"); + Iterator trainerClassNamesI = trainerClassNames.iterator(); + for (int j = 0; j < amount; j++) { + int len = lengthOfStringAt(offset, false) + 1; + String newName = trainerClassNamesI.next(); + writeFixedLengthString(newName, offset, len); + offset += len; + } + } + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return true; + } + + @Override + public List getDoublesTrainerClasses() { + int[] doublesClasses = romEntry.arrayEntries.get("DoublesTrainerClasses"); + List doubles = new ArrayList<>(); + for (int tClass : doublesClasses) { + doubles.add(tClass); + } + return doubles; + } + + @Override + public String getDefaultExtension() { + return "gbc"; + } + + @Override + public int abilitiesPerPokemon() { + return 0; + } + + @Override + public int highestAbilityIndex() { + return 0; + } + + @Override + public Map> getAbilityVariations() { + return new HashMap<>(); + } + + @Override + public boolean hasMegaEvolutions() { + return false; + } + + @Override + public int internalStringLength(String string) { + return translateString(string).length; + } + + @Override + public int miscTweaksAvailable() { + int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); + available |= MiscTweak.UPDATE_TYPE_EFFECTIVENESS.getValue(); + if (romEntry.codeTweaks.get("BWXPTweak") != null) { + available |= MiscTweak.BW_EXP_PATCH.getValue(); + } + if (romEntry.getValue("TextDelayFunctionOffset") != 0) { + available |= MiscTweak.FASTEST_TEXT.getValue(); + } + if (romEntry.arrayEntries.containsKey("CatchingTutorialOffsets")) { + available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); + } + available |= MiscTweak.BAN_LUCKY_EGG.getValue(); + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.BW_EXP_PATCH) { + applyBWEXPPatch(); + } else if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestTextPatch(); + } else if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { + applyCamelCaseNames(); + } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { + randomizeCatchingTutorial(); + } else if (tweak == MiscTweak.BAN_LUCKY_EGG) { + allowedItems.banSingles(Gen2Items.luckyEgg); + nonBadItems.banSingles(Gen2Items.luckyEgg); + } else if (tweak == MiscTweak.UPDATE_TYPE_EFFECTIVENESS) { + updateTypeEffectiveness(); + } + } + + @Override + public boolean isEffectivenessUpdated() { + return effectivenessUpdated; + } + + private void randomizeCatchingTutorial() { + if (romEntry.arrayEntries.containsKey("CatchingTutorialOffsets")) { + // Pick a pokemon + int pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; + while (pokemon == Species.unown) { + // Unown is banned + pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; + } + + int[] offsets = romEntry.arrayEntries.get("CatchingTutorialOffsets"); + for (int offset : offsets) { + rom[offset] = (byte) pokemon; + } + } + + } + + private void applyBWEXPPatch() { + String patchName = romEntry.codeTweaks.get("BWXPTweak"); + if (patchName == null) { + return; + } + + try { + FileFunctions.applyPatch(rom, patchName); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void applyFastestTextPatch() { + if (romEntry.getValue("TextDelayFunctionOffset") != 0) { + rom[romEntry.getValue("TextDelayFunctionOffset")] = GBConstants.gbZ80Ret; + } + } + + private void updateTypeEffectiveness() { + List typeEffectivenessTable = readTypeEffectivenessTable(); + log("--Updating Type Effectiveness--"); + for (TypeRelationship relationship : typeEffectivenessTable) { + // Change Ghost 0.5x against Steel to Ghost 1x to Steel + if (relationship.attacker == Type.GHOST && relationship.defender == Type.STEEL) { + relationship.effectiveness = Effectiveness.NEUTRAL; + log("Replaced: Ghost not very effective vs Steel => Ghost neutral vs Steel"); + } + + // Change Dark 0.5x against Steel to Dark 1x to Steel + else if (relationship.attacker == Type.DARK && relationship.defender == Type.STEEL) { + relationship.effectiveness = Effectiveness.NEUTRAL; + log("Replaced: Dark not very effective vs Steel => Dark neutral vs Steel"); + } + } + logBlankLine(); + writeTypeEffectivenessTable(typeEffectivenessTable); + effectivenessUpdated = true; + } + + private List readTypeEffectivenessTable() { + List typeEffectivenessTable = new ArrayList<>(); + int currentOffset = romEntry.getValue("TypeEffectivenessOffset"); + int attackingType = rom[currentOffset]; + // 0xFE marks the end of the table *not* affected by Foresight, while 0xFF marks + // the actual end of the table. Since we don't care about Ghost immunities at all, + // just stop once we reach the Foresight section. + while (attackingType != (byte) 0xFE) { + int defendingType = rom[currentOffset + 1]; + int effectivenessInternal = rom[currentOffset + 2]; + Type attacking = Gen2Constants.typeTable[attackingType]; + Type defending = Gen2Constants.typeTable[defendingType]; + Effectiveness effectiveness = null; + switch (effectivenessInternal) { + case 20: + effectiveness = Effectiveness.DOUBLE; + break; + case 10: + effectiveness = Effectiveness.NEUTRAL; + break; + case 5: + effectiveness = Effectiveness.HALF; + break; + case 0: + effectiveness = Effectiveness.ZERO; + break; + } + if (effectiveness != null) { + TypeRelationship relationship = new TypeRelationship(attacking, defending, effectiveness); + typeEffectivenessTable.add(relationship); + } + currentOffset += 3; + attackingType = rom[currentOffset]; + } + return typeEffectivenessTable; + } + + private void writeTypeEffectivenessTable(List typeEffectivenessTable) { + int currentOffset = romEntry.getValue("TypeEffectivenessOffset"); + for (TypeRelationship relationship : typeEffectivenessTable) { + rom[currentOffset] = Gen2Constants.typeToByte(relationship.attacker); + rom[currentOffset + 1] = Gen2Constants.typeToByte(relationship.defender); + byte effectivenessInternal = 0; + switch (relationship.effectiveness) { + case DOUBLE: + effectivenessInternal = 20; + break; + case NEUTRAL: + effectivenessInternal = 10; + break; + case HALF: + effectivenessInternal = 5; + break; + case ZERO: + effectivenessInternal = 0; + break; + } + rom[currentOffset + 2] = effectivenessInternal; + currentOffset += 3; + } + } + + @Override + public void enableGuaranteedPokemonCatching() { + String prefix = romEntry.getString("GuaranteedCatchPrefix"); + int offset = find(rom, prefix); + if (offset > 0) { + offset += prefix.length() / 2; // because it was a prefix + + // The game guarantees that the catching tutorial always succeeds in catching by running + // the following code: + // ld a, [wBattleType] + // cp BATTLETYPE_TUTORIAL + // jp z, .catch_without_fail + // By making the jump here unconditional, we can ensure that catching always succeeds no + // matter the battle type. We check that the original condition is present just for safety. + if (rom[offset] == (byte)0xCA) { + rom[offset] = (byte)0xC3; + } + } + } + + @Override + public void randomizeIntroPokemon() { + // Intro sprite + + // Pick a pokemon + int pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; + while (pokemon == Species.unown) { + // Unown is banned + pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; + } + + rom[romEntry.getValue("IntroSpriteOffset")] = (byte) pokemon; + rom[romEntry.getValue("IntroCryOffset")] = (byte) pokemon; + + } + + @Override + public ItemList getAllowedItems() { + return allowedItems; + } + + @Override + public ItemList getNonBadItems() { + return nonBadItems; + } + + @Override + public List getUniqueNoSellItems() { + return new ArrayList<>(); + } + + @Override + public List getRegularShopItems() { + return null; // Not implemented + } + + @Override + public List getOPShopItems() { + return null; // Not implemented + } + + private void loadItemNames() { + itemNames = new String[256]; + itemNames[0] = "glitch"; + // trying to emulate pretty much what the game does here + // normal items + int origOffset = romEntry.getValue("ItemNamesOffset"); + int itemNameOffset = origOffset; + for (int index = 1; index <= 0x100; index++) { + if (itemNameOffset / GBConstants.bankSize > origOffset / GBConstants.bankSize) { + // the game would continue making its merry way into VRAM here, + // but we don't have VRAM to simulate. + // just give up. + break; + } + int startOfText = itemNameOffset; + while ((rom[itemNameOffset] & 0xFF) != GBConstants.stringTerminator) { + itemNameOffset++; + } + itemNameOffset++; + itemNames[index % 256] = readFixedLengthString(startOfText, 20); + } + } + + @Override + public String[] getItemNames() { + return itemNames; + } + + private void patchFleeing() { + havePatchedFleeing = true; + int offset = romEntry.getValue("FleeingDataOffset"); + rom[offset] = (byte) 0xFF; + rom[offset + Gen2Constants.fleeingSetTwoOffset] = (byte) 0xFF; + rom[offset + Gen2Constants.fleeingSetThreeOffset] = (byte) 0xFF; + } + + private void loadLandmarkNames() { + + int lmOffset = romEntry.getValue("LandmarkTableOffset"); + int lmBank = bankOf(lmOffset); + int lmCount = romEntry.getValue("LandmarkCount"); + + landmarkNames = new String[lmCount]; + + for (int i = 0; i < lmCount; i++) { + int lmNameOffset = calculateOffset(lmBank, readWord(lmOffset + i * 4 + 2)); + landmarkNames[i] = readVariableLengthString(lmNameOffset, false).replace("\\x1F", " "); + } + + } + + private void preprocessMaps() { + itemOffs = new ArrayList<>(); + + int mhOffset = romEntry.getValue("MapHeaders"); + int mapGroupCount = Gen2Constants.mapGroupCount; + int mapsInLastGroup = Gen2Constants.mapsInLastGroup; + int mhBank = bankOf(mhOffset); + mapNames = new String[mapGroupCount + 1][100]; + + int[] groupOffsets = new int[mapGroupCount]; + for (int i = 0; i < mapGroupCount; i++) { + groupOffsets[i] = calculateOffset(mhBank, readWord(mhOffset + i * 2)); + } + + // Read maps + for (int mg = 0; mg < mapGroupCount; mg++) { + int offset = groupOffsets[mg]; + int maxOffset = (mg == mapGroupCount - 1) ? (mhBank + 1) * GBConstants.bankSize : groupOffsets[mg + 1]; + int map = 0; + int maxMap = (mg == mapGroupCount - 1) ? mapsInLastGroup : Integer.MAX_VALUE; + while (offset < maxOffset && map < maxMap) { + processMapAt(offset, mg + 1, map + 1); + offset += 9; + map++; + } + } + } + + private void processMapAt(int offset, int mapBank, int mapNumber) { + + // second map header + int smhBank = rom[offset] & 0xFF; + int smhPointer = readWord(offset + 3); + int smhOffset = calculateOffset(smhBank, smhPointer); + + // map name + int mapLandmark = rom[offset + 5] & 0xFF; + mapNames[mapBank][mapNumber] = landmarkNames[mapLandmark]; + + // event header + // event header is in same bank as script header + int ehBank = rom[smhOffset + 6] & 0xFF; + int ehPointer = readWord(smhOffset + 9); + int ehOffset = calculateOffset(ehBank, ehPointer); + + // skip over filler + ehOffset += 2; + + // warps + int warpCount = rom[ehOffset++] & 0xFF; + // warps are skipped + ehOffset += warpCount * 5; + + // xy triggers + int triggerCount = rom[ehOffset++] & 0xFF; + // xy triggers are skipped + ehOffset += triggerCount * 8; + + // signposts + int signpostCount = rom[ehOffset++] & 0xFF; + // we do care about these + for (int sp = 0; sp < signpostCount; sp++) { + // type=7 are hidden items + int spType = rom[ehOffset + sp * 5 + 2] & 0xFF; + if (spType == 7) { + // get event pointer + int spPointer = readWord(ehOffset + sp * 5 + 3); + int spOffset = calculateOffset(ehBank, spPointer); + // item is at spOffset+2 (first two bytes are the flag id) + itemOffs.add(spOffset + 2); + } + } + // now skip past them + ehOffset += signpostCount * 5; + + // visible objects/people + int peopleCount = rom[ehOffset++] & 0xFF; + // we also care about these + for (int p = 0; p < peopleCount; p++) { + // color_function & 1 = 1 if itemball + int pColorFunction = rom[ehOffset + p * 13 + 7]; + if ((pColorFunction & 1) == 1) { + // get event pointer + int pPointer = readWord(ehOffset + p * 13 + 9); + int pOffset = calculateOffset(ehBank, pPointer); + // item is at the pOffset for non-hidden items + itemOffs.add(pOffset); + } + } + + } + + @Override + public List getRequiredFieldTMs() { + return Gen2Constants.requiredFieldTMs; + } + + @Override + public List getCurrentFieldTMs() { + List fieldTMs = new ArrayList<>(); + + for (int offset : itemOffs) { + int itemHere = rom[offset] & 0xFF; + if (Gen2Constants.allowedItems.isTM(itemHere)) { + int thisTM; + if (itemHere >= Gen2Constants.tmBlockOneIndex + && itemHere < Gen2Constants.tmBlockOneIndex + Gen2Constants.tmBlockOneSize) { + thisTM = itemHere - Gen2Constants.tmBlockOneIndex + 1; + } else if (itemHere >= Gen2Constants.tmBlockTwoIndex + && itemHere < Gen2Constants.tmBlockTwoIndex + Gen2Constants.tmBlockTwoSize) { + thisTM = itemHere - Gen2Constants.tmBlockTwoIndex + 1 + Gen2Constants.tmBlockOneSize; // TM + // block + // 2 + // offset + } else { + thisTM = itemHere - Gen2Constants.tmBlockThreeIndex + 1 + Gen2Constants.tmBlockOneSize + + Gen2Constants.tmBlockTwoSize; // TM block 3 offset + } + // hack for the bug catching contest repeat TM28 + if (!fieldTMs.contains(thisTM)) { + fieldTMs.add(thisTM); + } + } + } + return fieldTMs; + } + + @Override + public void setFieldTMs(List fieldTMs) { + Iterator iterTMs = fieldTMs.iterator(); + int[] givenTMs = new int[256]; + + for (int offset : itemOffs) { + int itemHere = rom[offset] & 0xFF; + if (Gen2Constants.allowedItems.isTM(itemHere)) { + // Cache replaced TMs to duplicate bug catching contest TM + if (givenTMs[itemHere] != 0) { + rom[offset] = (byte) givenTMs[itemHere]; + } else { + // Replace this with a TM from the list + int tm = iterTMs.next(); + if (tm >= 1 && tm <= Gen2Constants.tmBlockOneSize) { + tm += Gen2Constants.tmBlockOneIndex - 1; + } else if (tm >= Gen2Constants.tmBlockOneSize + 1 + && tm <= Gen2Constants.tmBlockOneSize + Gen2Constants.tmBlockTwoSize) { + tm += Gen2Constants.tmBlockTwoIndex - 1 - Gen2Constants.tmBlockOneSize; + } else { + tm += Gen2Constants.tmBlockThreeIndex - 1 - Gen2Constants.tmBlockOneSize + - Gen2Constants.tmBlockTwoSize; + } + givenTMs[itemHere] = tm; + rom[offset] = (byte) tm; + } + } + } + } + + @Override + public List getRegularFieldItems() { + List fieldItems = new ArrayList<>(); + + for (int offset : itemOffs) { + int itemHere = rom[offset] & 0xFF; + if (Gen2Constants.allowedItems.isAllowed(itemHere) && !(Gen2Constants.allowedItems.isTM(itemHere))) { + fieldItems.add(itemHere); + } + } + return fieldItems; + } + + @Override + public void setRegularFieldItems(List items) { + Iterator iterItems = items.iterator(); + + for (int offset : itemOffs) { + int itemHere = rom[offset] & 0xFF; + if (Gen2Constants.allowedItems.isAllowed(itemHere) && !(Gen2Constants.allowedItems.isTM(itemHere))) { + // Replace it + rom[offset] = (byte) (iterItems.next().intValue()); + } + } + + } + + @Override + public List getIngameTrades() { + List trades = new ArrayList<>(); + + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int nicknameLength = romEntry.getValue("TradeNameLength"); + int otLength = romEntry.getValue("TradeOTLength"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = nicknameLength + otLength + 9; + if (entryLength % 2 != 0) { + entryLength++; + } + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = new IngameTrade(); + int entryOffset = tableOffset + entry * entryLength; + trade.requestedPokemon = pokes[rom[entryOffset + 1] & 0xFF]; + trade.givenPokemon = pokes[rom[entryOffset + 2] & 0xFF]; + trade.nickname = readString(entryOffset + 3, nicknameLength, false); + int atkdef = rom[entryOffset + 3 + nicknameLength] & 0xFF; + int spdspc = rom[entryOffset + 4 + nicknameLength] & 0xFF; + trade.ivs = new int[] { (atkdef >> 4) & 0xF, atkdef & 0xF, (spdspc >> 4) & 0xF, spdspc & 0xF }; + trade.item = rom[entryOffset + 5 + nicknameLength] & 0xFF; + trade.otId = readWord(entryOffset + 6 + nicknameLength); + trade.otName = readString(entryOffset + 8 + nicknameLength, otLength, false); + trades.add(trade); + } + + return trades; + + } + + @Override + public void setIngameTrades(List trades) { + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int nicknameLength = romEntry.getValue("TradeNameLength"); + int otLength = romEntry.getValue("TradeOTLength"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = nicknameLength + otLength + 9; + if (entryLength % 2 != 0) { + entryLength++; + } + int tradeOffset = 0; + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = trades.get(tradeOffset++); + int entryOffset = tableOffset + entry * entryLength; + rom[entryOffset + 1] = (byte) trade.requestedPokemon.number; + rom[entryOffset + 2] = (byte) trade.givenPokemon.number; + if (romEntry.getValue("CanChangeTrainerText") > 0) { + writeFixedLengthString(trade.nickname, entryOffset + 3, nicknameLength); + } + rom[entryOffset + 3 + nicknameLength] = (byte) (trade.ivs[0] << 4 | trade.ivs[1]); + rom[entryOffset + 4 + nicknameLength] = (byte) (trade.ivs[2] << 4 | trade.ivs[3]); + rom[entryOffset + 5 + nicknameLength] = (byte) trade.item; + writeWord(entryOffset + 6 + nicknameLength, trade.otId); + if (romEntry.getValue("CanChangeTrainerText") > 0) { + writeFixedLengthString(trade.otName, entryOffset + 8 + nicknameLength, otLength); + } + // remove gender req + rom[entryOffset + 8 + nicknameLength + otLength] = 0; + + } + } + + @Override + public boolean hasDVs() { + return true; + } + + @Override + public int generationOfPokemon() { + return 2; + } + + @Override + public void removeEvosForPokemonPool() { + List pokemonIncluded = this.mainPokemonList; + Set keepEvos = new HashSet<>(); + for (Pokemon pk : pokes) { + if (pk != null) { + keepEvos.clear(); + for (Evolution evol : pk.evolutionsFrom) { + if (pokemonIncluded.contains(evol.from) && pokemonIncluded.contains(evol.to)) { + keepEvos.add(evol); + } else { + evol.to.evolutionsTo.remove(evol); + } + } + pk.evolutionsFrom.retainAll(keepEvos); + } + } + } + + private void writeEvosAndMovesLearnt(boolean writeEvos, Map> movesets) { + // this assumes that the evo/attack pointers & data + // are at the end of the bank + // which, in every clean G/S/C rom supported, they are + // specify null to either argument to copy old values + int movesEvosStart = romEntry.getValue("PokemonMovesetsTableOffset"); + int movesEvosBank = bankOf(movesEvosStart); + byte[] pointerTable = new byte[Gen2Constants.pokemonCount * 2]; + int startOfNextBank; + if (isVietCrystal) { + startOfNextBank = 0x43E00; // fix for pokedex crash + } + else { + startOfNextBank = ((movesEvosStart / GBConstants.bankSize) + 1) * GBConstants.bankSize; + } + int dataBlockSize = startOfNextBank - (movesEvosStart + pointerTable.length); + int dataBlockOffset = movesEvosStart + pointerTable.length; + byte[] dataBlock = new byte[dataBlockSize]; + int offsetInData = 0; + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + // determine pointer + int oldDataOffset = calculateOffset(movesEvosBank, readWord(movesEvosStart + (i - 1) * 2)); + int offsetStart = dataBlockOffset + offsetInData; + boolean evoWritten = false; + if (!writeEvos) { + // copy old + int evoOffset = oldDataOffset; + while (rom[evoOffset] != 0x00) { + int method = rom[evoOffset] & 0xFF; + int limiter = (method == 5) ? 4 : 3; + for (int b = 0; b < limiter; b++) { + dataBlock[offsetInData++] = rom[evoOffset++]; + } + evoWritten = true; + } + } else { + for (Evolution evo : pokes[i].evolutionsFrom) { + // write evos + dataBlock[offsetInData++] = (byte) evo.type.toIndex(2); + if (evo.type == EvolutionType.LEVEL || evo.type == EvolutionType.STONE + || evo.type == EvolutionType.TRADE_ITEM) { + // simple types + dataBlock[offsetInData++] = (byte) evo.extraInfo; + } else if (evo.type == EvolutionType.TRADE) { + // non-item trade + dataBlock[offsetInData++] = (byte) 0xFF; + } else if (evo.type == EvolutionType.HAPPINESS) { + // cond 01 + dataBlock[offsetInData++] = 0x01; + } else if (evo.type == EvolutionType.HAPPINESS_DAY) { + // cond 02 + dataBlock[offsetInData++] = 0x02; + } else if (evo.type == EvolutionType.HAPPINESS_NIGHT) { + // cond 03 + dataBlock[offsetInData++] = 0x03; + } else if (evo.type == EvolutionType.LEVEL_ATTACK_HIGHER) { + dataBlock[offsetInData++] = (byte) evo.extraInfo; + dataBlock[offsetInData++] = 0x01; + } else if (evo.type == EvolutionType.LEVEL_DEFENSE_HIGHER) { + dataBlock[offsetInData++] = (byte) evo.extraInfo; + dataBlock[offsetInData++] = 0x02; + } else if (evo.type == EvolutionType.LEVEL_ATK_DEF_SAME) { + dataBlock[offsetInData++] = (byte) evo.extraInfo; + dataBlock[offsetInData++] = 0x03; + } + dataBlock[offsetInData++] = (byte) evo.to.number; + evoWritten = true; + } + } + // can we reuse a terminator? + if (!evoWritten && offsetStart != dataBlockOffset) { + // reuse last pokemon's move terminator for our evos + offsetStart -= 1; + } else { + // write a terminator + dataBlock[offsetInData++] = 0x00; + } + // write table entry now that we're sure of its location + int pointerNow = makeGBPointer(offsetStart); + writeWord(pointerTable, (i - 1) * 2, pointerNow); + // moveset + if (movesets == null) { + // copy old + int movesOffset = oldDataOffset; + // move past evos + while (rom[movesOffset] != 0x00) { + int method = rom[movesOffset] & 0xFF; + movesOffset += (method == 5) ? 4 : 3; + } + movesOffset++; + // copy moves + while (rom[movesOffset] != 0x00) { + dataBlock[offsetInData++] = rom[movesOffset++]; + dataBlock[offsetInData++] = rom[movesOffset++]; + } + } else { + List moves = movesets.get(pokes[i].number); + for (MoveLearnt ml : moves) { + dataBlock[offsetInData++] = (byte) ml.level; + dataBlock[offsetInData++] = (byte) ml.move; + } + } + // terminator + dataBlock[offsetInData++] = 0x00; + } + // write new data + System.arraycopy(pointerTable, 0, rom, movesEvosStart, pointerTable.length); + System.arraycopy(dataBlock, 0, rom, dataBlockOffset, dataBlock.length); + } + + @Override + public boolean supportsFourStartingMoves() { + return (romEntry.getValue("SupportsFourStartingMoves") > 0); + } + + @Override + public List getGameBreakingMoves() { + // add OHKO moves for gen2 because x acc is still broken + return Gen2Constants.brokenMoves; + } + + @Override + public List getIllegalMoves() { + // 3 moves that crash the game when used by self or opponent + if (isVietCrystal) { + return Gen2Constants.illegalVietCrystalMoves; + } + return new ArrayList<>(); + } + + @Override + public List getFieldMoves() { + // cut, fly, surf, strength, flash, + // dig, teleport, whirlpool, waterfall, + // rock smash, headbutt, sweet scent + // not softboiled or milk drink + return Gen2Constants.fieldMoves; + } + + @Override + public List getEarlyRequiredHMMoves() { + // just cut + return Gen2Constants.earlyRequiredHMMoves; + } + + @Override + public boolean isRomValid() { + return romEntry.expectedCRC32 == actualCRC32; + } + + @Override + public BufferedImage getMascotImage() { + Pokemon mascot = randomPokemon(); + while (mascot.number == Species.unown) { + // Unown is banned as handling it would add a ton of extra effort. + mascot = randomPokemon(); + } + + // Each Pokemon has a front and back pic with a bank and a pointer + // (3*2=6) + // There is no zero-entry. + int picPointer = romEntry.getValue("PicPointers") + (mascot.number - 1) * 6; + int picWidth = mascot.picDimensions & 0x0F; + int picHeight = (mascot.picDimensions >> 4) & 0x0F; + + int picBank = (rom[picPointer] & 0xFF); + if (romEntry.isCrystal) { + // Crystal pic banks are offset by x36 for whatever reason. + picBank += 0x36; + } else { + // Hey, G/S are dumb too! Arbitrarily redirected bank numbers. + if (picBank == 0x13) { + picBank = 0x1F; + } else if (picBank == 0x14) { + picBank = 0x20; + } else if (picBank == 0x1F) { + picBank = 0x2E; + } + } + int picOffset = calculateOffset(picBank, readWord(picPointer + 1)); + + Gen2Decmp mscSprite = new Gen2Decmp(rom, picOffset, picWidth, picHeight); + int w = picWidth * 8; + int h = picHeight * 8; + + // Palette? + // Two colors per Pokemon + two more for shiny, unlike pics there is a + // zero-entry. + // Black and white are left alone at the start and end of the palette. + int[] palette = new int[] { 0xFFFFFFFF, 0xFFAAAAAA, 0xFF666666, 0xFF000000 }; + int paletteOffset = romEntry.getValue("PokemonPalettes") + mascot.number * 8; + if (random.nextInt(10) == 0) { + // Use shiny instead + paletteOffset += 4; + } + for (int i = 0; i < 2; i++) { + palette[i + 1] = GFXFunctions.conv16BitColorToARGB(readWord(paletteOffset + i * 2)); + } + + byte[] data = mscSprite.getFlattenedData(); + + BufferedImage bim = GFXFunctions.drawTiledImage(data, palette, w, h, 8); + GFXFunctions.pseudoTransparency(bim, palette[0]); + + return bim; + } + + @Override + public void writeCheckValueToROM(int value) { + if (romEntry.getValue("CheckValueOffset") > 0) { + int cvOffset = romEntry.getValue("CheckValueOffset"); + for (int i = 0; i < 4; i++) { + rom[cvOffset + i] = (byte) ((value >> (3 - i) * 8) & 0xFF); + } + } + } +} diff --git a/src/com/pkrandom/romhandlers/Gen3RomHandler.java b/src/com/pkrandom/romhandlers/Gen3RomHandler.java new file mode 100755 index 0000000..838315d --- /dev/null +++ b/src/com/pkrandom/romhandlers/Gen3RomHandler.java @@ -0,0 +1,4473 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- Gen3RomHandler.java - randomizer handler for R/S/E/FR/LG. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import com.pkrandom.*; +import com.pkrandom.constants.*; +import com.pkrandom.exceptions.RandomizationException; +import com.pkrandom.exceptions.RandomizerIOException; +import com.pkrandom.pokemon.*; +import compressors.DSDecmp; + +public class Gen3RomHandler extends AbstractGBRomHandler { + + public static class Factory extends RomHandler.Factory { + + @Override + public Gen3RomHandler create(Random random, PrintStream logStream) { + return new Gen3RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + long fileLength = new File(filename).length(); + if (fileLength > 32 * 1024 * 1024) { + return false; + } + byte[] loaded = loadFilePartial(filename, 0x100000); + // nope + return loaded.length != 0 && detectRomInner(loaded, (int) fileLength); + } + } + + public Gen3RomHandler(Random random) { + super(random, null); + } + + public Gen3RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + private static class RomEntry { + private String name; + private String romCode; + private String tableFile; + private int version; + private int romType; + private boolean copyStaticPokemon; + private Map entries = new HashMap<>(); + private Map arrayEntries = new HashMap<>(); + private Map strings = new HashMap<>(); + private List staticPokemon = new ArrayList<>(); + private List roamingPokemon = new ArrayList<>(); + private List tmmtTexts = new ArrayList<>(); + private Map codeTweaks = new HashMap(); + private long expectedCRC32 = -1; + + public RomEntry() { + + } + + public RomEntry(RomEntry toCopy) { + this.name = toCopy.name; + this.romCode = toCopy.romCode; + this.tableFile = toCopy.tableFile; + this.version = toCopy.version; + this.romType = toCopy.romType; + this.copyStaticPokemon = toCopy.copyStaticPokemon; + this.entries.putAll(toCopy.entries); + this.arrayEntries.putAll(toCopy.arrayEntries); + this.strings.putAll(toCopy.strings); + this.staticPokemon.addAll(toCopy.staticPokemon); + this.roamingPokemon.addAll(toCopy.roamingPokemon); + this.tmmtTexts.addAll(toCopy.tmmtTexts); + this.codeTweaks.putAll(toCopy.codeTweaks); + this.expectedCRC32 = toCopy.expectedCRC32; + } + + private int getValue(String key) { + if (!entries.containsKey(key)) { + entries.put(key, 0); + } + return entries.get(key); + } + + private String getString(String key) { + if (!strings.containsKey(key)) { + strings.put(key, ""); + } + return strings.get(key); + } + } + + private static class TMOrMTTextEntry { + private int number; + private int mapBank, mapNumber; + private int personNum; + private int offsetInScript; + private int actualOffset; + private String template; + private boolean isMoveTutor; + } + + private static List roms; + + static { + loadROMInfo(); + } + + private static void loadROMInfo() { + roms = new ArrayList<>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen3_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + // Static Pokemon? + if (r[0].equals("StaticPokemon{}")) { + current.staticPokemon.add(parseStaticPokemon(r[1])); + } else if (r[0].equals("RoamingPokemon{}")) { + current.roamingPokemon.add(parseStaticPokemon(r[1])); + } else if (r[0].equals("TMText[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] parts = r[1].substring(1, r[1].length() - 1).split(",", 6); + TMOrMTTextEntry tte = new TMOrMTTextEntry(); + tte.number = parseRIInt(parts[0]); + tte.mapBank = parseRIInt(parts[1]); + tte.mapNumber = parseRIInt(parts[2]); + tte.personNum = parseRIInt(parts[3]); + tte.offsetInScript = parseRIInt(parts[4]); + tte.template = parts[5]; + tte.isMoveTutor = false; + current.tmmtTexts.add(tte); + } + } else if (r[0].equals("MoveTutorText[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] parts = r[1].substring(1, r[1].length() - 1).split(",", 6); + TMOrMTTextEntry tte = new TMOrMTTextEntry(); + tte.number = parseRIInt(parts[0]); + tte.mapBank = parseRIInt(parts[1]); + tte.mapNumber = parseRIInt(parts[2]); + tte.personNum = parseRIInt(parts[3]); + tte.offsetInScript = parseRIInt(parts[4]); + tte.template = parts[5]; + tte.isMoveTutor = true; + current.tmmtTexts.add(tte); + } + } else if (r[0].equals("Game")) { + current.romCode = r[1]; + } else if (r[0].equals("Version")) { + current.version = parseRIInt(r[1]); + } else if (r[0].equals("Type")) { + if (r[1].equalsIgnoreCase("Ruby")) { + current.romType = Gen3Constants.RomType_Ruby; + } else if (r[1].equalsIgnoreCase("Sapp")) { + current.romType = Gen3Constants.RomType_Sapp; + } else if (r[1].equalsIgnoreCase("Em")) { + current.romType = Gen3Constants.RomType_Em; + } else if (r[1].equalsIgnoreCase("FRLG")) { + current.romType = Gen3Constants.RomType_FRLG; + } else { + System.err.println("unrecognised rom type: " + r[1]); + } + } else if (r[0].equals("TableFile")) { + current.tableFile = r[1]; + } else if (r[0].equals("CopyStaticPokemon")) { + int csp = parseRIInt(r[1]); + current.copyStaticPokemon = (csp > 0); + } else if (r[0].equals("CRC32")) { + current.expectedCRC32 = parseRILong("0x" + r[1]); + } else if (r[0].endsWith("Tweak")) { + current.codeTweaks.put(r[0], r[1]); + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.name)) { + // copy from here + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.entries.putAll(otherEntry.entries); + current.strings.putAll(otherEntry.strings); + boolean cTT = (current.getValue("CopyTMText") == 1); + if (current.copyStaticPokemon) { + current.staticPokemon.addAll(otherEntry.staticPokemon); + current.roamingPokemon.addAll(otherEntry.roamingPokemon); + current.entries.put("StaticPokemonSupport", 1); + } else { + current.entries.put("StaticPokemonSupport", 0); + } + if (cTT) { + current.tmmtTexts.addAll(otherEntry.tmmtTexts); + } + current.tableFile = otherEntry.tableFile; + } + } + } else if (r[0].endsWith("Locator") || r[0].endsWith("Prefix")) { + current.strings.put(r[0], r[1]); + } else { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.arrayEntries.put(r[0], offs); + } + } else { + int offs = parseRIInt(r[1]); + current.entries.put(r[0], offs); + } + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + System.err.println("File not found!"); + } + + } + + private static int parseRIInt(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Integer.parseInt(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + private static long parseRILong(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Long.parseLong(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + private static StaticPokemon parseStaticPokemon(String staticPokemonString) { + StaticPokemon sp = new StaticPokemon(); + String pattern = "[A-z]+=\\[(0x[0-9a-fA-F]+,?\\s?)+]"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(staticPokemonString); + while (m.find()) { + String[] segments = m.group().split("="); + String[] romOffsets = segments[1].substring(1, segments[1].length() - 1).split(","); + int[] offsets = new int [romOffsets.length]; + for (int i = 0; i < offsets.length; i++) { + offsets[i] = parseRIInt(romOffsets[i]); + } + switch (segments[0]) { + case "Species": + sp.speciesOffsets = offsets; + break; + case "Level": + sp.levelOffsets = offsets; + break; + } + } + return sp; + } + + private void loadTextTable(String filename) { + try { + Scanner sc = new Scanner(FileFunctions.openConfig(filename + ".tbl"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine(); + if (!q.trim().isEmpty()) { + String[] r = q.split("=", 2); + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + tb[Integer.parseInt(r[0], 16)] = r[1]; + d.put(r[1], (byte) Integer.parseInt(r[0], 16)); + } + } + sc.close(); + } catch (FileNotFoundException e) { + System.err.println("File not found!"); + } + + } + + // This ROM's data + private Pokemon[] pokes, pokesInternal; + private List pokemonList; + private int numRealPokemon; + private Move[] moves; + private boolean jamboMovesetHack; + private RomEntry romEntry; + private boolean havePatchedObedience; + private String[] tb; + public Map d; + private String[] abilityNames; + private String[] itemNames; + private boolean mapLoadingDone; + private List itemOffs; + private String[][] mapNames; + private boolean isRomHack; + private int[] internalToPokedex, pokedexToInternal; + private int pokedexCount; + private String[] pokeNames; + private ItemList allowedItems, nonBadItems; + private int pickupItemsTableOffset; + private long actualCRC32; + private boolean effectivenessUpdated; + + @Override + public boolean detectRom(byte[] rom) { + return detectRomInner(rom, rom.length); + } + + private static boolean detectRomInner(byte[] rom, int romSize) { + if (romSize != Gen3Constants.size8M && romSize != Gen3Constants.size16M && romSize != Gen3Constants.size32M) { + return false; // size check + } + // Special case for Emerald unofficial translation + if (romName(rom, Gen3Constants.unofficialEmeraldROMName)) { + // give it a rom code so it can be detected + rom[Gen3Constants.romCodeOffset] = 'B'; + rom[Gen3Constants.romCodeOffset + 1] = 'P'; + rom[Gen3Constants.romCodeOffset + 2] = 'E'; + rom[Gen3Constants.romCodeOffset + 3] = 'T'; + rom[Gen3Constants.headerChecksumOffset] = 0x66; + } + // Wild Pokemon header + if (find(rom, Gen3Constants.wildPokemonPointerPrefix) == -1) { + return false; + } + // Map Banks header + if (find(rom, Gen3Constants.mapBanksPointerPrefix) == -1) { + return false; + } + // Pokedex Order header + if (findMultiple(rom, Gen3Constants.pokedexOrderPointerPrefix).size() != 3) { + return false; + } + for (RomEntry re : roms) { + if (romCode(rom, re.romCode) && (rom[Gen3Constants.romVersionOffset] & 0xFF) == re.version) { + return true; // match + } + } + return false; // GBA rom we don't support yet + } + + @Override + public void loadedRom() { + for (RomEntry re : roms) { + if (romCode(rom, re.romCode) && (rom[0xBC] & 0xFF) == re.version) { + romEntry = new RomEntry(re); // clone so we can modify + break; + } + } + + tb = new String[256]; + d = new HashMap<>(); + isRomHack = false; + jamboMovesetHack = false; + + // Pokemon count stuff, needs to be available first + List pokedexOrderPrefixes = findMultiple(rom, Gen3Constants.pokedexOrderPointerPrefix); + romEntry.entries.put("PokedexOrder", readPointer(pokedexOrderPrefixes.get(1) + 16)); + + // Pokemon names offset + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + int baseNomOffset = find(rom, Gen3Constants.rsPokemonNamesPointerSuffix); + romEntry.entries.put("PokemonNames", readPointer(baseNomOffset - 4)); + romEntry.entries.put( + "FrontSprites", + readPointer(findPointerPrefixAndSuffix(Gen3Constants.rsFrontSpritesPointerPrefix, + Gen3Constants.rsFrontSpritesPointerSuffix))); + romEntry.entries.put( + "PokemonPalettes", + readPointer(findPointerPrefixAndSuffix(Gen3Constants.rsPokemonPalettesPointerPrefix, + Gen3Constants.rsPokemonPalettesPointerSuffix))); + } else { + romEntry.entries.put("PokemonNames", readPointer(Gen3Constants.efrlgPokemonNamesPointer)); + romEntry.entries.put("MoveNames", readPointer(Gen3Constants.efrlgMoveNamesPointer)); + romEntry.entries.put("AbilityNames", readPointer(Gen3Constants.efrlgAbilityNamesPointer)); + romEntry.entries.put("ItemData", readPointer(Gen3Constants.efrlgItemDataPointer)); + romEntry.entries.put("MoveData", readPointer(Gen3Constants.efrlgMoveDataPointer)); + romEntry.entries.put("PokemonStats", readPointer(Gen3Constants.efrlgPokemonStatsPointer)); + romEntry.entries.put("FrontSprites", readPointer(Gen3Constants.efrlgFrontSpritesPointer)); + romEntry.entries.put("PokemonPalettes", readPointer(Gen3Constants.efrlgPokemonPalettesPointer)); + romEntry.entries.put("MoveTutorCompatibility", + romEntry.getValue("MoveTutorData") + romEntry.getValue("MoveTutorMoves") * 2); + } + + loadTextTable(romEntry.tableFile); + + if (romEntry.romCode.equals("BPRE") && romEntry.version == 0) { + basicBPRE10HackSupport(); + } + + loadPokemonNames(); + loadPokedex(); + loadPokemonStats(); + constructPokemonList(); + populateEvolutions(); + loadMoves(); + + // Get wild Pokemon offset + int baseWPOffset = findMultiple(rom, Gen3Constants.wildPokemonPointerPrefix).get(0); + romEntry.entries.put("WildPokemon", readPointer(baseWPOffset + 12)); + + // map banks + int baseMapsOffset = findMultiple(rom, Gen3Constants.mapBanksPointerPrefix).get(0); + romEntry.entries.put("MapHeaders", readPointer(baseMapsOffset + 12)); + this.determineMapBankSizes(); + + // map labels + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + int baseMLOffset = find(rom, Gen3Constants.frlgMapLabelsPointerPrefix); + romEntry.entries.put("MapLabels", readPointer(baseMLOffset + 12)); + } else { + int baseMLOffset = find(rom, Gen3Constants.rseMapLabelsPointerPrefix); + romEntry.entries.put("MapLabels", readPointer(baseMLOffset + 12)); + } + + mapLoadingDone = false; + loadAbilityNames(); + loadItemNames(); + + allowedItems = Gen3Constants.allowedItems.copy(); + nonBadItems = Gen3Constants.getNonBadItems(romEntry.romType).copy(); + + actualCRC32 = FileFunctions.getCRC32(rom); + } + + private int findPointerPrefixAndSuffix(String prefix, String suffix) { + if (prefix.length() % 2 != 0 || suffix.length() % 2 != 0) { + return -1; + } + byte[] searchPref = new byte[prefix.length() / 2]; + for (int i = 0; i < searchPref.length; i++) { + searchPref[i] = (byte) Integer.parseInt(prefix.substring(i * 2, i * 2 + 2), 16); + } + byte[] searchSuff = new byte[suffix.length() / 2]; + for (int i = 0; i < searchSuff.length; i++) { + searchSuff[i] = (byte) Integer.parseInt(suffix.substring(i * 2, i * 2 + 2), 16); + } + if (searchPref.length >= searchSuff.length) { + // Prefix first + List offsets = RomFunctions.search(rom, searchPref); + if (offsets.size() == 0) { + return -1; + } + for (int prefOffset : offsets) { + if (prefOffset + 4 + searchSuff.length > rom.length) { + continue; // not enough room for this to be valid + } + int ptrOffset = prefOffset + searchPref.length; + int pointerValue = readPointer(ptrOffset); + if (pointerValue < 0 || pointerValue >= rom.length) { + // Not a valid pointer + continue; + } + boolean suffixMatch = true; + for (int i = 0; i < searchSuff.length; i++) { + if (rom[ptrOffset + 4 + i] != searchSuff[i]) { + suffixMatch = false; + break; + } + } + if (suffixMatch) { + return ptrOffset; + } + } + return -1; // No match + } else { + // Suffix first + List offsets = RomFunctions.search(rom, searchSuff); + if (offsets.size() == 0) { + return -1; + } + for (int suffOffset : offsets) { + if (suffOffset - 4 - searchPref.length < 0) { + continue; // not enough room for this to be valid + } + int ptrOffset = suffOffset - 4; + int pointerValue = readPointer(ptrOffset); + if (pointerValue < 0 || pointerValue >= rom.length) { + // Not a valid pointer + continue; + } + boolean prefixMatch = true; + for (int i = 0; i < searchPref.length; i++) { + if (rom[ptrOffset - searchPref.length + i] != searchPref[i]) { + prefixMatch = false; + break; + } + } + if (prefixMatch) { + return ptrOffset; + } + } + return -1; // No match + } + } + + private void basicBPRE10HackSupport() { + if (basicBPRE10HackDetection()) { + this.isRomHack = true; + // NUMBER OF POKEMON DETECTION + + // this is the most annoying bit + // we'll try to get it from the pokemon names, + // and sanity check it using other things + // this of course means we can't support + // any hack with extended length names + + int iPokemonCount = 0; + int namesOffset = romEntry.getValue("PokemonNames"); + int nameLen = romEntry.getValue("PokemonNameLength"); + while (true) { + int nameOffset = namesOffset + (iPokemonCount + 1) * nameLen; + int nameStrLen = lengthOfStringAt(nameOffset); + if (nameStrLen > 0 && nameStrLen < nameLen && rom[nameOffset] != 0) { + iPokemonCount++; + } else { + break; + } + } + + // Is there an unused egg slot at the end? + String lastName = readVariableLengthString(namesOffset + iPokemonCount * nameLen); + if (lastName.equals("?") || lastName.equals("-")) { + iPokemonCount--; + } + + // Jambo's Moves Learnt table hack? + // need to check this before using moveset pointers + int movesetsTable; + if (readLong(0x3EB20) == 0x47084918) { + // Hack applied, adjust accordingly + int firstRoutinePtr = readPointer(0x3EB84); + movesetsTable = readPointer(firstRoutinePtr + 75); + jamboMovesetHack = true; + } else { + movesetsTable = readPointer(0x3EA7C); + jamboMovesetHack = false; + } + + // secondary check: moveset pointers + // if a slot has an invalid moveset pointer, it's not a real slot + // Before that, grab the moveset table from a known pointer to it. + romEntry.entries.put("PokemonMovesets", movesetsTable); + while (iPokemonCount >= 0) { + int movesetPtr = readPointer(movesetsTable + iPokemonCount * 4); + if (movesetPtr < 0 || movesetPtr >= rom.length) { + iPokemonCount--; + } else { + break; + } + } + + // sanity check: pokedex order + // pokedex entries have to be within 0-1023 + // even after extending the dex + // (at least with conventional methods) + // so if we run into an invalid one + // then we can cut off the count + int pdOffset = romEntry.getValue("PokedexOrder"); + for (int i = 1; i <= iPokemonCount; i++) { + int pdEntry = readWord(pdOffset + (i - 1) * 2); + if (pdEntry > 1023) { + iPokemonCount = i - 1; + break; + } + } + + // write new pokemon count + romEntry.entries.put("PokemonCount", iPokemonCount); + + // update some key offsets from known pointers + romEntry.entries.put("PokemonTMHMCompat", readPointer(0x43C68)); + romEntry.entries.put("PokemonEvolutions", readPointer(0x42F6C)); + romEntry.entries.put("MoveTutorCompatibility", readPointer(0x120C30)); + int descsTable = readPointer(0xE5440); + romEntry.entries.put("MoveDescriptions", descsTable); + int trainersTable = readPointer(0xFC00); + romEntry.entries.put("TrainerData", trainersTable); + + // try to detect number of moves using the descriptions + int moveCount = 0; + while (true) { + int descPointer = readPointer(descsTable + (moveCount) * 4); + if (descPointer >= 0 && descPointer < rom.length) { + int descStrLen = lengthOfStringAt(descPointer); + if (descStrLen > 0 && descStrLen < 100) { + // okay, this does seem fine + moveCount++; + continue; + } + } + break; + } + romEntry.entries.put("MoveCount", moveCount); + + // attempt to detect number of trainers using various tells + int trainerCount = 1; + int tEntryLen = romEntry.getValue("TrainerEntrySize"); + int tNameLen = romEntry.getValue("TrainerNameLength"); + while (true) { + int trOffset = trainersTable + tEntryLen * trainerCount; + int pokeDataType = rom[trOffset] & 0xFF; + if (pokeDataType >= 4) { + // only allowed 0-3 + break; + } + int numPokes = rom[trOffset + (tEntryLen - 8)] & 0xFF; + if (numPokes == 0 || numPokes > 6) { + break; + } + int pointerToPokes = readPointer(trOffset + (tEntryLen - 4)); + if (pointerToPokes < 0 || pointerToPokes >= rom.length) { + break; + } + int nameLength = lengthOfStringAt(trOffset + 4); + if (nameLength >= tNameLen) { + break; + } + // found a valid trainer entry, recognize it + trainerCount++; + } + romEntry.entries.put("TrainerCount", trainerCount); + } + + } + + private boolean basicBPRE10HackDetection() { + if (rom.length != Gen3Constants.size16M) { + return true; + } + long csum = FileFunctions.getCRC32(rom); + return csum != 3716707868L; + } + + @Override + public void savingRom() { + savePokemonStats(); + saveMoves(); + } + + private void loadPokedex() { + int pdOffset = romEntry.getValue("PokedexOrder"); + int numInternalPokes = romEntry.getValue("PokemonCount"); + int maxPokedex = 0; + internalToPokedex = new int[numInternalPokes + 1]; + pokedexToInternal = new int[numInternalPokes + 1]; + for (int i = 1; i <= numInternalPokes; i++) { + int dexEntry = readWord(rom, pdOffset + (i - 1) * 2); + if (dexEntry != 0) { + internalToPokedex[i] = dexEntry; + // take the first pokemon only for each dex entry + if (pokedexToInternal[dexEntry] == 0) { + pokedexToInternal[dexEntry] = i; + } + maxPokedex = Math.max(maxPokedex, dexEntry); + } + } + if (maxPokedex == Gen3Constants.unhackedMaxPokedex) { + // see if the slots between johto and hoenn are in use + // old rom hacks use them instead of expanding pokes + int offs = romEntry.getValue("PokemonStats"); + int usedSlots = 0; + for (int i = 0; i < Gen3Constants.unhackedMaxPokedex - Gen3Constants.unhackedRealPokedex; i++) { + int pokeSlot = Gen3Constants.hoennPokesStart + i; + int pokeOffs = offs + pokeSlot * Gen3Constants.baseStatsEntrySize; + String lowerName = pokeNames[pokeSlot].toLowerCase(); + if (!this.matches(rom, pokeOffs, Gen3Constants.emptyPokemonSig) && !lowerName.contains("unused") + && !lowerName.equals("?") && !lowerName.equals("-")) { + usedSlots++; + pokedexToInternal[Gen3Constants.unhackedRealPokedex + usedSlots] = pokeSlot; + internalToPokedex[pokeSlot] = Gen3Constants.unhackedRealPokedex + usedSlots; + } else { + internalToPokedex[pokeSlot] = 0; + } + } + // remove the fake extra slots + for (int i = usedSlots + 1; i <= Gen3Constants.unhackedMaxPokedex - Gen3Constants.unhackedRealPokedex; i++) { + pokedexToInternal[Gen3Constants.unhackedRealPokedex + i] = 0; + } + // if any slots were used at all, this is a rom hack + if (usedSlots > 0) { + this.isRomHack = true; + } + this.pokedexCount = Gen3Constants.unhackedRealPokedex + usedSlots; + } else { + this.isRomHack = true; + this.pokedexCount = maxPokedex; + } + + } + + private void constructPokemonList() { + if (!this.isRomHack) { + // simple behavior: all pokes in the dex are valid + pokemonList = Arrays.asList(pokes); + } else { + // only include "valid" pokes + pokemonList = new ArrayList<>(); + pokemonList.add(null); + for (int i = 1; i < pokes.length; i++) { + Pokemon pk = pokes[i]; + if (pk != null) { + String lowerName = pk.name.toLowerCase(); + if (!lowerName.contains("unused") && !lowerName.equals("?")) { + pokemonList.add(pk); + } + } + } + } + numRealPokemon = pokemonList.size() - 1; + + } + + private void loadPokemonStats() { + pokes = new Pokemon[this.pokedexCount + 1]; + int numInternalPokes = romEntry.getValue("PokemonCount"); + pokesInternal = new Pokemon[numInternalPokes + 1]; + int offs = romEntry.getValue("PokemonStats"); + for (int i = 1; i <= numInternalPokes; i++) { + Pokemon pk = new Pokemon(); + pk.name = pokeNames[i]; + pk.number = internalToPokedex[i]; + if (pk.number != 0) { + pokes[pk.number] = pk; + } + pokesInternal[i] = pk; + int pkoffs = offs + i * Gen3Constants.baseStatsEntrySize; + loadBasicPokeStats(pk, pkoffs); + } + + // In these games, the alternate formes of Deoxys have hardcoded stats that are used 99% of the time; + // the only times these hardcoded stats are ignored are during Link Battles. Since not many people + // are using the randomizer to battle against others, let's just always use these stats. + if (romEntry.romType == Gen3Constants.RomType_FRLG || romEntry.romType == Gen3Constants.RomType_Em) { + String deoxysStatPrefix = romEntry.strings.get("DeoxysStatPrefix"); + int offset = find(deoxysStatPrefix); + if (offset > 0) { + offset += deoxysStatPrefix.length() / 2; // because it was a prefix + Pokemon deoxys = pokes[Species.deoxys]; + deoxys.hp = readWord(offset); + deoxys.attack = readWord(offset + 2); + deoxys.defense = readWord(offset + 4); + deoxys.speed = readWord(offset + 6); + deoxys.spatk = readWord(offset + 8); + deoxys.spdef = readWord(offset + 10); + } + } + } + + private void savePokemonStats() { + // Write pokemon names & stats + int offs = romEntry.getValue("PokemonNames"); + int nameLen = romEntry.getValue("PokemonNameLength"); + int offs2 = romEntry.getValue("PokemonStats"); + int numInternalPokes = romEntry.getValue("PokemonCount"); + for (int i = 1; i <= numInternalPokes; i++) { + Pokemon pk = pokesInternal[i]; + int stringOffset = offs + i * nameLen; + writeFixedLengthString(pk.name, stringOffset, nameLen); + saveBasicPokeStats(pk, offs2 + i * Gen3Constants.baseStatsEntrySize); + } + + // Make sure to write to the hardcoded Deoxys stat location, since otherwise it will just have vanilla + // stats no matter what settings the user selected. + if (romEntry.romType == Gen3Constants.RomType_FRLG || romEntry.romType == Gen3Constants.RomType_Em) { + String deoxysStatPrefix = romEntry.strings.get("DeoxysStatPrefix"); + int offset = find(deoxysStatPrefix); + if (offset > 0) { + offset += deoxysStatPrefix.length() / 2; // because it was a prefix + Pokemon deoxys = pokes[Species.deoxys]; + writeWord(offset, deoxys.hp); + writeWord(offset + 2, deoxys.attack); + writeWord(offset + 4, deoxys.defense); + writeWord(offset + 6, deoxys.speed); + writeWord(offset + 8, deoxys.spatk); + writeWord(offset + 10, deoxys.spdef); + } + } + + writeEvolutions(); + } + + private void loadMoves() { + int moveCount = romEntry.getValue("MoveCount"); + moves = new Move[moveCount + 1]; + int offs = romEntry.getValue("MoveData"); + int nameoffs = romEntry.getValue("MoveNames"); + int namelen = romEntry.getValue("MoveNameLength"); + for (int i = 1; i <= moveCount; i++) { + moves[i] = new Move(); + moves[i].name = readFixedLengthString(nameoffs + i * namelen, namelen); + moves[i].number = i; + moves[i].internalId = i; + moves[i].effectIndex = rom[offs + i * 0xC] & 0xFF; + moves[i].hitratio = ((rom[offs + i * 0xC + 3] & 0xFF)); + moves[i].power = rom[offs + i * 0xC + 1] & 0xFF; + moves[i].pp = rom[offs + i * 0xC + 4] & 0xFF; + moves[i].type = Gen3Constants.typeTable[rom[offs + i * 0xC + 2]]; + moves[i].target = rom[offs + i * 0xC + 6] & 0xFF; + moves[i].category = GBConstants.physicalTypes.contains(moves[i].type) ? MoveCategory.PHYSICAL : MoveCategory.SPECIAL; + if (moves[i].power == 0 && !GlobalConstants.noPowerNonStatusMoves.contains(i)) { + moves[i].category = MoveCategory.STATUS; + } + moves[i].priority = rom[offs + i * 0xC + 7]; + int flags = rom[offs + i * 0xC + 8] & 0xFF; + moves[i].makesContact = (flags & 1) != 0; + moves[i].isSoundMove = Gen3Constants.soundMoves.contains(moves[i].number); + + if (i == Moves.swift) { + perfectAccuracy = (int)moves[i].hitratio; + } + + if (GlobalConstants.normalMultihitMoves.contains(i)) { + moves[i].hitCount = 3; + } else if (GlobalConstants.doubleHitMoves.contains(i)) { + moves[i].hitCount = 2; + } else if (i == Moves.tripleKick) { + moves[i].hitCount = 2.71; // this assumes the first hit lands + } + + int secondaryEffectChance = rom[offs + i * 0xC + 5] & 0xFF; + loadStatChangesFromEffect(moves[i], secondaryEffectChance); + loadStatusFromEffect(moves[i], secondaryEffectChance); + loadMiscMoveInfoFromEffect(moves[i], secondaryEffectChance); + } + } + + private void loadStatChangesFromEffect(Move move, int secondaryEffectChance) { + switch (move.effectIndex) { + case Gen3Constants.noDamageAtkPlusOneEffect: + case Gen3Constants.noDamageDefPlusOneEffect: + case Gen3Constants.noDamageSpAtkPlusOneEffect: + case Gen3Constants.noDamageEvasionPlusOneEffect: + case Gen3Constants.noDamageAtkMinusOneEffect: + case Gen3Constants.noDamageDefMinusOneEffect: + case Gen3Constants.noDamageSpeMinusOneEffect: + case Gen3Constants.noDamageAccuracyMinusOneEffect: + case Gen3Constants.noDamageEvasionMinusOneEffect: + case Gen3Constants.noDamageAtkPlusTwoEffect: + case Gen3Constants.noDamageDefPlusTwoEffect: + case Gen3Constants.noDamageSpePlusTwoEffect: + case Gen3Constants.noDamageSpAtkPlusTwoEffect: + case Gen3Constants.noDamageSpDefPlusTwoEffect: + case Gen3Constants.noDamageAtkMinusTwoEffect: + case Gen3Constants.noDamageDefMinusTwoEffect: + case Gen3Constants.noDamageSpeMinusTwoEffect: + case Gen3Constants.noDamageSpDefMinusTwoEffect: + case Gen3Constants.minimizeEffect: + case Gen3Constants.swaggerEffect: + case Gen3Constants.defenseCurlEffect: + case Gen3Constants.flatterEffect: + case Gen3Constants.chargeEffect: + case Gen3Constants.noDamageAtkAndDefMinusOneEffect: + case Gen3Constants.noDamageDefAndSpDefPlusOneEffect: + case Gen3Constants.noDamageAtkAndDefPlusOneEffect: + case Gen3Constants.noDamageSpAtkAndSpDefPlusOneEffect: + case Gen3Constants.noDamageAtkAndSpePlusOneEffect: + if (move.target == 16) { + move.statChangeMoveType = StatChangeMoveType.NO_DAMAGE_USER; + } else { + move.statChangeMoveType = StatChangeMoveType.NO_DAMAGE_TARGET; + } + break; + + case Gen3Constants.damageAtkMinusOneEffect: + case Gen3Constants.damageDefMinusOneEffect: + case Gen3Constants.damageSpeMinusOneEffect: + case Gen3Constants.damageSpAtkMinusOneEffect: + case Gen3Constants.damageSpDefMinusOneEffect: + case Gen3Constants.damageAccuracyMinusOneEffect: + move.statChangeMoveType = StatChangeMoveType.DAMAGE_TARGET; + break; + + case Gen3Constants.damageUserDefPlusOneEffect: + case Gen3Constants.damageUserAtkPlusOneEffect: + case Gen3Constants.damageUserAllPlusOneEffect: + case Gen3Constants.damageUserAtkAndDefMinusOneEffect: + case Gen3Constants.damageUserSpAtkMinusTwoEffect: + move.statChangeMoveType = StatChangeMoveType.DAMAGE_USER; + break; + + default: + // Move does not have a stat-changing effect + return; + } + + switch (move.effectIndex) { + case Gen3Constants.noDamageAtkPlusOneEffect: + case Gen3Constants.damageUserAtkPlusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 1; + break; + case Gen3Constants.noDamageDefPlusOneEffect: + case Gen3Constants.damageUserDefPlusOneEffect: + case Gen3Constants.defenseCurlEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = 1; + break; + case Gen3Constants.noDamageSpAtkPlusOneEffect: + case Gen3Constants.flatterEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_ATTACK; + move.statChanges[0].stages = 1; + break; + case Gen3Constants.noDamageEvasionPlusOneEffect: + case Gen3Constants.minimizeEffect: + move.statChanges[0].type = StatChangeType.EVASION; + move.statChanges[0].stages = 1; + break; + case Gen3Constants.noDamageAtkMinusOneEffect: + case Gen3Constants.damageAtkMinusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = -1; + break; + case Gen3Constants.noDamageDefMinusOneEffect: + case Gen3Constants.damageDefMinusOneEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = -1; + break; + case Gen3Constants.noDamageSpeMinusOneEffect: + case Gen3Constants.damageSpeMinusOneEffect: + move.statChanges[0].type = StatChangeType.SPEED; + move.statChanges[0].stages = -1; + break; + case Gen3Constants.noDamageAccuracyMinusOneEffect: + case Gen3Constants.damageAccuracyMinusOneEffect: + move.statChanges[0].type = StatChangeType.ACCURACY; + move.statChanges[0].stages = -1; + break; + case Gen3Constants.noDamageEvasionMinusOneEffect: + move.statChanges[0].type = StatChangeType.EVASION; + move.statChanges[0].stages = -1; + break; + case Gen3Constants.noDamageAtkPlusTwoEffect: + case Gen3Constants.swaggerEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 2; + break; + case Gen3Constants.noDamageDefPlusTwoEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = 2; + break; + case Gen3Constants.noDamageSpePlusTwoEffect: + move.statChanges[0].type = StatChangeType.SPEED; + move.statChanges[0].stages = 2; + break; + case Gen3Constants.noDamageSpAtkPlusTwoEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_ATTACK; + move.statChanges[0].stages = 2; + break; + case Gen3Constants.noDamageSpDefPlusTwoEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[0].stages = 2; + break; + case Gen3Constants.noDamageAtkMinusTwoEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = -2; + break; + case Gen3Constants.noDamageDefMinusTwoEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = -2; + break; + case Gen3Constants.noDamageSpeMinusTwoEffect: + move.statChanges[0].type = StatChangeType.SPEED; + move.statChanges[0].stages = -2; + break; + case Gen3Constants.noDamageSpDefMinusTwoEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[0].stages = -2; + break; + case Gen3Constants.damageSpAtkMinusOneEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_ATTACK; + move.statChanges[0].stages = -1; + break; + case Gen3Constants.damageSpDefMinusOneEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[0].stages = -1; + break; + case Gen3Constants.damageUserAllPlusOneEffect: + move.statChanges[0].type = StatChangeType.ALL; + move.statChanges[0].stages = 1; + break; + case Gen3Constants.chargeEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[0].stages = 1; + break; + case Gen3Constants.damageUserAtkAndDefMinusOneEffect: + case Gen3Constants.noDamageAtkAndDefMinusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = -1; + move.statChanges[1].type = StatChangeType.DEFENSE; + move.statChanges[1].stages = -1; + break; + case Gen3Constants.damageUserSpAtkMinusTwoEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_ATTACK; + move.statChanges[0].stages = -2; + break; + case Gen3Constants.noDamageDefAndSpDefPlusOneEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = 1; + move.statChanges[1].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[1].stages = 1; + break; + case Gen3Constants.noDamageAtkAndDefPlusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 1; + move.statChanges[1].type = StatChangeType.DEFENSE; + move.statChanges[1].stages = 1; + break; + case Gen3Constants.noDamageSpAtkAndSpDefPlusOneEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_ATTACK; + move.statChanges[0].stages = 1; + move.statChanges[1].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[1].stages = 1; + break; + case Gen3Constants.noDamageAtkAndSpePlusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 1; + move.statChanges[1].type = StatChangeType.SPEED; + move.statChanges[1].stages = 1; + break; + } + + if (move.statChangeMoveType == StatChangeMoveType.DAMAGE_TARGET || move.statChangeMoveType == StatChangeMoveType.DAMAGE_USER) { + for (int i = 0; i < move.statChanges.length; i++) { + if (move.statChanges[i].type != StatChangeType.NONE) { + move.statChanges[i].percentChance = secondaryEffectChance; + if (move.statChanges[i].percentChance == 0.0) { + move.statChanges[i].percentChance = 100.0; + } + } + } + } + } + + private void loadStatusFromEffect(Move move, int secondaryEffectChance) { + if (move.number == Moves.bounce) { + // GF hardcoded this, so we have to as well + move.statusMoveType = StatusMoveType.DAMAGE; + move.statusType = StatusType.PARALYZE; + move.statusPercentChance = secondaryEffectChance; + return; + } + + switch (move.effectIndex) { + case Gen3Constants.noDamageSleepEffect: + case Gen3Constants.toxicEffect: + case Gen3Constants.noDamageConfusionEffect: + case Gen3Constants.noDamagePoisonEffect: + case Gen3Constants.noDamageParalyzeEffect: + case Gen3Constants.noDamageBurnEffect: + case Gen3Constants.swaggerEffect: + case Gen3Constants.flatterEffect: + case Gen3Constants.teeterDanceEffect: + move.statusMoveType = StatusMoveType.NO_DAMAGE; + break; + + case Gen3Constants.damagePoisonEffect: + case Gen3Constants.damageBurnEffect: + case Gen3Constants.damageFreezeEffect: + case Gen3Constants.damageParalyzeEffect: + case Gen3Constants.damageConfusionEffect: + case Gen3Constants.twineedleEffect: + case Gen3Constants.damageBurnAndThawUserEffect: + case Gen3Constants.thunderEffect: + case Gen3Constants.blazeKickEffect: + case Gen3Constants.poisonFangEffect: + case Gen3Constants.poisonTailEffect: + move.statusMoveType = StatusMoveType.DAMAGE; + break; + + default: + // Move does not have a status effect + return; + } + + switch (move.effectIndex) { + case Gen3Constants.noDamageSleepEffect: + move.statusType = StatusType.SLEEP; + break; + case Gen3Constants.damagePoisonEffect: + case Gen3Constants.noDamagePoisonEffect: + case Gen3Constants.twineedleEffect: + case Gen3Constants.poisonTailEffect: + move.statusType = StatusType.POISON; + break; + case Gen3Constants.damageBurnEffect: + case Gen3Constants.damageBurnAndThawUserEffect: + case Gen3Constants.noDamageBurnEffect: + case Gen3Constants.blazeKickEffect: + move.statusType = StatusType.BURN; + break; + case Gen3Constants.damageFreezeEffect: + move.statusType = StatusType.FREEZE; + break; + case Gen3Constants.damageParalyzeEffect: + case Gen3Constants.noDamageParalyzeEffect: + case Gen3Constants.thunderEffect: + move.statusType = StatusType.PARALYZE; + break; + case Gen3Constants.toxicEffect: + case Gen3Constants.poisonFangEffect: + move.statusType = StatusType.TOXIC_POISON; + break; + case Gen3Constants.noDamageConfusionEffect: + case Gen3Constants.damageConfusionEffect: + case Gen3Constants.swaggerEffect: + case Gen3Constants.flatterEffect: + case Gen3Constants.teeterDanceEffect: + move.statusType = StatusType.CONFUSION; + break; + } + + if (move.statusMoveType == StatusMoveType.DAMAGE) { + move.statusPercentChance = secondaryEffectChance; + if (move.statusPercentChance == 0.0) { + move.statusPercentChance = 100.0; + } + } + } + + private void loadMiscMoveInfoFromEffect(Move move, int secondaryEffectChance) { + switch (move.effectIndex) { + case Gen3Constants.increasedCritEffect: + case Gen3Constants.blazeKickEffect: + case Gen3Constants.poisonTailEffect: + move.criticalChance = CriticalChance.INCREASED; + break; + + case Gen3Constants.futureSightAndDoomDesireEffect: + case Gen3Constants.spitUpEffect: + move.criticalChance = CriticalChance.NONE; + + case Gen3Constants.flinchEffect: + case Gen3Constants.snoreEffect: + case Gen3Constants.twisterEffect: + case Gen3Constants.flinchWithMinimizeBonusEffect: + case Gen3Constants.fakeOutEffect: + move.flinchPercentChance = secondaryEffectChance; + break; + + case Gen3Constants.damageAbsorbEffect: + case Gen3Constants.dreamEaterEffect: + move.absorbPercent = 50; + break; + + case Gen3Constants.damageRecoil25PercentEffect: + move.recoilPercent = 25; + break; + + case Gen3Constants.damageRecoil33PercentEffect: + move.recoilPercent = 33; + break; + + case Gen3Constants.bindingEffect: + case Gen3Constants.trappingEffect: + move.isTrapMove = true; + break; + + case Gen3Constants.razorWindEffect: + case Gen3Constants.skullBashEffect: + case Gen3Constants.solarbeamEffect: + case Gen3Constants.semiInvulnerableEffect: + move.isChargeMove = true; + break; + + case Gen3Constants.rechargeEffect: + move.isRechargeMove = true; + break; + + case Gen3Constants.skyAttackEffect: + move.criticalChance = CriticalChance.INCREASED; + move.flinchPercentChance = secondaryEffectChance; + move.isChargeMove = true; + break; + } + } + + private void saveMoves() { + int moveCount = romEntry.getValue("MoveCount"); + int offs = romEntry.getValue("MoveData"); + for (int i = 1; i <= moveCount; i++) { + rom[offs + i * 0xC] = (byte) moves[i].effectIndex; + rom[offs + i * 0xC + 1] = (byte) moves[i].power; + rom[offs + i * 0xC + 2] = Gen3Constants.typeToByte(moves[i].type); + int hitratio = (int) Math.round(moves[i].hitratio); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 100) { + hitratio = 100; + } + rom[offs + i * 0xC + 3] = (byte) hitratio; + rom[offs + i * 0xC + 4] = (byte) moves[i].pp; + } + } + + public List getMoves() { + return Arrays.asList(moves); + } + + private void loadBasicPokeStats(Pokemon pkmn, int offset) { + pkmn.hp = rom[offset + Gen3Constants.bsHPOffset] & 0xFF; + pkmn.attack = rom[offset + Gen3Constants.bsAttackOffset] & 0xFF; + pkmn.defense = rom[offset + Gen3Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = rom[offset + Gen3Constants.bsSpeedOffset] & 0xFF; + pkmn.spatk = rom[offset + Gen3Constants.bsSpAtkOffset] & 0xFF; + pkmn.spdef = rom[offset + Gen3Constants.bsSpDefOffset] & 0xFF; + // Type + pkmn.primaryType = Gen3Constants.typeTable[rom[offset + Gen3Constants.bsPrimaryTypeOffset] & 0xFF]; + pkmn.secondaryType = Gen3Constants.typeTable[rom[offset + Gen3Constants.bsSecondaryTypeOffset] & 0xFF]; + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + pkmn.catchRate = rom[offset + Gen3Constants.bsCatchRateOffset] & 0xFF; + pkmn.growthCurve = ExpCurve.fromByte(rom[offset + Gen3Constants.bsGrowthCurveOffset]); + // Abilities + pkmn.ability1 = rom[offset + Gen3Constants.bsAbility1Offset] & 0xFF; + pkmn.ability2 = rom[offset + Gen3Constants.bsAbility2Offset] & 0xFF; + + // Held Items? + int item1 = readWord(offset + Gen3Constants.bsCommonHeldItemOffset); + int item2 = readWord(offset + Gen3Constants.bsRareHeldItemOffset); + + if (item1 == item2) { + // guaranteed + pkmn.guaranteedHeldItem = item1; + pkmn.commonHeldItem = 0; + pkmn.rareHeldItem = 0; + } else { + pkmn.guaranteedHeldItem = 0; + pkmn.commonHeldItem = item1; + pkmn.rareHeldItem = item2; + } + pkmn.darkGrassHeldItem = -1; + + pkmn.genderRatio = rom[offset + Gen3Constants.bsGenderRatioOffset] & 0xFF; + } + + private void saveBasicPokeStats(Pokemon pkmn, int offset) { + rom[offset + Gen3Constants.bsHPOffset] = (byte) pkmn.hp; + rom[offset + Gen3Constants.bsAttackOffset] = (byte) pkmn.attack; + rom[offset + Gen3Constants.bsDefenseOffset] = (byte) pkmn.defense; + rom[offset + Gen3Constants.bsSpeedOffset] = (byte) pkmn.speed; + rom[offset + Gen3Constants.bsSpAtkOffset] = (byte) pkmn.spatk; + rom[offset + Gen3Constants.bsSpDefOffset] = (byte) pkmn.spdef; + rom[offset + Gen3Constants.bsPrimaryTypeOffset] = Gen3Constants.typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + rom[offset + Gen3Constants.bsSecondaryTypeOffset] = rom[offset + Gen3Constants.bsPrimaryTypeOffset]; + } else { + rom[offset + Gen3Constants.bsSecondaryTypeOffset] = Gen3Constants.typeToByte(pkmn.secondaryType); + } + rom[offset + Gen3Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + rom[offset + Gen3Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + + rom[offset + Gen3Constants.bsAbility1Offset] = (byte) pkmn.ability1; + if (pkmn.ability2 == 0) { + // required to not break evos with random ability + rom[offset + Gen3Constants.bsAbility2Offset] = (byte) pkmn.ability1; + } else { + rom[offset + Gen3Constants.bsAbility2Offset] = (byte) pkmn.ability2; + } + + // Held items + if (pkmn.guaranteedHeldItem > 0) { + writeWord(offset + Gen3Constants.bsCommonHeldItemOffset, pkmn.guaranteedHeldItem); + writeWord(offset + Gen3Constants.bsRareHeldItemOffset, pkmn.guaranteedHeldItem); + } else { + writeWord(offset + Gen3Constants.bsCommonHeldItemOffset, pkmn.commonHeldItem); + writeWord(offset + Gen3Constants.bsRareHeldItemOffset, pkmn.rareHeldItem); + } + + rom[offset + Gen3Constants.bsGenderRatioOffset] = (byte) pkmn.genderRatio; + } + + private void loadPokemonNames() { + int offs = romEntry.getValue("PokemonNames"); + int nameLen = romEntry.getValue("PokemonNameLength"); + int numInternalPokes = romEntry.getValue("PokemonCount"); + pokeNames = new String[numInternalPokes + 1]; + for (int i = 1; i <= numInternalPokes; i++) { + pokeNames[i] = readFixedLengthString(offs + i * nameLen, nameLen); + } + } + + private String readString(int offset, int maxLength) { + StringBuilder string = new StringBuilder(); + for (int c = 0; c < maxLength; c++) { + int currChar = rom[offset + c] & 0xFF; + if (tb[currChar] != null) { + string.append(tb[currChar]); + } else { + if (currChar == Gen3Constants.textTerminator) { + break; + } else if (currChar == Gen3Constants.textVariable) { + int nextChar = rom[offset + c + 1] & 0xFF; + string.append("\\v").append(String.format("%02X", nextChar)); + c++; + } else { + string.append("\\x").append(String.format("%02X", currChar)); + } + } + } + return string.toString(); + } + + private byte[] translateString(String text) { + List data = new ArrayList<>(); + while (text.length() != 0) { + int i = Math.max(0, 4 - text.length()); + if (text.charAt(0) == '\\' && text.charAt(1) == 'x') { + data.add((byte) Integer.parseInt(text.substring(2, 4), 16)); + text = text.substring(4); + } else if (text.charAt(0) == '\\' && text.charAt(1) == 'v') { + data.add((byte) Gen3Constants.textVariable); + data.add((byte) Integer.parseInt(text.substring(2, 4), 16)); + text = text.substring(4); + } else { + while (!(d.containsKey(text.substring(0, 4 - i)) || (i == 4))) { + i++; + } + if (i == 4) { + text = text.substring(1); + } else { + data.add(d.get(text.substring(0, 4 - i))); + text = text.substring(4 - i); + } + } + } + byte[] ret = new byte[data.size()]; + for (int i = 0; i < ret.length; i++) { + ret[i] = data.get(i); + } + return ret; + } + + private String readFixedLengthString(int offset, int length) { + return readString(offset, length); + } + + private String readVariableLengthString(int offset) { + return readString(offset, Integer.MAX_VALUE); + } + + private void writeFixedLengthString(String str, int offset, int length) { + byte[] translated = translateString(str); + int len = Math.min(translated.length, length); + System.arraycopy(translated, 0, rom, offset, len); + if (len < length) { + rom[offset + len] = (byte) Gen3Constants.textTerminator; + len++; + } + while (len < length) { + rom[offset + len] = 0; + len++; + } + } + + private void writeVariableLengthString(String str, int offset) { + byte[] translated = translateString(str); + System.arraycopy(translated, 0, rom, offset, translated.length); + rom[offset + translated.length] = (byte) 0xFF; + } + + private int lengthOfStringAt(int offset) { + int len = 0; + while ((rom[offset + (len++)] & 0xFF) != 0xFF) { + } + return len - 1; + } + + private static boolean romName(byte[] rom, String name) { + try { + int sigOffset = Gen3Constants.romNameOffset; + byte[] sigBytes = name.getBytes("US-ASCII"); + for (int i = 0; i < sigBytes.length; i++) { + if (rom[sigOffset + i] != sigBytes[i]) { + return false; + } + } + return true; + } catch (UnsupportedEncodingException ex) { + return false; + } + + } + + private static boolean romCode(byte[] rom, String codeToCheck) { + try { + int sigOffset = Gen3Constants.romCodeOffset; + byte[] sigBytes = codeToCheck.getBytes("US-ASCII"); + for (int i = 0; i < sigBytes.length; i++) { + if (rom[sigOffset + i] != sigBytes[i]) { + return false; + } + } + return true; + } catch (UnsupportedEncodingException ex) { + return false; + } + + } + + private int readPointer(int offset) { + return readLong(offset) - 0x8000000; + } + + private int readLong(int offset) { + return (rom[offset] & 0xFF) + ((rom[offset + 1] & 0xFF) << 8) + ((rom[offset + 2] & 0xFF) << 16) + + (((rom[offset + 3] & 0xFF)) << 24); + } + + private void writePointer(int offset, int pointer) { + writeLong(offset, pointer + 0x8000000); + } + + private void writeLong(int offset, int value) { + rom[offset] = (byte) (value & 0xFF); + rom[offset + 1] = (byte) ((value >> 8) & 0xFF); + rom[offset + 2] = (byte) ((value >> 16) & 0xFF); + rom[offset + 3] = (byte) (((value >> 24) & 0xFF)); + } + + @Override + public List getStarters() { + List starters = new ArrayList<>(); + int baseOffset = romEntry.getValue("StarterPokemon"); + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp + || romEntry.romType == Gen3Constants.RomType_Em) { + // do something + Pokemon starter1 = pokesInternal[readWord(baseOffset)]; + Pokemon starter2 = pokesInternal[readWord(baseOffset + Gen3Constants.rseStarter2Offset)]; + Pokemon starter3 = pokesInternal[readWord(baseOffset + Gen3Constants.rseStarter3Offset)]; + starters.add(starter1); + starters.add(starter2); + starters.add(starter3); + } else { + // do something else + Pokemon starter1 = pokesInternal[readWord(baseOffset)]; + Pokemon starter2 = pokesInternal[readWord(baseOffset + Gen3Constants.frlgStarter2Offset)]; + Pokemon starter3 = pokesInternal[readWord(baseOffset + Gen3Constants.frlgStarter3Offset)]; + starters.add(starter1); + starters.add(starter2); + starters.add(starter3); + } + return starters; + } + + @Override + public boolean setStarters(List newStarters) { + if (newStarters.size() != 3) { + return false; + } + + // Support Deoxys/Mew starters in E/FR/LG + attemptObedienceEvolutionPatches(); + int baseOffset = romEntry.getValue("StarterPokemon"); + + int starter0 = pokedexToInternal[newStarters.get(0).number]; + int starter1 = pokedexToInternal[newStarters.get(1).number]; + int starter2 = pokedexToInternal[newStarters.get(2).number]; + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp + || romEntry.romType == Gen3Constants.RomType_Em) { + + // US + // order: 0, 1, 2 + writeWord(baseOffset, starter0); + writeWord(baseOffset + Gen3Constants.rseStarter2Offset, starter1); + writeWord(baseOffset + Gen3Constants.rseStarter3Offset, starter2); + + } else { + // frlg: + // order: 0, 1, 2 + writeWord(baseOffset, starter0); + writeWord(baseOffset + Gen3Constants.frlgStarterRepeatOffset, starter1); + + writeWord(baseOffset + Gen3Constants.frlgStarter2Offset, starter1); + writeWord(baseOffset + Gen3Constants.frlgStarter2Offset + Gen3Constants.frlgStarterRepeatOffset, starter2); + + writeWord(baseOffset + Gen3Constants.frlgStarter3Offset, starter2); + writeWord(baseOffset + Gen3Constants.frlgStarter3Offset + Gen3Constants.frlgStarterRepeatOffset, starter0); + + if (romEntry.romCode.charAt(3) != 'J' && romEntry.romCode.charAt(3) != 'B') { + // Update PROF. Oak's descriptions for each starter + // First result for each STARTERNAME is the text we need + List bulbasaurFoundTexts = RomFunctions.search(rom, translateString(pokes[Gen3Constants.frlgBaseStarter1].name.toUpperCase())); + List charmanderFoundTexts = RomFunctions.search(rom, translateString(pokes[Gen3Constants.frlgBaseStarter2].name.toUpperCase())); + List squirtleFoundTexts = RomFunctions.search(rom, translateString(pokes[Gen3Constants.frlgBaseStarter3].name.toUpperCase())); + writeFRLGStarterText(bulbasaurFoundTexts, newStarters.get(0), "you want to go with\\nthe "); + writeFRLGStarterText(charmanderFoundTexts, newStarters.get(1), "you’re claiming the\\n"); + writeFRLGStarterText(squirtleFoundTexts, newStarters.get(2), "you’ve decided on the\\n"); + } + } + return true; + + } + + @Override + public boolean hasStarterAltFormes() { + return false; + } + + @Override + public int starterCount() { + return 3; + } + + @Override + public Map getUpdatedPokemonStats(int generation) { + return GlobalConstants.getStatChanges(generation); + } + + @Override + public boolean supportsStarterHeldItems() { + return true; + } + + @Override + public List getStarterHeldItems() { + List sHeldItems = new ArrayList<>(); + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + // offset from normal starter offset as a word + int baseOffset = romEntry.getValue("StarterPokemon"); + sHeldItems.add(readWord(baseOffset + Gen3Constants.frlgStarterItemsOffset)); + } else { + int baseOffset = romEntry.getValue("StarterItems"); + int i1 = rom[baseOffset] & 0xFF; + int i2 = rom[baseOffset + 2] & 0xFF; + if (i2 == 0) { + sHeldItems.add(i1); + } else { + sHeldItems.add(i2 + 0xFF); + } + } + return sHeldItems; + } + + @Override + public void setStarterHeldItems(List items) { + if (items.size() != 1) { + return; + } + int item = items.get(0); + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + // offset from normal starter offset as a word + int baseOffset = romEntry.getValue("StarterPokemon"); + writeWord(baseOffset + Gen3Constants.frlgStarterItemsOffset, item); + } else { + int baseOffset = romEntry.getValue("StarterItems"); + if (item <= 0xFF) { + rom[baseOffset] = (byte) item; + rom[baseOffset + 2] = 0; + rom[baseOffset + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR2; + } else { + rom[baseOffset] = (byte) 0xFF; + rom[baseOffset + 2] = (byte) (item - 0xFF); + rom[baseOffset + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR2; + } + } + } + + private void writeFRLGStarterText(List foundTexts, Pokemon pkmn, String oakText) { + if (foundTexts.size() > 0) { + int offset = foundTexts.get(0); + String pokeName = pkmn.name; + String pokeType = pkmn.primaryType == null ? "???" : pkmn.primaryType.toString(); + if (pokeType.equals("NORMAL") && pkmn.secondaryType != null) { + pokeType = pkmn.secondaryType.toString(); + } + String speech = pokeName + " is your choice.\\pSo, \\v01, " + oakText + pokeType + " POKéMON " + pokeName + + "?"; + writeFixedLengthString(speech, offset, lengthOfStringAt(offset) + 1); + } + } + + @Override + public List getEncounters(boolean useTimeOfDay) { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + + int startOffs = romEntry.getValue("WildPokemon"); + List encounterAreas = new ArrayList<>(); + Set seenOffsets = new TreeSet<>(); + int offs = startOffs; + while (true) { + // Read pointers + int bank = rom[offs] & 0xFF; + int map = rom[offs + 1] & 0xFF; + if (bank == 0xFF && map == 0xFF) { + break; + } + + String mapName = mapNames[bank][map]; + + int grassPokes = readPointer(offs + 4); + int waterPokes = readPointer(offs + 8); + int treePokes = readPointer(offs + 12); + int fishPokes = readPointer(offs + 16); + + // Add pokemanz + if (grassPokes >= 0 && grassPokes < rom.length && rom[grassPokes] != 0 + && !seenOffsets.contains(readPointer(grassPokes + 4))) { + encounterAreas.add(readWildArea(grassPokes, Gen3Constants.grassSlots, mapName + " Grass/Cave")); + seenOffsets.add(readPointer(grassPokes + 4)); + } + if (waterPokes >= 0 && waterPokes < rom.length && rom[waterPokes] != 0 + && !seenOffsets.contains(readPointer(waterPokes + 4))) { + encounterAreas.add(readWildArea(waterPokes, Gen3Constants.surfingSlots, mapName + " Surfing")); + seenOffsets.add(readPointer(waterPokes + 4)); + } + if (treePokes >= 0 && treePokes < rom.length && rom[treePokes] != 0 + && !seenOffsets.contains(readPointer(treePokes + 4))) { + encounterAreas.add(readWildArea(treePokes, Gen3Constants.rockSmashSlots, mapName + " Rock Smash")); + seenOffsets.add(readPointer(treePokes + 4)); + } + if (fishPokes >= 0 && fishPokes < rom.length && rom[fishPokes] != 0 + && !seenOffsets.contains(readPointer(fishPokes + 4))) { + encounterAreas.add(readWildArea(fishPokes, Gen3Constants.fishingSlots, mapName + " Fishing")); + seenOffsets.add(readPointer(fishPokes + 4)); + } + + offs += 20; + } + if (romEntry.arrayEntries.containsKey("BattleTrappersBanned")) { + // Some encounter sets aren't allowed to have Pokemon + // with Arena Trap, Shadow Tag etc. + int[] bannedAreas = romEntry.arrayEntries.get("BattleTrappersBanned"); + Set battleTrappers = new HashSet<>(); + for (Pokemon pk : getPokemon()) { + if (hasBattleTrappingAbility(pk)) { + battleTrappers.add(pk); + } + } + for (int areaIdx : bannedAreas) { + encounterAreas.get(areaIdx).bannedPokemon.addAll(battleTrappers); + } + } + return encounterAreas; + } + + private boolean hasBattleTrappingAbility(Pokemon pokemon) { + return pokemon != null + && (GlobalConstants.battleTrappingAbilities.contains(pokemon.ability1) || GlobalConstants.battleTrappingAbilities + .contains(pokemon.ability2)); + } + + private EncounterSet readWildArea(int offset, int numOfEntries, String setName) { + EncounterSet thisSet = new EncounterSet(); + thisSet.rate = rom[offset]; + thisSet.displayName = setName; + // Grab the *real* pointer to data + int dataOffset = readPointer(offset + 4); + // Read the entries + for (int i = 0; i < numOfEntries; i++) { + // min, max, species, species + Encounter enc = new Encounter(); + enc.level = rom[dataOffset + i * 4]; + enc.maxLevel = rom[dataOffset + i * 4 + 1]; + try { + enc.pokemon = pokesInternal[readWord(dataOffset + i * 4 + 2)]; + } catch (ArrayIndexOutOfBoundsException ex) { + throw ex; + } + thisSet.encounters.add(enc); + } + return thisSet; + } + + @Override + public void setEncounters(boolean useTimeOfDay, List encounters) { + // Support Deoxys/Mew catches in E/FR/LG + attemptObedienceEvolutionPatches(); + + int startOffs = romEntry.getValue("WildPokemon"); + Iterator encounterAreas = encounters.iterator(); + Set seenOffsets = new TreeSet<>(); + int offs = startOffs; + while (true) { + // Read pointers + int bank = rom[offs] & 0xFF; + int map = rom[offs + 1] & 0xFF; + if (bank == 0xFF && map == 0xFF) { + break; + } + + int grassPokes = readPointer(offs + 4); + int waterPokes = readPointer(offs + 8); + int treePokes = readPointer(offs + 12); + int fishPokes = readPointer(offs + 16); + + // Add pokemanz + if (grassPokes >= 0 && grassPokes < rom.length && rom[grassPokes] != 0 + && !seenOffsets.contains(readPointer(grassPokes + 4))) { + writeWildArea(grassPokes, Gen3Constants.grassSlots, encounterAreas.next()); + seenOffsets.add(readPointer(grassPokes + 4)); + } + if (waterPokes >= 0 && waterPokes < rom.length && rom[waterPokes] != 0 + && !seenOffsets.contains(readPointer(waterPokes + 4))) { + writeWildArea(waterPokes, Gen3Constants.surfingSlots, encounterAreas.next()); + seenOffsets.add(readPointer(waterPokes + 4)); + } + if (treePokes >= 0 && treePokes < rom.length && rom[treePokes] != 0 + && !seenOffsets.contains(readPointer(treePokes + 4))) { + writeWildArea(treePokes, Gen3Constants.rockSmashSlots, encounterAreas.next()); + seenOffsets.add(readPointer(treePokes + 4)); + } + if (fishPokes >= 0 && fishPokes < rom.length && rom[fishPokes] != 0 + && !seenOffsets.contains(readPointer(fishPokes + 4))) { + writeWildArea(fishPokes, Gen3Constants.fishingSlots, encounterAreas.next()); + seenOffsets.add(readPointer(fishPokes + 4)); + } + + offs += 20; + } + } + + @Override + public boolean hasWildAltFormes() { + return false; + } + + @Override + public List bannedForWildEncounters() { + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + // Ban Unown in FRLG because the game crashes if it is encountered outside of Tanoby Ruins. + // See GenerateWildMon in wild_encounter.c in pokefirered + return new ArrayList<>(Collections.singletonList(pokes[Species.unown])); + } + return new ArrayList<>(); + } + + @Override + public List getTrainers() { + int baseOffset = romEntry.getValue("TrainerData"); + int amount = romEntry.getValue("TrainerCount"); + int entryLen = romEntry.getValue("TrainerEntrySize"); + List theTrainers = new ArrayList<>(); + List tcnames = this.getTrainerClassNames(); + for (int i = 1; i < amount; i++) { + // Trainer entries are 40 bytes + // Team flags; 1 byte; 0x01 = custom moves, 0x02 = held item + // Class; 1 byte + // Encounter Music and gender; 1 byte + // Battle Sprite; 1 byte + // Name; 12 bytes; 0xff terminated + // Items; 2 bytes each, 4 item slots + // Battle Mode; 1 byte; 0 means single, 1 means double. + // 3 bytes not used + // AI Flags; 1 byte + // 3 bytes not used + // Number of pokemon in team; 1 byte + // 3 bytes not used + // Pointer to pokemon; 4 bytes + // https://github.com/pret/pokefirered/blob/3dce3407d5f9bca69d61b1cf1b314fb1e921d572/include/battle.h#L111 + int trOffset = baseOffset + i * entryLen; + Trainer tr = new Trainer(); + tr.offset = trOffset; + tr.index = i; + int trainerclass = rom[trOffset + 1] & 0xFF; + tr.trainerclass = (rom[trOffset + 2] & 0x80) > 0 ? 1 : 0; + + int pokeDataType = rom[trOffset] & 0xFF; + boolean doubleBattle = rom[trOffset + (entryLen - 16)] == 0x01; + int numPokes = rom[trOffset + (entryLen - 8)] & 0xFF; + int pointerToPokes = readPointer(trOffset + (entryLen - 4)); + tr.poketype = pokeDataType; + tr.name = this.readVariableLengthString(trOffset + 4); + tr.fullDisplayName = tcnames.get(trainerclass) + " " + tr.name; + // Pokemon structure data is like + // IV IV LV SP SP + // (HI HI) + // (M1 M1 M2 M2 M3 M3 M4 M4) + // IV is a "difficulty" level between 0 and 255 to represent 0 to 31 IVs. + // These IVs affect all attributes. For the vanilla games, the majority + // of trainers have 0 IVs; Elite Four members will have 31 IVs. + // https://github.com/pret/pokeemerald/blob/6c38837b266c0dd36ccdd04559199282daa7a8a0/include/data.h#L22 + if (pokeDataType == 0) { + // blocks of 8 bytes + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon thisPoke = new TrainerPokemon(); + thisPoke.IVs = ((readWord(pointerToPokes + poke * 8) & 0xFF) * 31) / 255; + thisPoke.level = readWord(pointerToPokes + poke * 8 + 2); + thisPoke.pokemon = pokesInternal[readWord(pointerToPokes + poke * 8 + 4)]; + tr.pokemon.add(thisPoke); + } + } else if (pokeDataType == 2) { + // blocks of 8 bytes + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon thisPoke = new TrainerPokemon(); + thisPoke.IVs = ((readWord(pointerToPokes + poke * 8) & 0xFF) * 31) / 255; + thisPoke.level = readWord(pointerToPokes + poke * 8 + 2); + thisPoke.pokemon = pokesInternal[readWord(pointerToPokes + poke * 8 + 4)]; + thisPoke.heldItem = readWord(pointerToPokes + poke * 8 + 6); + tr.pokemon.add(thisPoke); + } + } else if (pokeDataType == 1) { + // blocks of 16 bytes + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon thisPoke = new TrainerPokemon(); + thisPoke.IVs = ((readWord(pointerToPokes + poke * 16) & 0xFF) * 31) / 255; + thisPoke.level = readWord(pointerToPokes + poke * 16 + 2); + thisPoke.pokemon = pokesInternal[readWord(pointerToPokes + poke * 16 + 4)]; + for (int move = 0; move < 4; move++) { + thisPoke.moves[move] = readWord(pointerToPokes + poke * 16 + 6 + (move*2)); + } + tr.pokemon.add(thisPoke); + } + } else if (pokeDataType == 3) { + // blocks of 16 bytes + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon thisPoke = new TrainerPokemon(); + thisPoke.IVs = ((readWord(pointerToPokes + poke * 16) & 0xFF) * 31) / 255; + thisPoke.level = readWord(pointerToPokes + poke * 16 + 2); + thisPoke.pokemon = pokesInternal[readWord(pointerToPokes + poke * 16 + 4)]; + thisPoke.heldItem = readWord(pointerToPokes + poke * 16 + 6); + for (int move = 0; move < 4; move++) { + thisPoke.moves[move] = readWord(pointerToPokes + poke * 16 + 8 + (move*2)); + } + tr.pokemon.add(thisPoke); + } + } + theTrainers.add(tr); + } + + if (romEntry.romType == Gen3Constants.RomType_Em) { + int mossdeepStevenOffset = romEntry.getValue("MossdeepStevenTeamOffset"); + Trainer mossdeepSteven = new Trainer(); + mossdeepSteven.offset = mossdeepStevenOffset; + mossdeepSteven.index = amount; + mossdeepSteven.poketype = 1; // Custom moves, but no held items + + // This is literally how the game does it too, lol. Have to subtract one because the + // trainers internally are one-indexed, but then theTrainers is zero-indexed. + Trainer meteorFallsSteven = theTrainers.get(Gen3Constants.emMeteorFallsStevenIndex - 1); + mossdeepSteven.trainerclass = meteorFallsSteven.trainerclass; + mossdeepSteven.name = meteorFallsSteven.name; + mossdeepSteven.fullDisplayName = meteorFallsSteven.fullDisplayName; + + for (int i = 0; i < 3; i++) { + int currentOffset = mossdeepStevenOffset + (i * 20); + TrainerPokemon thisPoke = new TrainerPokemon(); + thisPoke.pokemon = pokesInternal[readWord(currentOffset)]; + thisPoke.IVs = rom[currentOffset + 2]; + thisPoke.level = rom[currentOffset + 3]; + for (int move = 0; move < 4; move++) { + thisPoke.moves[move] = readWord(currentOffset + 12 + (move * 2)); + } + mossdeepSteven.pokemon.add(thisPoke); + } + + theTrainers.add(mossdeepSteven); + } + + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + Gen3Constants.trainerTagsRS(theTrainers, romEntry.romType); + } else if (romEntry.romType == Gen3Constants.RomType_Em) { + Gen3Constants.trainerTagsE(theTrainers); + Gen3Constants.setMultiBattleStatusEm(theTrainers); + } else { + Gen3Constants.trainerTagsFRLG(theTrainers); + } + return theTrainers; + } + + @Override + public List getEvolutionItems() { + return Gen3Constants.evolutionItems; + } + + @Override + public List getXItems() { + return Gen3Constants.xItems; + } + + @Override + public List getMainPlaythroughTrainers() { + return new ArrayList<>(); // Not implemented + } + + @Override + public List getEliteFourTrainers(boolean isChallengeMode) { + return Arrays.stream(romEntry.arrayEntries.get("EliteFourIndices")).boxed().collect(Collectors.toList()); + } + + + @Override + public void setTrainers(List trainerData, boolean doubleBattleMode) { + int baseOffset = romEntry.getValue("TrainerData"); + int amount = romEntry.getValue("TrainerCount"); + int entryLen = romEntry.getValue("TrainerEntrySize"); + Iterator theTrainers = trainerData.iterator(); + int fso = romEntry.getValue("FreeSpace"); + + // Get current movesets in case we need to reset them for certain + // trainer mons. + Map> movesets = this.getMovesLearnt(); + + for (int i = 1; i < amount; i++) { + int trOffset = baseOffset + i * entryLen; + Trainer tr = theTrainers.next(); + // Do we need to repoint this trainer's data? + int oldPokeType = rom[trOffset] & 0xFF; + int oldPokeCount = rom[trOffset + (entryLen - 8)] & 0xFF; + int newPokeCount = tr.pokemon.size(); + int newDataSize = newPokeCount * ((tr.poketype & 1) == 1 ? 16 : 8); + int oldDataSize = oldPokeCount * ((oldPokeType & 1) == 1 ? 16 : 8); + + // write out new data first... + rom[trOffset] = (byte) tr.poketype; + rom[trOffset + (entryLen - 8)] = (byte) newPokeCount; + if (doubleBattleMode) { + if (!tr.skipImportant()) { + rom[trOffset + (entryLen - 16)] = 0x01; + } + } + + // now, do we need to repoint? + int pointerToPokes; + if (newDataSize > oldDataSize) { + int writeSpace = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, newDataSize, fso, true); + if (writeSpace < fso) { + throw new RandomizerIOException("ROM is full"); + } + writePointer(trOffset + (entryLen - 4), writeSpace); + pointerToPokes = writeSpace; + } else { + pointerToPokes = readPointer(trOffset + (entryLen - 4)); + } + + Iterator pokes = tr.pokemon.iterator(); + + // Write out Pokemon data! + if (tr.pokemonHaveCustomMoves()) { + // custom moves, blocks of 16 bytes + for (int poke = 0; poke < newPokeCount; poke++) { + TrainerPokemon tp = pokes.next(); + // Add 1 to offset integer division truncation + writeWord(pointerToPokes + poke * 16, Math.min(255, 1 + (tp.IVs * 255) / 31)); + writeWord(pointerToPokes + poke * 16 + 2, tp.level); + writeWord(pointerToPokes + poke * 16 + 4, pokedexToInternal[tp.pokemon.number]); + int movesStart; + if (tr.pokemonHaveItems()) { + writeWord(pointerToPokes + poke * 16 + 6, tp.heldItem); + movesStart = 8; + } else { + movesStart = 6; + writeWord(pointerToPokes + poke * 16 + 14, 0); + } + if (tp.resetMoves) { + int[] pokeMoves = RomFunctions.getMovesAtLevel(tp.pokemon.number, movesets, tp.level); + for (int m = 0; m < 4; m++) { + writeWord(pointerToPokes + poke * 16 + movesStart + m * 2, pokeMoves[m]); + } + } else { + writeWord(pointerToPokes + poke * 16 + movesStart, tp.moves[0]); + writeWord(pointerToPokes + poke * 16 + movesStart + 2, tp.moves[1]); + writeWord(pointerToPokes + poke * 16 + movesStart + 4, tp.moves[2]); + writeWord(pointerToPokes + poke * 16 + movesStart + 6, tp.moves[3]); + } + } + } else { + // no moves, blocks of 8 bytes + for (int poke = 0; poke < newPokeCount; poke++) { + TrainerPokemon tp = pokes.next(); + writeWord(pointerToPokes + poke * 8, Math.min(255, 1 + (tp.IVs * 255) / 31)); + writeWord(pointerToPokes + poke * 8 + 2, tp.level); + writeWord(pointerToPokes + poke * 8 + 4, pokedexToInternal[tp.pokemon.number]); + if (tr.pokemonHaveItems()) { + writeWord(pointerToPokes + poke * 8 + 6, tp.heldItem); + } else { + writeWord(pointerToPokes + poke * 8 + 6, 0); + } + } + } + } + + if (romEntry.romType == Gen3Constants.RomType_Em) { + int mossdeepStevenOffset = romEntry.getValue("MossdeepStevenTeamOffset"); + Trainer mossdeepSteven = trainerData.get(amount - 1); + + for (int i = 0; i < 3; i++) { + int currentOffset = mossdeepStevenOffset + (i * 20); + TrainerPokemon tp = mossdeepSteven.pokemon.get(i); + writeWord(currentOffset, pokedexToInternal[tp.pokemon.number]); + rom[currentOffset + 2] = (byte)tp.IVs; + rom[currentOffset + 3] = (byte)tp.level; + for (int move = 0; move < 4; move++) { + writeWord(currentOffset + 12 + (move * 2), tp.moves[move]); + } + } + } + } + + private void writeWildArea(int offset, int numOfEntries, EncounterSet encounters) { + // Grab the *real* pointer to data + int dataOffset = readPointer(offset + 4); + // Write the entries + for (int i = 0; i < numOfEntries; i++) { + Encounter enc = encounters.encounters.get(i); + // min, max, species, species + int levels = enc.level | (enc.maxLevel << 8); + writeWord(dataOffset + i * 4, levels); + writeWord(dataOffset + i * 4 + 2, pokedexToInternal[enc.pokemon.number]); + } + } + + @Override + public List getPokemon() { + return pokemonList; + } + + @Override + public List getPokemonInclFormes() { + return pokemonList; // No alt formes for now, should include Deoxys formes in the future + } + + @Override + public List getAltFormes() { + return new ArrayList<>(); + } + + @Override + public List getMegaEvolutions() { + return new ArrayList<>(); + } + + @Override + public Pokemon getAltFormeOfPokemon(Pokemon pk, int forme) { + return pk; + } + + @Override + public List getIrregularFormes() { + return new ArrayList<>(); + } + + @Override + public boolean hasFunctionalFormes() { + return false; + } + + @Override + public Map> getMovesLearnt() { + Map> movesets = new TreeMap<>(); + int baseOffset = romEntry.getValue("PokemonMovesets"); + for (int i = 1; i <= numRealPokemon; i++) { + Pokemon pkmn = pokemonList.get(i); + int offsToPtr = baseOffset + (pokedexToInternal[pkmn.number]) * 4; + int moveDataLoc = readPointer(offsToPtr); + List moves = new ArrayList<>(); + if (jamboMovesetHack) { + while ((rom[moveDataLoc] & 0xFF) != 0x00 || (rom[moveDataLoc + 1] & 0xFF) != 0x00 + || (rom[moveDataLoc + 2] & 0xFF) != 0xFF) { + MoveLearnt ml = new MoveLearnt(); + ml.level = rom[moveDataLoc + 2] & 0xFF; + ml.move = readWord(moveDataLoc); + moves.add(ml); + moveDataLoc += 3; + } + } else { + while ((rom[moveDataLoc] & 0xFF) != 0xFF || (rom[moveDataLoc + 1] & 0xFF) != 0xFF) { + int move = (rom[moveDataLoc] & 0xFF); + int level = (rom[moveDataLoc + 1] & 0xFE) >> 1; + if ((rom[moveDataLoc + 1] & 0x01) == 0x01) { + move += 0x100; + } + MoveLearnt ml = new MoveLearnt(); + ml.level = level; + ml.move = move; + moves.add(ml); + moveDataLoc += 2; + } + } + movesets.put(pkmn.number, moves); + } + return movesets; + } + + @Override + public void setMovesLearnt(Map> movesets) { + int baseOffset = romEntry.getValue("PokemonMovesets"); + int fso = romEntry.getValue("FreeSpace"); + for (int i = 1; i <= numRealPokemon; i++) { + Pokemon pkmn = pokemonList.get(i); + int offsToPtr = baseOffset + (pokedexToInternal[pkmn.number]) * 4; + int moveDataLoc = readPointer(offsToPtr); + List moves = movesets.get(pkmn.number); + int newMoveCount = moves.size(); + int mloc = moveDataLoc; + int entrySize; + if (jamboMovesetHack) { + while ((rom[mloc] & 0xFF) != 0x00 || (rom[mloc + 1] & 0xFF) != 0x00 || (rom[mloc + 2] & 0xFF) != 0xFF) { + mloc += 3; + } + entrySize = 3; + } else { + while ((rom[mloc] & 0xFF) != 0xFF || (rom[mloc + 1] & 0xFF) != 0xFF) { + mloc += 2; + } + entrySize = 2; + } + int currentMoveCount = (mloc - moveDataLoc) / entrySize; + + if (newMoveCount > currentMoveCount) { + // Repoint for more space + int newBytesNeeded = newMoveCount * entrySize + entrySize * 2; + int writeSpace = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, newBytesNeeded, fso); + if (writeSpace < fso) { + throw new RandomizerIOException("ROM is full"); + } + writePointer(offsToPtr, writeSpace); + moveDataLoc = writeSpace; + } + + // Write new moveset now that space is ensured. + for (MoveLearnt ml : moves) { + moveDataLoc += writeMLToOffset(moveDataLoc, ml); + } + + // If move count changed, new terminator is required + // In the repoint enough space was reserved to add some padding to + // make sure the terminator isn't detected as free space. + // If no repoint, the padding goes over the old moves/terminator. + if (newMoveCount != currentMoveCount) { + if (jamboMovesetHack) { + rom[moveDataLoc] = 0x00; + rom[moveDataLoc + 1] = 0x00; + rom[moveDataLoc + 2] = (byte) 0xFF; + rom[moveDataLoc + 3] = 0x00; + rom[moveDataLoc + 4] = 0x00; + rom[moveDataLoc + 5] = 0x00; + } else { + rom[moveDataLoc] = (byte) 0xFF; + rom[moveDataLoc + 1] = (byte) 0xFF; + rom[moveDataLoc + 2] = 0x00; + rom[moveDataLoc + 3] = 0x00; + } + } + + } + + } + + private int writeMLToOffset(int offset, MoveLearnt ml) { + if (jamboMovesetHack) { + writeWord(offset, ml.move); + rom[offset + 2] = (byte) ml.level; + return 3; + } else { + rom[offset] = (byte) (ml.move & 0xFF); + int levelPart = (ml.level << 1) & 0xFE; + if (ml.move > 255) { + levelPart++; + } + rom[offset + 1] = (byte) levelPart; + return 2; + } + } + + @Override + public Map> getEggMoves() { + Map> eggMoves = new TreeMap<>(); + int baseOffset = romEntry.getValue("EggMoves"); + int currentOffset = baseOffset; + int currentSpecies = 0; + List currentMoves = new ArrayList<>(); + int val = FileFunctions.read2ByteInt(rom, currentOffset); + + // Check egg_moves.h in the Gen 3 decomps for more info on how this algorithm works. + while (val != 0xFFFF) { + if (val > 20000) { + int species = val - 20000; + if (currentMoves.size() > 0) { + eggMoves.put(internalToPokedex[currentSpecies], currentMoves); + } + currentSpecies = species; + currentMoves = new ArrayList<>(); + } else { + currentMoves.add(val); + } + currentOffset += 2; + val = FileFunctions.read2ByteInt(rom, currentOffset); + } + + // Need to make sure the last entry gets recorded too + if (currentMoves.size() > 0) { + eggMoves.put(internalToPokedex[currentSpecies], currentMoves); + } + return eggMoves; + } + + @Override + public void setEggMoves(Map> eggMoves) { + int baseOffset = romEntry.getValue("EggMoves"); + int currentOffset = baseOffset; + for (int species : eggMoves.keySet()) { + FileFunctions.write2ByteInt(rom, currentOffset, pokedexToInternal[species] + 20000); + currentOffset += 2; + for (int move : eggMoves.get(species)) { + FileFunctions.write2ByteInt(rom, currentOffset, move); + currentOffset += 2; + } + } + } + + private static class StaticPokemon { + private int[] speciesOffsets; + private int[] levelOffsets; + + public StaticPokemon() { + this.speciesOffsets = new int[0]; + this.levelOffsets = new int[0]; + } + + public Pokemon getPokemon(Gen3RomHandler parent) { + return parent.pokesInternal[parent.readWord(speciesOffsets[0])]; + } + + public void setPokemon(Gen3RomHandler parent, Pokemon pkmn) { + int value = parent.pokedexToInternal[pkmn.number]; + for (int offset : speciesOffsets) { + parent.writeWord(offset, value); + } + } + + public int getLevel(byte[] rom, int i) { + if (levelOffsets.length <= i) { + return 1; + } + return rom[levelOffsets[i]]; + } + + public void setLevel(byte[] rom, int level, int i) { + if (levelOffsets.length > i) { // Might not have a level entry e.g., it's an egg + rom[levelOffsets[i]] = (byte) level; + } + } + } + + @Override + public List getStaticPokemon() { + List statics = new ArrayList<>(); + List staticsHere = romEntry.staticPokemon; + int[] staticEggOffsets = new int[0]; + if (romEntry.arrayEntries.containsKey("StaticEggPokemonOffsets")) { + staticEggOffsets = romEntry.arrayEntries.get("StaticEggPokemonOffsets"); + } + for (int i = 0; i < staticsHere.size(); i++) { + int currentOffset = i; + StaticPokemon staticPK = staticsHere.get(i); + StaticEncounter se = new StaticEncounter(); + se.pkmn = staticPK.getPokemon(this); + se.level = staticPK.getLevel(rom, 0); + se.isEgg = Arrays.stream(staticEggOffsets).anyMatch(x-> x == currentOffset); + statics.add(se); + } + + if (romEntry.codeTweaks.get("StaticFirstBattleTweak") != null) { + // Read in and randomize the static starting Poochyena/Zigzagoon fight in RSE + int startingSpeciesOffset = romEntry.getValue("StaticFirstBattleSpeciesOffset"); + int species = readWord(startingSpeciesOffset); + if (species == 0xFFFF) { + // Patch hasn't been applied, so apply it first + try { + FileFunctions.applyPatch(rom, romEntry.codeTweaks.get("StaticFirstBattleTweak")); + species = readWord(startingSpeciesOffset); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + Pokemon pkmn = pokesInternal[species]; + int startingLevelOffset = romEntry.getValue("StaticFirstBattleLevelOffset"); + int level = rom[startingLevelOffset]; + StaticEncounter se = new StaticEncounter(); + se.pkmn = pkmn; + se.level = level; + statics.add(se); + } else if (romEntry.codeTweaks.get("GhostMarowakTweak") != null) { + // Read in and randomize the static Ghost Marowak fight in FRLG + int[] ghostMarowakOffsets = romEntry.arrayEntries.get("GhostMarowakSpeciesOffsets"); + int species = readWord(ghostMarowakOffsets[0]); + if (species == 0xFFFF) { + // Patch hasn't been applied, so apply it first + try { + FileFunctions.applyPatch(rom, romEntry.codeTweaks.get("GhostMarowakTweak")); + species = readWord(ghostMarowakOffsets[0]); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + Pokemon pkmn = pokesInternal[species]; + int[] startingLevelOffsets = romEntry.arrayEntries.get("GhostMarowakLevelOffsets"); + int level = rom[startingLevelOffsets[0]]; + StaticEncounter se = new StaticEncounter(); + se.pkmn = pkmn; + se.level = level; + statics.add(se); + } + + try { + getRoamers(statics); + } catch (Exception e) { + throw new RandomizerIOException(e); + } + + return statics; + } + + @Override + public boolean setStaticPokemon(List staticPokemon) { + // Support Deoxys/Mew gifts/catches in E/FR/LG + attemptObedienceEvolutionPatches(); + + List staticsHere = romEntry.staticPokemon; + int roamerSize = romEntry.roamingPokemon.size(); + if (romEntry.romType == Gen3Constants.RomType_Em) { + // Emerald roamers are set as linkedEncounters to their respective + // Southern Island statics and thus don't count. + roamerSize = 0; + } + int hardcodedStaticSize = 0; + if (romEntry.codeTweaks.get("StaticFirstBattleTweak") != null || romEntry.codeTweaks.get("GhostMarowakTweak") != null) { + hardcodedStaticSize = 1; + } + + if (staticPokemon.size() != staticsHere.size() + hardcodedStaticSize + roamerSize) { + return false; + } + + for (int i = 0; i < staticsHere.size(); i++) { + staticsHere.get(i).setPokemon(this, staticPokemon.get(i).pkmn); + staticsHere.get(i).setLevel(rom, staticPokemon.get(i).level, 0); + } + + if (romEntry.codeTweaks.get("StaticFirstBattleTweak") != null) { + StaticEncounter startingFirstBattle = staticPokemon.get(romEntry.getValue("StaticFirstBattleOffset")); + int startingSpeciesOffset = romEntry.getValue("StaticFirstBattleSpeciesOffset"); + writeWord(startingSpeciesOffset, pokedexToInternal[startingFirstBattle.pkmn.number]); + int startingLevelOffset = romEntry.getValue("StaticFirstBattleLevelOffset"); + rom[startingLevelOffset] = (byte) startingFirstBattle.level; + } else if (romEntry.codeTweaks.get("GhostMarowakTweak") != null) { + StaticEncounter ghostMarowak = staticPokemon.get(romEntry.getValue("GhostMarowakOffset")); + int[] ghostMarowakSpeciesOffsets = romEntry.arrayEntries.get("GhostMarowakSpeciesOffsets"); + for (int i = 0; i < ghostMarowakSpeciesOffsets.length; i++) { + writeWord(ghostMarowakSpeciesOffsets[i], pokedexToInternal[ghostMarowak.pkmn.number]); + } + int[] ghostMarowakLevelOffsets = romEntry.arrayEntries.get("GhostMarowakLevelOffsets"); + for (int i = 0; i < ghostMarowakLevelOffsets.length; i++) { + rom[ghostMarowakLevelOffsets[i]] = (byte) ghostMarowak.level; + } + + // The code for creating Ghost Marowak tries to ensure the Pokemon is female. If the Pokemon + // cannot be female (because they are always male or an indeterminate gender), then the game + // will infinite loop trying and failing to make the Pokemon female. For Pokemon that cannot + // be female, change the specified gender to something that actually works. + int ghostMarowakGenderOffset = romEntry.getValue("GhostMarowakGenderOffset"); + if (ghostMarowak.pkmn.genderRatio == 0 || ghostMarowak.pkmn.genderRatio == 0xFF) { + // 0x00 is 100% male, and 0xFF is indeterminate gender + rom[ghostMarowakGenderOffset] = (byte) ghostMarowak.pkmn.genderRatio; + } + } + + setRoamers(staticPokemon); + return true; + } + + private void getRoamers(List statics) throws IOException { + if (romEntry.romType == Gen3Constants.RomType_Ruby) { + int firstSpecies = readWord(rom, romEntry.roamingPokemon.get(0).speciesOffsets[0]); + if (firstSpecies == 0) { + // Before applying the patch, the first species offset will be pointing to + // the lower bytes of 0x2000000, so when it reads a word, it will be 0. + applyRubyRoamerPatch(); + } + StaticPokemon roamer = romEntry.roamingPokemon.get(0); + StaticEncounter se = new StaticEncounter(); + se.pkmn = roamer.getPokemon(this); + se.level = roamer.getLevel(rom, 0); + statics.add(se); + } else if (romEntry.romType == Gen3Constants.RomType_Sapp) { + StaticPokemon roamer = romEntry.roamingPokemon.get(0); + StaticEncounter se = new StaticEncounter(); + se.pkmn = roamer.getPokemon(this); + se.level = roamer.getLevel(rom, 0); + statics.add(se); + } else if (romEntry.romType == Gen3Constants.RomType_FRLG && romEntry.codeTweaks.get("RoamingPokemonTweak") != null) { + int firstSpecies = readWord(rom, romEntry.roamingPokemon.get(0).speciesOffsets[0]); + if (firstSpecies == 0xFFFF) { + // This means that the IPS patch hasn't been applied yet, since the first species + // ID location is free space. + FileFunctions.applyPatch(rom, romEntry.codeTweaks.get("RoamingPokemonTweak")); + } + for (int i = 0; i < romEntry.roamingPokemon.size(); i++) { + StaticPokemon roamer = romEntry.roamingPokemon.get(i); + StaticEncounter se = new StaticEncounter(); + se.pkmn = roamer.getPokemon(this); + se.level = roamer.getLevel(rom, 0); + statics.add(se); + } + } else if (romEntry.romType == Gen3Constants.RomType_Em) { + int firstSpecies = readWord(rom, romEntry.roamingPokemon.get(0).speciesOffsets[0]); + if (firstSpecies >= pokesInternal.length) { + // Before applying the patch, the first species offset is a pointer with a huge value. + // Thus, this check is a good indicator that the patch needs to be applied. + applyEmeraldRoamerPatch(); + } + int[] southernIslandOffsets = romEntry.arrayEntries.get("StaticSouthernIslandOffsets"); + for (int i = 0; i < romEntry.roamingPokemon.size(); i++) { + StaticPokemon roamer = romEntry.roamingPokemon.get(i); + StaticEncounter se = new StaticEncounter(); + se.pkmn = roamer.getPokemon(this); + se.level = roamer.getLevel(rom, 0); + + // Link each roamer to their respective Southern Island static encounter so that + // they randomize to the same species. + StaticEncounter southernIslandEncounter = statics.get(southernIslandOffsets[i]); + southernIslandEncounter.linkedEncounters.add(se); + } + } + } + + private void setRoamers(List statics) { + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + StaticEncounter roamerEncounter = statics.get(statics.size() - 1); + StaticPokemon roamer = romEntry.roamingPokemon.get(0); + roamer.setPokemon(this, roamerEncounter.pkmn); + for (int i = 0; i < roamer.levelOffsets.length; i++) { + roamer.setLevel(rom, roamerEncounter.level, i); + } + } else if (romEntry.romType == Gen3Constants.RomType_FRLG && romEntry.codeTweaks.get("RoamingPokemonTweak") != null) { + for (int i = 0; i < romEntry.roamingPokemon.size(); i++) { + int offsetInStaticList = statics.size() - 3 + i; + StaticEncounter roamerEncounter = statics.get(offsetInStaticList); + StaticPokemon roamer = romEntry.roamingPokemon.get(i); + roamer.setPokemon(this, roamerEncounter.pkmn); + for (int j = 0; j < roamer.levelOffsets.length; j++) { + roamer.setLevel(rom, roamerEncounter.level, j); + } + } + } else if (romEntry.romType == Gen3Constants.RomType_Em) { + int[] southernIslandOffsets = romEntry.arrayEntries.get("StaticSouthernIslandOffsets"); + for (int i = 0; i < romEntry.roamingPokemon.size(); i++) { + StaticEncounter southernIslandEncounter = statics.get(southernIslandOffsets[i]); + StaticEncounter roamerEncounter = southernIslandEncounter.linkedEncounters.get(0); + StaticPokemon roamer = romEntry.roamingPokemon.get(i); + roamer.setPokemon(this, roamerEncounter.pkmn); + for (int j = 0; j < roamer.levelOffsets.length; j++) { + roamer.setLevel(rom, roamerEncounter.level, j); + } + } + } + } + + private void applyRubyRoamerPatch() { + int offset = romEntry.getValue("FindMapsWithMonFunctionStartOffset"); + + // The constant 0x2000000 is actually in the function twice, so we'll replace the first instance + // with Latios's ID. First, change the "ldr r2, [pc, #0x68]" near the start of the function to + // "ldr r2, [pc, #0x15C]" so it points to the second usage of 0x2000000 + rom[offset + 22] = 0x57; + + // In the space formerly occupied by the first 0x2000000, write Latios's ID + FileFunctions.writeFullInt(rom, offset + 128, pokedexToInternal[Species.latios]); + + // Where the original function computes Latios's ID by setting r0 to 0xCC << 1, just pc-relative + // load our constant. We have four bytes of space to play with, and we need to make sure the offset + // from the pc is 4-byte aligned; we need to nop for alignment and then perform the load. + rom[offset + 12] = 0x00; + rom[offset + 13] = 0x00; + rom[offset + 14] = 0x1C; + rom[offset + 15] = 0x48; + + offset = romEntry.getValue("CreateInitialRoamerMonFunctionStartOffset"); + + // At the very end of the function, the game pops the lr from the stack and stores it in r0, then + // it does "bx r0" to jump back to the caller, and then it has two bytes of padding afterwards. For + // some reason, Ruby very rarely does "pop { pc }" even though that seemingly works fine. By doing + // that, we only need one instruction to return to the caller, giving us four bytes to write + // Latios's species ID. + rom[offset + 182] = 0x00; + rom[offset + 183] = (byte) 0xBD; + FileFunctions.writeFullInt(rom, offset + 184, pokedexToInternal[Species.latios]); + + // Now write a pc-relative load to this new species ID constant over the original move and lsl. Similar + // to before, we need to write a nop first for alignment, then pc-relative load into r6. + rom[offset + 10] = 0x00; + rom[offset + 11] = 0x00; + rom[offset + 12] = 0x2A; + rom[offset + 13] = 0x4E; + } + + private void applyEmeraldRoamerPatch() { + int offset = romEntry.getValue("CreateInitialRoamerMonFunctionStartOffset"); + + // Latias's species ID is already a pc-relative loaded constant, but Latios's isn't. We need to make + // some room for it; the constant 0x03005D8C is actually in the function twice, so we'll replace the first + // instance with Latios's ID. First, change the "ldr r0, [pc, #0xC]" at the start of the function to + // "ldr r0, [pc, #0x104]", so it points to the second usage of 0x03005D8C + rom[offset + 14] = 0x41; + + // In the space formerly occupied by the first 0x03005D8C, write Latios's ID + FileFunctions.writeFullInt(rom, offset + 28, pokedexToInternal[Species.latios]); + + // In the original function, we "lsl r0, r0, #0x10" then compare r0 to 0. The thing is, this left + // shift doesn't actually matter, because 0 << 0x10 = 0, and [non-zero] << 0x10 = [non-zero]. + // Let's move the compare up to take its place and then load Latios's ID into r3 for use in another + // branch later. + rom[offset + 8] = 0x00; + rom[offset + 9] = 0x28; + rom[offset + 10] = 0x04; + rom[offset + 11] = 0x4B; + + // Lastly, in the branch that normally does r2 = 0xCC << 0x1 to compute Latios's ID, just mov r3 + // into r2, since it was loaded with his ID with the above code. + rom[offset + 48] = 0x1A; + rom[offset + 49] = 0x46; + rom[offset + 50] = 0x00; + rom[offset + 51] = 0x00; + } + + @Override + public List getTMMoves() { + List tms = new ArrayList<>(); + int offset = romEntry.getValue("TmMoves"); + for (int i = 1; i <= Gen3Constants.tmCount; i++) { + tms.add(readWord(offset + (i - 1) * 2)); + } + return tms; + } + + @Override + public List getHMMoves() { + return Gen3Constants.hmMoves; + } + + @Override + public void setTMMoves(List moveIndexes) { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + int offset = romEntry.getValue("TmMoves"); + for (int i = 1; i <= Gen3Constants.tmCount; i++) { + writeWord(offset + (i - 1) * 2, moveIndexes.get(i - 1)); + } + int otherOffset = romEntry.getValue("TmMovesDuplicate"); + if (otherOffset > 0) { + // Emerald/FR/LG have *two* TM tables + System.arraycopy(rom, offset, rom, otherOffset, Gen3Constants.tmCount * 2); + } + + int iiOffset = romEntry.getValue("ItemImages"); + if (iiOffset > 0) { + int[] pals = romEntry.arrayEntries.get("TmPals"); + // Update the item image palettes + // Gen3 TMs are 289-338 + for (int i = 0; i < 50; i++) { + Move mv = moves[moveIndexes.get(i)]; + int typeID = Gen3Constants.typeToByte(mv.type); + writePointer(iiOffset + (Gen3Constants.tmItemOffset + i) * 8 + 4, pals[typeID]); + } + } + + int fsOffset = romEntry.getValue("FreeSpace"); + + // Item descriptions + if (romEntry.getValue("MoveDescriptions") > 0) { + // JP blocked for now - uses different item structure anyway + int idOffset = romEntry.getValue("ItemData"); + int mdOffset = romEntry.getValue("MoveDescriptions"); + int entrySize = romEntry.getValue("ItemEntrySize"); + int limitPerLine = (romEntry.romType == Gen3Constants.RomType_FRLG) ? Gen3Constants.frlgItemDescCharsPerLine + : Gen3Constants.rseItemDescCharsPerLine; + for (int i = 0; i < Gen3Constants.tmCount; i++) { + int itemBaseOffset = idOffset + (i + Gen3Constants.tmItemOffset) * entrySize; + int moveBaseOffset = mdOffset + (moveIndexes.get(i) - 1) * 4; + int moveTextPointer = readPointer(moveBaseOffset); + String moveDesc = readVariableLengthString(moveTextPointer); + String newItemDesc = RomFunctions.rewriteDescriptionForNewLineSize(moveDesc, "\\n", limitPerLine, ssd); + // Find freespace + int fsBytesNeeded = translateString(newItemDesc).length + 1; + int newItemDescOffset = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, fsBytesNeeded, + fsOffset); + if (newItemDescOffset < fsOffset) { + String nl = System.getProperty("line.separator"); + log("Couldn't insert new item description." + nl); + return; + } + writeVariableLengthString(newItemDesc, newItemDescOffset); + writePointer(itemBaseOffset + Gen3Constants.itemDataDescriptionOffset, newItemDescOffset); + } + } + + // TM Text? + for (TMOrMTTextEntry tte : romEntry.tmmtTexts) { + if (tte.actualOffset > 0 && !tte.isMoveTutor) { + // create the new TM text + int oldPointer = readPointer(tte.actualOffset); + if (oldPointer < 0 || oldPointer >= rom.length) { + String nl = System.getProperty("line.separator"); + log("Couldn't insert new TM text. Skipping remaining TM text updates." + nl); + return; + } + String moveName = this.moves[moveIndexes.get(tte.number - 1)].name; + // temporarily use underscores to stop the move name being split + String tmpMoveName = moveName.replace(' ', '_'); + String unformatted = tte.template.replace("[move]", tmpMoveName); + String newText = RomFunctions.formatTextWithReplacements(unformatted, null, "\\n", "\\l", "\\p", + Gen3Constants.regularTextboxCharsPerLine, ssd); + // get rid of the underscores + newText = newText.replace(tmpMoveName, moveName); + // insert the new text into free space + int fsBytesNeeded = translateString(newText).length + 1; + int newOffset = RomFunctions.freeSpaceFinder(rom, (byte) 0xFF, fsBytesNeeded, fsOffset); + if (newOffset < fsOffset) { + String nl = System.getProperty("line.separator"); + log("Couldn't insert new TM text." + nl); + return; + } + writeVariableLengthString(newText, newOffset); + // search for copies of the pointer: + // make a needle of the pointer + byte[] searchNeedle = new byte[4]; + System.arraycopy(rom, tte.actualOffset, searchNeedle, 0, 4); + // find copies within 500 bytes either way of actualOffset + int minOffset = Math.max(0, tte.actualOffset - Gen3Constants.pointerSearchRadius); + int maxOffset = Math.min(rom.length, tte.actualOffset + Gen3Constants.pointerSearchRadius); + List pointerLocs = RomFunctions.search(rom, minOffset, maxOffset, searchNeedle); + for (int pointerLoc : pointerLocs) { + // write the new pointer + writePointer(pointerLoc, newOffset); + } + } + } + } + + private RomFunctions.StringSizeDeterminer ssd = encodedText -> translateString(encodedText).length; + + @Override + public int getTMCount() { + return Gen3Constants.tmCount; + } + + @Override + public int getHMCount() { + return Gen3Constants.hmCount; + } + + @Override + public Map getTMHMCompatibility() { + Map compat = new TreeMap<>(); + int offset = romEntry.getValue("PokemonTMHMCompat"); + for (int i = 1; i <= numRealPokemon; i++) { + Pokemon pkmn = pokemonList.get(i); + int compatOffset = offset + (pokedexToInternal[pkmn.number]) * 8; + boolean[] flags = new boolean[Gen3Constants.tmCount + Gen3Constants.hmCount + 1]; + for (int j = 0; j < 8; j++) { + readByteIntoFlags(flags, j * 8 + 1, compatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map compatData) { + int offset = romEntry.getValue("PokemonTMHMCompat"); + for (Map.Entry compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int compatOffset = offset + (pokedexToInternal[pkmn.number]) * 8; + for (int j = 0; j < 8; j++) { + rom[compatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public boolean hasMoveTutors() { + return (romEntry.romType == Gen3Constants.RomType_Em || romEntry.romType == Gen3Constants.RomType_FRLG); + } + + @Override + public List getMoveTutorMoves() { + if (!hasMoveTutors()) { + return new ArrayList<>(); + } + List mts = new ArrayList<>(); + int moveCount = romEntry.getValue("MoveTutorMoves"); + int offset = romEntry.getValue("MoveTutorData"); + for (int i = 0; i < moveCount; i++) { + mts.add(readWord(offset + i * 2)); + } + return mts; + } + + @Override + public void setMoveTutorMoves(List moves) { + if (!hasMoveTutors()) { + return; + } + int moveCount = romEntry.getValue("MoveTutorMoves"); + int offset = romEntry.getValue("MoveTutorData"); + if (moveCount != moves.size()) { + return; + } + for (int i = 0; i < moveCount; i++) { + writeWord(offset + i * 2, moves.get(i)); + } + int fsOffset = romEntry.getValue("FreeSpace"); + + // Move Tutor Text? + for (TMOrMTTextEntry tte : romEntry.tmmtTexts) { + if (tte.actualOffset > 0 && tte.isMoveTutor) { + // create the new MT text + int oldPointer = readPointer(tte.actualOffset); + if (oldPointer < 0 || oldPointer >= rom.length) { + throw new RandomizationException( + "Move Tutor Text update failed: couldn't read a move tutor text pointer."); + } + String moveName = this.moves[moves.get(tte.number)].name; + // temporarily use underscores to stop the move name being split + String tmpMoveName = moveName.replace(' ', '_'); + String unformatted = tte.template.replace("[move]", tmpMoveName); + String newText = RomFunctions.formatTextWithReplacements(unformatted, null, "\\n", "\\l", "\\p", + Gen3Constants.regularTextboxCharsPerLine, ssd); + // get rid of the underscores + newText = newText.replace(tmpMoveName, moveName); + // insert the new text into free space + int fsBytesNeeded = translateString(newText).length + 1; + int newOffset = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, fsBytesNeeded, fsOffset); + if (newOffset < fsOffset) { + String nl = System.getProperty("line.separator"); + log("Couldn't insert new Move Tutor text." + nl); + return; + } + writeVariableLengthString(newText, newOffset); + // search for copies of the pointer: + // make a needle of the pointer + byte[] searchNeedle = new byte[4]; + System.arraycopy(rom, tte.actualOffset, searchNeedle, 0, 4); + // find copies within 500 bytes either way of actualOffset + int minOffset = Math.max(0, tte.actualOffset - Gen3Constants.pointerSearchRadius); + int maxOffset = Math.min(rom.length, tte.actualOffset + Gen3Constants.pointerSearchRadius); + List pointerLocs = RomFunctions.search(rom, minOffset, maxOffset, searchNeedle); + for (int pointerLoc : pointerLocs) { + // write the new pointer + writePointer(pointerLoc, newOffset); + } + } + } + } + + @Override + public Map getMoveTutorCompatibility() { + if (!hasMoveTutors()) { + return new TreeMap<>(); + } + Map compat = new TreeMap<>(); + int moveCount = romEntry.getValue("MoveTutorMoves"); + int offset = romEntry.getValue("MoveTutorCompatibility"); + int bytesRequired = ((moveCount + 7) & ~7) / 8; + for (int i = 1; i <= numRealPokemon; i++) { + Pokemon pkmn = pokemonList.get(i); + int compatOffset = offset + pokedexToInternal[pkmn.number] * bytesRequired; + boolean[] flags = new boolean[moveCount + 1]; + for (int j = 0; j < bytesRequired; j++) { + readByteIntoFlags(flags, j * 8 + 1, compatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setMoveTutorCompatibility(Map compatData) { + if (!hasMoveTutors()) { + return; + } + int moveCount = romEntry.getValue("MoveTutorMoves"); + int offset = romEntry.getValue("MoveTutorCompatibility"); + int bytesRequired = ((moveCount + 7) & ~7) / 8; + for (Map.Entry compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int compatOffset = offset + pokedexToInternal[pkmn.number] * bytesRequired; + for (int j = 0; j < bytesRequired; j++) { + rom[compatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public String getROMName() { + return romEntry.name; + } + + @Override + public String getROMCode() { + return romEntry.romCode; + } + + @Override + public String getSupportLevel() { + return (romEntry.getValue("StaticPokemonSupport") > 0) ? "Complete" : "No Static Pokemon"; + } + + // For dynamic offsets later + private int find(String hexString) { + return find(rom, hexString); + } + + private static int find(byte[] haystack, String hexString) { + if (hexString.length() % 2 != 0) { + return -3; // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + List found = RomFunctions.search(haystack, searchFor); + if (found.size() == 0) { + return -1; // not found + } else if (found.size() > 1) { + return -2; // not unique + } else { + return found.get(0); + } + } + + private List findMultiple(String hexString) { + return findMultiple(rom, hexString); + } + + private static List findMultiple(byte[] haystack, String hexString) { + if (hexString.length() % 2 != 0) { + return new ArrayList<>(); // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + return RomFunctions.search(haystack, searchFor); + } + + private void writeHexString(String hexString, int offset) { + if (hexString.length() % 2 != 0) { + return; // error + } + for (int i = 0; i < hexString.length() / 2; i++) { + rom[offset + i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + } + + private void attemptObedienceEvolutionPatches() { + if (havePatchedObedience) { + return; + } + + havePatchedObedience = true; + // This routine *appears* to only exist in E/FR/LG... + // Look for the deoxys part which is + // MOVS R1, 0x19A + // CMP R0, R1 + // BEQ + // Hex is CD214900 8842 0FD0 + int deoxysObOffset = find(Gen3Constants.deoxysObeyCode); + if (deoxysObOffset > 0) { + // We found the deoxys check... + // Replacing it with MOVS R1, 0x0 would work fine. + // This would make it so species 0x0 (glitch only) would disobey. + // But MOVS R1, 0x0 (the version I know) is 2-byte + // So we just use it twice... + // the equivalent of nop'ing the second time. + rom[deoxysObOffset] = 0x00; + rom[deoxysObOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + rom[deoxysObOffset + 2] = 0x00; + rom[deoxysObOffset + 3] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + // Look for the mew check too... it's 0x16 ahead + if (readWord(deoxysObOffset + Gen3Constants.mewObeyOffsetFromDeoxysObey) == (((Gen3Constants.gbaCmpRxOpcode | Gen3Constants.gbaR0) << 8) | (Species.mew))) { + // Bingo, thats CMP R0, 0x97 + // change to CMP R0, 0x0 + writeWord(deoxysObOffset + Gen3Constants.mewObeyOffsetFromDeoxysObey, + (((Gen3Constants.gbaCmpRxOpcode | Gen3Constants.gbaR0) << 8) | (0))); + } + } + + // Look for evolutions too + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + int evoJumpOffset = find(Gen3Constants.levelEvoKantoDexCheckCode); + if (evoJumpOffset > 0) { + // This currently compares species to 0x97 and then allows + // evolution if it's <= that. + // Allow it regardless by using an unconditional jump instead + writeWord(evoJumpOffset, Gen3Constants.gbaNopOpcode); + writeWord(evoJumpOffset + 2, + ((Gen3Constants.gbaUnconditionalJumpOpcode << 8) | (Gen3Constants.levelEvoKantoDexJumpAmount))); + } + + int stoneJumpOffset = find(Gen3Constants.stoneEvoKantoDexCheckCode); + if (stoneJumpOffset > 0) { + // same as the above, but for stone evos + writeWord(stoneJumpOffset, Gen3Constants.gbaNopOpcode); + writeWord(stoneJumpOffset + 2, + ((Gen3Constants.gbaUnconditionalJumpOpcode << 8) | (Gen3Constants.stoneEvoKantoDexJumpAmount))); + } + } + } + + private void patchForNationalDex() { + log("--Patching for National Dex at Start of Game--"); + String nl = System.getProperty("line.separator"); + int fso = romEntry.getValue("FreeSpace"); + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + // Find the original pokedex script + int pkDexOffset = find(Gen3Constants.rsPokedexScriptIdentifier); + if (pkDexOffset < 0) { + log("Patch unsuccessful." + nl); + return; + } + int textPointer = readPointer(pkDexOffset - 4); + int realScriptLocation = pkDexOffset - 8; + int pointerLocToScript = find(pointerToHexString(realScriptLocation)); + if (pointerLocToScript < 0) { + log("Patch unsuccessful." + nl); + return; + } + // Find free space for our new routine + int writeSpace = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, 44, fso); + if (writeSpace < fso) { + log("Patch unsuccessful." + nl); + // Somehow this ROM is full + return; + } + writePointer(pointerLocToScript, writeSpace); + writeHexString(Gen3Constants.rsNatDexScriptPart1, writeSpace); + writePointer(writeSpace + 4, textPointer); + writeHexString(Gen3Constants.rsNatDexScriptPart2, writeSpace + 8); + + } else if (romEntry.romType == Gen3Constants.RomType_FRLG) { + // Find the original pokedex script + int pkDexOffset = find(Gen3Constants.frlgPokedexScriptIdentifier); + if (pkDexOffset < 0) { + log("Patch unsuccessful." + nl); + return; + } + // Find free space for our new routine + int writeSpace = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, 10, fso); + if (writeSpace < fso) { + // Somehow this ROM is full + log("Patch unsuccessful." + nl); + return; + } + rom[pkDexOffset] = 4; + writePointer(pkDexOffset + 1, writeSpace); + rom[pkDexOffset + 5] = 0; // NOP + + // Now write our new routine + writeHexString(Gen3Constants.frlgNatDexScript, writeSpace); + + // Fix people using the national dex flag + List ndexChecks = findMultiple(Gen3Constants.frlgNatDexFlagChecker); + for (int ndexCheckOffset : ndexChecks) { + // change to a flag-check + // 82C = "beaten e4/gary once" + writeHexString(Gen3Constants.frlgE4FlagChecker, ndexCheckOffset); + } + + // Fix oak in his lab + int oakLabCheckOffs = find(Gen3Constants.frlgOaksLabKantoDexChecker); + if (oakLabCheckOffs > 0) { + // replace it + writeHexString(Gen3Constants.frlgOaksLabFix, oakLabCheckOffs); + } + + // Fix oak outside your house + int oakHouseCheckOffs = find(Gen3Constants.frlgOakOutsideHouseCheck); + if (oakHouseCheckOffs > 0) { + // fix him to use ndex count + writeHexString(Gen3Constants.frlgOakOutsideHouseFix, oakHouseCheckOffs); + } + + // Fix Oak's aides so they look for your National Dex seen/caught, + // not your Kanto Dex seen/caught + int oakAideCheckOffs = find(Gen3Constants.frlgOakAideCheckPrefix); + if (oakAideCheckOffs > 0) { + oakAideCheckOffs += Gen3Constants.frlgOakAideCheckPrefix.length() / 2; // because it was a prefix + // Change the bne instruction to an unconditional branch to always use National Dex + rom[oakAideCheckOffs + 1] = (byte) 0xE0; + } + } else { + // Find the original pokedex script + int pkDexOffset = find(Gen3Constants.ePokedexScriptIdentifier); + if (pkDexOffset < 0) { + log("Patch unsuccessful." + nl); + return; + } + int textPointer = readPointer(pkDexOffset - 4); + int realScriptLocation = pkDexOffset - 8; + int pointerLocToScript = find(pointerToHexString(realScriptLocation)); + if (pointerLocToScript < 0) { + log("Patch unsuccessful." + nl); + return; + } + // Find free space for our new routine + int writeSpace = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, 27, fso); + if (writeSpace < fso) { + // Somehow this ROM is full + log("Patch unsuccessful." + nl); + return; + } + writePointer(pointerLocToScript, writeSpace); + writeHexString(Gen3Constants.eNatDexScriptPart1, writeSpace); + writePointer(writeSpace + 4, textPointer); + writeHexString(Gen3Constants.eNatDexScriptPart2, writeSpace + 8); + } + log("Patch successful!" + nl); + } + + private String pointerToHexString(int pointer) { + String hex = String.format("%08X", pointer + 0x08000000); + return new String(new char[] { hex.charAt(6), hex.charAt(7), hex.charAt(4), hex.charAt(5), hex.charAt(2), + hex.charAt(3), hex.charAt(0), hex.charAt(1) }); + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + int baseOffset = romEntry.getValue("PokemonEvolutions"); + int numInternalPokes = romEntry.getValue("PokemonCount"); + for (int i = 1; i <= numRealPokemon; i++) { + Pokemon pk = pokemonList.get(i); + int idx = pokedexToInternal[pk.number]; + int evoOffset = baseOffset + (idx) * 0x28; + for (int j = 0; j < 5; j++) { + int method = readWord(evoOffset + j * 8); + int evolvingTo = readWord(evoOffset + j * 8 + 4); + if (method >= 1 && method <= Gen3Constants.evolutionMethodCount && evolvingTo >= 1 + && evolvingTo <= numInternalPokes) { + int extraInfo = readWord(evoOffset + j * 8 + 2); + EvolutionType et = EvolutionType.fromIndex(3, method); + Evolution evo = new Evolution(pk, pokesInternal[evolvingTo], true, et, extraInfo); + if (!pk.evolutionsFrom.contains(evo)) { + pk.evolutionsFrom.add(evo); + pokesInternal[evolvingTo].evolutionsTo.add(evo); + } + } + } + // Split evos shouldn't carry stats unless the evo is Nincada's + // In that case, we should have Ninjask carry stats + if (pk.evolutionsFrom.size() > 1) { + for (Evolution e : pk.evolutionsFrom) { + if (e.type != EvolutionType.LEVEL_CREATE_EXTRA) { + e.carryStats = false; + } + } + } + } + } + + private void writeEvolutions() { + int baseOffset = romEntry.getValue("PokemonEvolutions"); + for (int i = 1; i <= numRealPokemon; i++) { + Pokemon pk = pokemonList.get(i); + int idx = pokedexToInternal[pk.number]; + int evoOffset = baseOffset + (idx) * 0x28; + int evosWritten = 0; + for (Evolution evo : pk.evolutionsFrom) { + writeWord(evoOffset, evo.type.toIndex(3)); + writeWord(evoOffset + 2, evo.extraInfo); + writeWord(evoOffset + 4, pokedexToInternal[evo.to.number]); + writeWord(evoOffset + 6, 0); + evoOffset += 8; + evosWritten++; + if (evosWritten == 5) { + break; + } + } + while (evosWritten < 5) { + writeWord(evoOffset, 0); + writeWord(evoOffset + 2, 0); + writeWord(evoOffset + 4, 0); + writeWord(evoOffset + 6, 0); + evoOffset += 8; + evosWritten++; + } + } + } + + @Override + public void removeImpossibleEvolutions(Settings settings) { + attemptObedienceEvolutionPatches(); + + // no move evos, so no need to check for those + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + for (Evolution evo : pkmn.evolutionsFrom) { + // Not trades, but impossible without trading + if (evo.type == EvolutionType.HAPPINESS_DAY && romEntry.romType == Gen3Constants.RomType_FRLG) { + // happiness day change to Sun Stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Gen3Items.sunStone; + addEvoUpdateStone(impossibleEvolutionUpdates, evo, itemNames[Gen3Items.sunStone]); + } + if (evo.type == EvolutionType.HAPPINESS_NIGHT && romEntry.romType == Gen3Constants.RomType_FRLG) { + // happiness night change to Moon Stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Gen3Items.moonStone; + addEvoUpdateStone(impossibleEvolutionUpdates, evo, itemNames[Gen3Items.moonStone]); + } + if (evo.type == EvolutionType.LEVEL_HIGH_BEAUTY && romEntry.romType == Gen3Constants.RomType_FRLG) { + // beauty change to level 35 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 35; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // Pure Trade + if (evo.type == EvolutionType.TRADE) { + // Haunter, Machoke, Kadabra, Graveler + // Make it into level 37, we're done. + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // Trade w/ Held Item + if (evo.type == EvolutionType.TRADE_ITEM) { + if (evo.from.number == Species.poliwhirl) { + // Poliwhirl: Lv 37 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } else if (evo.from.number == Species.slowpoke) { + // Slowpoke: Water Stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Gen3Items.waterStone; + addEvoUpdateStone(impossibleEvolutionUpdates, evo, itemNames[Gen3Items.waterStone]); + } else if (evo.from.number == Species.seadra) { + // Seadra: Lv 40 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 40; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } else if (evo.from.number == Species.clamperl + && evo.extraInfo == Gen3Items.deepSeaTooth) { + // Clamperl -> Huntail: Lv30 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 30; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } else if (evo.from.number == Species.clamperl + && evo.extraInfo == Gen3Items.deepSeaScale) { + // Clamperl -> Gorebyss: Water Stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Gen3Items.waterStone; + addEvoUpdateStone(impossibleEvolutionUpdates, evo, itemNames[Gen3Items.waterStone]); + } else { + // Onix, Scyther or Porygon: Lv30 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 30; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + } + } + } + } + + } + + @Override + public void makeEvolutionsEasier(Settings settings) { + // Reduce the amount of happiness required to evolve. + int offset = find(rom, Gen3Constants.friendshipValueForEvoLocator); + if (offset > 0) { + // Amount of required happiness for HAPPINESS evolutions. + if (rom[offset] == (byte)219) { + rom[offset] = (byte)159; + } + // FRLG doesn't have code to handle time-based evolutions. + if (romEntry.romType != Gen3Constants.RomType_FRLG) { + // Amount of required happiness for HAPPINESS_DAY evolutions. + if (rom[offset + 38] == (byte)219) { + rom[offset + 38] = (byte)159; + } + // Amount of required happiness for HAPPINESS_NIGHT evolutions. + if (rom[offset + 66] == (byte)219) { + rom[offset + 66] = (byte)159; + } + } + } + } + + @Override + public void removeTimeBasedEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + for (Evolution evol : pkmn.evolutionsFrom) { + // In Gen 3, only Eevee has a time-based evolution. + if (evol.type == EvolutionType.HAPPINESS_DAY) { + // Eevee: Make sun stone => Espeon + evol.type = EvolutionType.STONE; + evol.extraInfo = Gen3Items.sunStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evol, itemNames[evol.extraInfo]); + } else if (evol.type == EvolutionType.HAPPINESS_NIGHT) { + // Eevee: Make moon stone => Umbreon + evol.type = EvolutionType.STONE; + evol.extraInfo = Gen3Items.moonStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evol, itemNames[evol.extraInfo]); + } + } + } + } + } + + @Override + public boolean hasShopRandomization() { + return true; + } + + @Override + public Map getShopItems() { + List shopNames = Gen3Constants.getShopNames(romEntry.romType); + List mainGameShops = Arrays.stream(romEntry.arrayEntries.get("MainGameShops")).boxed().collect(Collectors.toList()); + List skipShops = Arrays.stream(romEntry.arrayEntries.get("SkipShops")).boxed().collect(Collectors.toList()); + Map shopItemsMap = new TreeMap<>(); + int[] shopItemOffsets = romEntry.arrayEntries.get("ShopItemOffsets"); + for (int i = 0; i < shopItemOffsets.length; i++) { + if (!skipShops.contains(i)) { + int offset = shopItemOffsets[i]; + List items = new ArrayList<>(); + int val = FileFunctions.read2ByteInt(rom, offset); + while (val != 0x0000) { + items.add(val); + offset += 2; + val = FileFunctions.read2ByteInt(rom, offset); + } + Shop shop = new Shop(); + shop.items = items; + shop.name = shopNames.get(i); + shop.isMainGame = mainGameShops.contains(i); + shopItemsMap.put(i, shop); + } + } + return shopItemsMap; + } + + @Override + public void setShopItems(Map shopItems) { + int[] shopItemOffsets = romEntry.arrayEntries.get("ShopItemOffsets"); + for (int i = 0; i < shopItemOffsets.length; i++) { + Shop thisShop = shopItems.get(i); + if (thisShop != null && thisShop.items != null) { + int offset = shopItemOffsets[i]; + Iterator iterItems = thisShop.items.iterator(); + while (iterItems.hasNext()) { + FileFunctions.write2ByteInt(rom, offset, iterItems.next()); + offset += 2; + } + } + } + } + + @Override + public void setShopPrices() { + int itemDataOffset = romEntry.getValue("ItemData"); + int entrySize = romEntry.getValue("ItemEntrySize"); + int itemCount = romEntry.getValue("ItemCount"); + for (int i = 1; i < itemCount; i++) { + int balancedPrice = Gen3Constants.balancedItemPrices.get(i) * 10; + int offset = itemDataOffset + (i * entrySize) + 16; + FileFunctions.write2ByteInt(rom, offset, balancedPrice); + } + } + + @Override + public List getPickupItems() { + List pickupItems = new ArrayList<>(); + int pickupItemCount = romEntry.getValue("PickupItemCount"); + int sizeOfPickupEntry = romEntry.romType == Gen3Constants.RomType_Em ? 2 : 4; + + // If we haven't found the pickup table for this ROM already, find it. + if (pickupItemsTableOffset == 0) { + String pickupTableStartLocator = romEntry.getString("PickupTableStartLocator"); + int offset = find(pickupTableStartLocator); + if (offset > 0) { + pickupItemsTableOffset = offset; + } + } + + // Assuming we've found the pickup table, extract the items out of it. + if (pickupItemsTableOffset > 0) { + for (int i = 0; i < pickupItemCount; i++) { + int itemOffset = pickupItemsTableOffset + (sizeOfPickupEntry * i); + int item = FileFunctions.read2ByteInt(rom, itemOffset); + PickupItem pickupItem = new PickupItem(item); + pickupItems.add(pickupItem); + } + } + + // Assuming we got the items from the last step, fill out the probabilities based on the game. + if (pickupItems.size() > 0) { + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + for (int levelRange = 0; levelRange < 10; levelRange++) { + pickupItems.get(0).probabilities[levelRange] = 30; + pickupItems.get(7).probabilities[levelRange] = 5; + pickupItems.get(8).probabilities[levelRange] = 4; + pickupItems.get(9).probabilities[levelRange] = 1; + for (int i = 1; i < 7; i++) { + pickupItems.get(i).probabilities[levelRange] = 10; + } + } + } else if (romEntry.romType == Gen3Constants.RomType_FRLG) { + for (int levelRange = 0; levelRange < 10; levelRange++) { + pickupItems.get(0).probabilities[levelRange] = 15; + for (int i = 1; i < 7; i++) { + pickupItems.get(i).probabilities[levelRange] = 10; + } + for (int i = 7; i < 11; i++) { + pickupItems.get(i).probabilities[levelRange] = 5; + } + for (int i = 11; i < 16; i++) { + pickupItems.get(i).probabilities[levelRange] = 1; + } + } + } else { + for (int levelRange = 0; levelRange < 10; levelRange++) { + int startingCommonItemOffset = levelRange; + int startingRareItemOffset = 18 + levelRange; + pickupItems.get(startingCommonItemOffset).probabilities[levelRange] = 30; + for (int i = 1; i < 7; i++) { + pickupItems.get(startingCommonItemOffset + i).probabilities[levelRange] = 10; + } + pickupItems.get(startingCommonItemOffset + 7).probabilities[levelRange] = 4; + pickupItems.get(startingCommonItemOffset + 8).probabilities[levelRange] = 4; + pickupItems.get(startingRareItemOffset).probabilities[levelRange] = 1; + pickupItems.get(startingRareItemOffset + 1).probabilities[levelRange] = 1; + } + } + } + return pickupItems; + } + + @Override + public void setPickupItems(List pickupItems) { + int sizeOfPickupEntry = romEntry.romType == Gen3Constants.RomType_Em ? 2 : 4; + if (pickupItemsTableOffset > 0) { + for (int i = 0; i < pickupItems.size(); i++) { + int itemOffset = pickupItemsTableOffset + (sizeOfPickupEntry * i); + FileFunctions.write2ByteInt(rom, itemOffset, pickupItems.get(i).item); + } + } + } + + @Override + public boolean canChangeTrainerText() { + return true; + } + + @Override + public List getTrainerNames() { + int baseOffset = romEntry.getValue("TrainerData"); + int amount = romEntry.getValue("TrainerCount"); + int entryLen = romEntry.getValue("TrainerEntrySize"); + List theTrainers = new ArrayList<>(); + for (int i = 1; i < amount; i++) { + theTrainers.add(readVariableLengthString(baseOffset + i * entryLen + 4)); + } + return theTrainers; + } + + @Override + public void setTrainerNames(List trainerNames) { + int baseOffset = romEntry.getValue("TrainerData"); + int amount = romEntry.getValue("TrainerCount"); + int entryLen = romEntry.getValue("TrainerEntrySize"); + int nameLen = romEntry.getValue("TrainerNameLength"); + Iterator theTrainers = trainerNames.iterator(); + for (int i = 1; i < amount; i++) { + String newName = theTrainers.next(); + writeFixedLengthString(newName, baseOffset + i * entryLen + 4, nameLen); + } + + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.MAX_LENGTH; + } + + @Override + public List getTCNameLengthsByTrainer() { + // not needed + return new ArrayList<>(); + } + + @Override + public int maxTrainerNameLength() { + return romEntry.getValue("TrainerNameLength") - 1; + } + + @Override + public List getTrainerClassNames() { + int baseOffset = romEntry.getValue("TrainerClassNames"); + int amount = romEntry.getValue("TrainerClassCount"); + int length = romEntry.getValue("TrainerClassNameLength"); + List trainerClasses = new ArrayList<>(); + for (int i = 0; i < amount; i++) { + trainerClasses.add(readVariableLengthString(baseOffset + i * length)); + } + return trainerClasses; + } + + @Override + public void setTrainerClassNames(List trainerClassNames) { + int baseOffset = romEntry.getValue("TrainerClassNames"); + int amount = romEntry.getValue("TrainerClassCount"); + int length = romEntry.getValue("TrainerClassNameLength"); + Iterator trainerClasses = trainerClassNames.iterator(); + for (int i = 0; i < amount; i++) { + writeFixedLengthString(trainerClasses.next(), baseOffset + i * length, length); + } + } + + @Override + public int maxTrainerClassNameLength() { + return romEntry.getValue("TrainerClassNameLength") - 1; + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return false; + } + + @Override + public List getDoublesTrainerClasses() { + int[] doublesClasses = romEntry.arrayEntries.get("DoublesTrainerClasses"); + List doubles = new ArrayList<>(); + for (int tClass : doublesClasses) { + doubles.add(tClass); + } + return doubles; + } + + @Override + public boolean canChangeStaticPokemon() { + return (romEntry.getValue("StaticPokemonSupport") > 0); + } + + @Override + public boolean hasStaticAltFormes() { + return false; + } + + @Override + public boolean hasMainGameLegendaries() { + return romEntry.arrayEntries.get("MainGameLegendaries") != null; + } + + @Override + public List getMainGameLegendaries() { + if (this.hasMainGameLegendaries()) { + return Arrays.stream(romEntry.arrayEntries.get("MainGameLegendaries")).boxed().collect(Collectors.toList()); + } + return new ArrayList<>(); + } + + @Override + public List getSpecialMusicStatics() { + return Arrays.stream(romEntry.arrayEntries.get("SpecialMusicStatics")).boxed().collect(Collectors.toList()); + } + + @Override + public void applyCorrectStaticMusic(Map specialMusicStaticChanges) { + List replaced = new ArrayList<>(); + int newIndexToMusicPoolOffset; + + if (romEntry.codeTweaks.get("NewIndexToMusicTweak") != null) { + try { + FileFunctions.applyPatch(rom, romEntry.codeTweaks.get("NewIndexToMusicTweak")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + newIndexToMusicPoolOffset = romEntry.getValue("NewIndexToMusicPoolOffset"); + + if (newIndexToMusicPoolOffset > 0) { + + for (int oldStatic: specialMusicStaticChanges.keySet()) { + int i = newIndexToMusicPoolOffset; + int index = internalToPokedex[readWord(rom, i)]; + while (index != oldStatic || replaced.contains(i)) { + i += 4; + index = internalToPokedex[readWord(rom, i)]; + } + writeWord(rom, i, pokedexToInternal[specialMusicStaticChanges.get(oldStatic)]); + replaced.add(i); + } + } + } + } + + @Override + public boolean hasStaticMusicFix() { + return romEntry.codeTweaks.get("NewIndexToMusicTweak") != null; + } + + @Override + public List getTotemPokemon() { + return new ArrayList<>(); + } + + @Override + public void setTotemPokemon(List totemPokemon) { + + } + + @Override + public String getDefaultExtension() { + return "gba"; + } + + @Override + public int abilitiesPerPokemon() { + return 2; + } + + @Override + public int highestAbilityIndex() { + return Gen3Constants.highestAbilityIndex; + } + + private void loadAbilityNames() { + int nameoffs = romEntry.getValue("AbilityNames"); + int namelen = romEntry.getValue("AbilityNameLength"); + abilityNames = new String[Gen3Constants.highestAbilityIndex + 1]; + for (int i = 0; i <= Gen3Constants.highestAbilityIndex; i++) { + abilityNames[i] = readFixedLengthString(nameoffs + namelen * i, namelen); + } + } + + @Override + public String abilityName(int number) { + return abilityNames[number]; + } + + @Override + public Map> getAbilityVariations() { + return Gen3Constants.abilityVariations; + } + + @Override + public List getUselessAbilities() { + return new ArrayList<>(Gen3Constants.uselessAbilities); + } + + @Override + public int getAbilityForTrainerPokemon(TrainerPokemon tp) { + // In Gen 3, Trainer Pokemon *always* use the first Ability, no matter what + return tp.pokemon.ability1; + } + + @Override + public boolean hasMegaEvolutions() { + return false; + } + + @Override + public int internalStringLength(String string) { + return translateString(string).length; + } + + @Override + public void randomizeIntroPokemon() { + // FRLG + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + // intro sprites : first 255 only due to size + Pokemon introPk = randomPokemonLimited(255, false); + if (introPk == null) { + return; + } + int introPokemon = pokedexToInternal[introPk.number]; + int frontSprites = romEntry.getValue("FrontSprites"); + int palettes = romEntry.getValue("PokemonPalettes"); + + rom[romEntry.getValue("IntroCryOffset")] = (byte) introPokemon; + rom[romEntry.getValue("IntroOtherOffset")] = (byte) introPokemon; + + int spriteBase = romEntry.getValue("IntroSpriteOffset"); + writePointer(spriteBase, frontSprites + introPokemon * 8); + writePointer(spriteBase + 4, palettes + introPokemon * 8); + } else if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + // intro sprites : any pokemon in the range 0-510 except bulbasaur + int introPokemon = pokedexToInternal[randomPokemon().number]; + while (introPokemon == 1 || introPokemon > 510) { + introPokemon = pokedexToInternal[randomPokemon().number]; + } + int frontSprites = romEntry.getValue("PokemonFrontSprites"); + int palettes = romEntry.getValue("PokemonNormalPalettes"); + int cryCommand = romEntry.getValue("IntroCryOffset"); + int otherCommand = romEntry.getValue("IntroOtherOffset"); + + if (introPokemon > 255) { + rom[cryCommand] = (byte) 0xFF; + rom[cryCommand + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR0; + + rom[cryCommand + 2] = (byte) (introPokemon - 0xFF); + rom[cryCommand + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR0; + + rom[otherCommand] = (byte) 0xFF; + rom[otherCommand + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR4; + + rom[otherCommand + 2] = (byte) (introPokemon - 0xFF); + rom[otherCommand + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR4; + } else { + rom[cryCommand] = (byte) introPokemon; + rom[cryCommand + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR0; + + writeWord(cryCommand + 2, Gen3Constants.gbaNopOpcode); + + rom[otherCommand] = (byte) introPokemon; + rom[otherCommand + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR4; + + writeWord(otherCommand + 2, Gen3Constants.gbaNopOpcode); + } + + writePointer(romEntry.getValue("IntroSpriteOffset"), frontSprites + introPokemon * 8); + writePointer(romEntry.getValue("IntroPaletteOffset"), palettes + introPokemon * 8); + } else { + // Emerald, intro sprite: any Pokemon. + int introPokemon = pokedexToInternal[randomPokemon().number]; + writeWord(romEntry.getValue("IntroSpriteOffset"), introPokemon); + writeWord(romEntry.getValue("IntroCryOffset"), introPokemon); + } + + } + + private Pokemon randomPokemonLimited(int maxValue, boolean blockNonMales) { + checkPokemonRestrictions(); + List validPokemon = new ArrayList<>(); + for (Pokemon pk : this.mainPokemonList) { + if (pokedexToInternal[pk.number] <= maxValue && (!blockNonMales || pk.genderRatio <= 0xFD)) { + validPokemon.add(pk); + } + } + if (validPokemon.size() == 0) { + return null; + } else { + return validPokemon.get(random.nextInt(validPokemon.size())); + } + } + + private void determineMapBankSizes() { + int mbpsOffset = romEntry.getValue("MapHeaders"); + List mapBankOffsets = new ArrayList<>(); + + int offset = mbpsOffset; + + // find map banks + while (true) { + boolean valid = true; + for (int mbOffset : mapBankOffsets) { + if (mbpsOffset < mbOffset && offset >= mbOffset) { + valid = false; + break; + } + } + if (!valid) { + break; + } + int newMBOffset = readPointer(offset); + if (newMBOffset < 0 || newMBOffset >= rom.length) { + break; + } + mapBankOffsets.add(newMBOffset); + offset += 4; + } + int bankCount = mapBankOffsets.size(); + int[] bankMapCounts = new int[bankCount]; + for (int bank = 0; bank < bankCount; bank++) { + int baseBankOffset = mapBankOffsets.get(bank); + int count = 0; + offset = baseBankOffset; + while (true) { + boolean valid = true; + for (int mbOffset : mapBankOffsets) { + if (baseBankOffset < mbOffset && offset >= mbOffset) { + valid = false; + break; + } + } + if (!valid) { + break; + } + if (baseBankOffset < mbpsOffset && offset >= mbpsOffset) { + break; + } + int newMapOffset = readPointer(offset); + if (newMapOffset < 0 || newMapOffset >= rom.length) { + break; + } + count++; + offset += 4; + } + bankMapCounts[bank] = count; + } + + romEntry.entries.put("MapBankCount", bankCount); + romEntry.arrayEntries.put("MapBankSizes", bankMapCounts); + } + + private void preprocessMaps() { + itemOffs = new ArrayList<>(); + int bankCount = romEntry.getValue("MapBankCount"); + int[] bankMapCounts = romEntry.arrayEntries.get("MapBankSizes"); + int itemBall = romEntry.getValue("ItemBallPic"); + mapNames = new String[bankCount][]; + int mbpsOffset = romEntry.getValue("MapHeaders"); + int mapLabels = romEntry.getValue("MapLabels"); + Map mapLabelsM = new HashMap<>(); + for (int bank = 0; bank < bankCount; bank++) { + int bankOffset = readPointer(mbpsOffset + bank * 4); + mapNames[bank] = new String[bankMapCounts[bank]]; + for (int map = 0; map < bankMapCounts[bank]; map++) { + int mhOffset = readPointer(bankOffset + map * 4); + + // map name + int mapLabel = rom[mhOffset + 0x14] & 0xFF; + if (mapLabelsM.containsKey(mapLabel)) { + mapNames[bank][map] = mapLabelsM.get(mapLabel); + } else { + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + mapNames[bank][map] = readVariableLengthString(readPointer(mapLabels + + (mapLabel - Gen3Constants.frlgMapLabelsStart) * 4)); + } else { + mapNames[bank][map] = readVariableLengthString(readPointer(mapLabels + mapLabel * 8 + 4)); + } + mapLabelsM.put(mapLabel, mapNames[bank][map]); + } + + // events + int eventOffset = readPointer(mhOffset + 4); + if (eventOffset >= 0 && eventOffset < rom.length) { + + int pCount = rom[eventOffset] & 0xFF; + int spCount = rom[eventOffset + 3] & 0xFF; + + if (pCount > 0) { + int peopleOffset = readPointer(eventOffset + 4); + for (int p = 0; p < pCount; p++) { + int pSprite = rom[peopleOffset + p * 24 + 1]; + if (pSprite == itemBall && readPointer(peopleOffset + p * 24 + 16) >= 0) { + // Get script and look inside + int scriptOffset = readPointer(peopleOffset + p * 24 + 16); + if (rom[scriptOffset] == 0x1A && rom[scriptOffset + 1] == 0x00 + && (rom[scriptOffset + 2] & 0xFF) == 0x80 && rom[scriptOffset + 5] == 0x1A + && rom[scriptOffset + 6] == 0x01 && (rom[scriptOffset + 7] & 0xFF) == 0x80 + && rom[scriptOffset + 10] == 0x09 + && (rom[scriptOffset + 11] == 0x00 || rom[scriptOffset + 11] == 0x01)) { + // item ball script + itemOffs.add(scriptOffset + 3); + } + } + } + // TM Text? + for (TMOrMTTextEntry tte : romEntry.tmmtTexts) { + if (tte.mapBank == bank && tte.mapNumber == map) { + // process this one + int scriptOffset = readPointer(peopleOffset + (tte.personNum - 1) * 24 + 16); + if (scriptOffset >= 0) { + if (romEntry.romType == Gen3Constants.RomType_FRLG && tte.isMoveTutor + && (tte.number == 5 || (tte.number >= 8 && tte.number <= 11))) { + scriptOffset = readPointer(scriptOffset + 1); + } else if (romEntry.romType == Gen3Constants.RomType_FRLG && tte.isMoveTutor + && tte.number == 7) { + scriptOffset = readPointer(scriptOffset + 0x1F); + } + int lookAt = scriptOffset + tte.offsetInScript; + // make sure this actually looks like a text + // pointer + if (lookAt >= 0 && lookAt < rom.length - 2) { + if (rom[lookAt + 3] == 0x08 || rom[lookAt + 3] == 0x09) { + // okay, it passes the basic test + tte.actualOffset = lookAt; + } + } + } + } + } + } + + if (spCount > 0) { + int signpostsOffset = readPointer(eventOffset + 16); + for (int sp = 0; sp < spCount; sp++) { + int spType = rom[signpostsOffset + sp * 12 + 5]; + if (spType >= 5 && spType <= 7) { + // hidden item + int itemHere = readWord(signpostsOffset + sp * 12 + 8); + if (itemHere != 0) { + // itemid 0 is coins + itemOffs.add(signpostsOffset + sp * 12 + 8); + } + } + } + } + } + } + } + } + + @Override + public ItemList getAllowedItems() { + return allowedItems; + } + + @Override + public ItemList getNonBadItems() { + return nonBadItems; + } + + @Override + public List getUniqueNoSellItems() { + return new ArrayList<>(); + } + + @Override + public List getRegularShopItems() { + return Gen3Constants.regularShopItems; + } + + @Override + public List getOPShopItems() { + return Gen3Constants.opShopItems; + } + + private void loadItemNames() { + int nameoffs = romEntry.getValue("ItemData"); + int structlen = romEntry.getValue("ItemEntrySize"); + int maxcount = romEntry.getValue("ItemCount"); + itemNames = new String[maxcount + 1]; + for (int i = 0; i <= maxcount; i++) { + itemNames[i] = readVariableLengthString(nameoffs + structlen * i); + } + } + + @Override + public String[] getItemNames() { + return itemNames; + } + + @Override + public List getRequiredFieldTMs() { + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + return Gen3Constants.frlgRequiredFieldTMs; + } else if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + return Gen3Constants.rsRequiredFieldTMs; + } else { + // emerald has a few TMs from pickup + return Gen3Constants.eRequiredFieldTMs; + } + } + + @Override + public List getCurrentFieldTMs() { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + List fieldTMs = new ArrayList<>(); + + for (int offset : itemOffs) { + int itemHere = readWord(offset); + if (Gen3Constants.allowedItems.isTM(itemHere)) { + int thisTM = itemHere - Gen3Constants.tmItemOffset + 1; + // hack for repeat TMs + if (!fieldTMs.contains(thisTM)) { + fieldTMs.add(thisTM); + } + } + } + return fieldTMs; + } + + @Override + public void setFieldTMs(List fieldTMs) { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + Iterator iterTMs = fieldTMs.iterator(); + int[] givenTMs = new int[512]; + + for (int offset : itemOffs) { + int itemHere = readWord(offset); + if (Gen3Constants.allowedItems.isTM(itemHere)) { + // Cache replaced TMs to duplicate repeats + if (givenTMs[itemHere] != 0) { + rom[offset] = (byte) givenTMs[itemHere]; + } else { + // Replace this with a TM from the list + int tm = iterTMs.next(); + tm += Gen3Constants.tmItemOffset - 1; + givenTMs[itemHere] = tm; + writeWord(offset, tm); + } + } + } + } + + @Override + public List getRegularFieldItems() { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + List fieldItems = new ArrayList<>(); + + for (int offset : itemOffs) { + int itemHere = readWord(offset); + if (Gen3Constants.allowedItems.isAllowed(itemHere) && !(Gen3Constants.allowedItems.isTM(itemHere))) { + fieldItems.add(itemHere); + } + } + return fieldItems; + } + + @Override + public void setRegularFieldItems(List items) { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + Iterator iterItems = items.iterator(); + + for (int offset : itemOffs) { + int itemHere = readWord(offset); + if (Gen3Constants.allowedItems.isAllowed(itemHere) && !(Gen3Constants.allowedItems.isTM(itemHere))) { + // Replace it + writeWord(offset, iterItems.next()); + } + } + + } + + @Override + public List getIngameTrades() { + List trades = new ArrayList<>(); + + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = 60; + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = new IngameTrade(); + int entryOffset = tableOffset + entry * entryLength; + trade.nickname = readVariableLengthString(entryOffset); + trade.givenPokemon = pokesInternal[readWord(entryOffset + 12)]; + trade.ivs = new int[6]; + for (int i = 0; i < 6; i++) { + trade.ivs[i] = rom[entryOffset + 14 + i] & 0xFF; + } + trade.otId = readWord(entryOffset + 24); + trade.item = readWord(entryOffset + 40); + trade.otName = readVariableLengthString(entryOffset + 43); + trade.requestedPokemon = pokesInternal[readWord(entryOffset + 56)]; + trades.add(trade); + } + + return trades; + + } + + @Override + public void setIngameTrades(List trades) { + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = 60; + int tradeOffset = 0; + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = trades.get(tradeOffset++); + int entryOffset = tableOffset + entry * entryLength; + writeFixedLengthString(trade.nickname, entryOffset, 12); + writeWord(entryOffset + 12, pokedexToInternal[trade.givenPokemon.number]); + for (int i = 0; i < 6; i++) { + rom[entryOffset + 14 + i] = (byte) trade.ivs[i]; + } + writeWord(entryOffset + 24, trade.otId); + writeWord(entryOffset + 40, trade.item); + writeFixedLengthString(trade.otName, entryOffset + 43, 11); + writeWord(entryOffset + 56, pokedexToInternal[trade.requestedPokemon.number]); + } + } + + @Override + public boolean hasDVs() { + return false; + } + + @Override + public int generationOfPokemon() { + return 3; + } + + @Override + public void removeEvosForPokemonPool() { + List pokemonIncluded = this.mainPokemonList; + Set keepEvos = new HashSet<>(); + for (Pokemon pk : pokes) { + if (pk != null) { + keepEvos.clear(); + for (Evolution evol : pk.evolutionsFrom) { + if (pokemonIncluded.contains(evol.from) && pokemonIncluded.contains(evol.to)) { + keepEvos.add(evol); + } else { + evol.to.evolutionsTo.remove(evol); + } + } + pk.evolutionsFrom.retainAll(keepEvos); + } + } + } + + @Override + public boolean supportsFourStartingMoves() { + return true; + } + + @Override + public List getFieldMoves() { + // cut, fly, surf, strength, flash, + // dig, teleport, waterfall, + // rock smash, sweet scent + // not softboiled or milk drink + // dive and secret power in RSE only + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + return Gen3Constants.frlgFieldMoves; + } else { + return Gen3Constants.rseFieldMoves; + } + } + + @Override + public List getEarlyRequiredHMMoves() { + // RSE: rock smash + // FRLG: cut + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + return Gen3Constants.frlgEarlyRequiredHMMoves; + } else { + return Gen3Constants.rseEarlyRequiredHMMoves; + } + } + + @Override + public int miscTweaksAvailable() { + int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); + available |= MiscTweak.NATIONAL_DEX_AT_START.getValue(); + available |= MiscTweak.UPDATE_TYPE_EFFECTIVENESS.getValue(); + if (romEntry.getValue("RunIndoorsTweakOffset") > 0) { + available |= MiscTweak.RUNNING_SHOES_INDOORS.getValue(); + } + if (romEntry.getValue("TextSpeedValuesOffset") > 0 || romEntry.codeTweaks.get("InstantTextTweak") != null) { + available |= MiscTweak.FASTEST_TEXT.getValue(); + } + if (romEntry.getValue("CatchingTutorialOpponentMonOffset") > 0 + || romEntry.getValue("CatchingTutorialPlayerMonOffset") > 0) { + available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); + } + if (romEntry.getValue("PCPotionOffset") != 0) { + available |= MiscTweak.RANDOMIZE_PC_POTION.getValue(); + } + available |= MiscTweak.BAN_LUCKY_EGG.getValue(); + available |= MiscTweak.RUN_WITHOUT_RUNNING_SHOES.getValue(); + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + available |= MiscTweak.BALANCE_STATIC_LEVELS.getValue(); + } + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.RUNNING_SHOES_INDOORS) { + applyRunningShoesIndoorsPatch(); + } else if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestTextPatch(); + } else if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { + applyCamelCaseNames(); + } else if (tweak == MiscTweak.NATIONAL_DEX_AT_START) { + patchForNationalDex(); + } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { + randomizeCatchingTutorial(); + } else if (tweak == MiscTweak.BAN_LUCKY_EGG) { + allowedItems.banSingles(Gen3Items.luckyEgg); + nonBadItems.banSingles(Gen3Items.luckyEgg); + } else if (tweak == MiscTweak.RANDOMIZE_PC_POTION) { + randomizePCPotion(); + } else if (tweak == MiscTweak.RUN_WITHOUT_RUNNING_SHOES) { + applyRunWithoutRunningShoesPatch(); + } else if (tweak == MiscTweak.BALANCE_STATIC_LEVELS) { + int[] fossilLevelOffsets = romEntry.arrayEntries.get("FossilLevelOffsets"); + for (int fossilLevelOffset : fossilLevelOffsets) { + writeWord(rom, fossilLevelOffset, 30); + } + } else if (tweak == MiscTweak.UPDATE_TYPE_EFFECTIVENESS) { + updateTypeEffectiveness(); + } + } + + @Override + public boolean isEffectivenessUpdated() { + return effectivenessUpdated; + } + + private void randomizeCatchingTutorial() { + if (romEntry.getValue("CatchingTutorialOpponentMonOffset") > 0) { + int oppOffset = romEntry.getValue("CatchingTutorialOpponentMonOffset"); + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + Pokemon opponent = randomPokemonLimited(255, true); + if (opponent != null) { + + int oppValue = pokedexToInternal[opponent.number]; + rom[oppOffset] = (byte) oppValue; + rom[oppOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + } + } else { + Pokemon opponent = randomPokemonLimited(510, true); + if (opponent != null) { + int oppValue = pokedexToInternal[opponent.number]; + if (oppValue > 255) { + rom[oppOffset] = (byte) 0xFF; + rom[oppOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + + rom[oppOffset + 2] = (byte) (oppValue - 0xFF); + rom[oppOffset + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR1; + } else { + rom[oppOffset] = (byte) oppValue; + rom[oppOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + + writeWord(oppOffset + 2, Gen3Constants.gbaNopOpcode); + } + } + } + } + + if (romEntry.getValue("CatchingTutorialPlayerMonOffset") > 0) { + int playerOffset = romEntry.getValue("CatchingTutorialPlayerMonOffset"); + Pokemon playerMon = randomPokemonLimited(510, false); + if (playerMon != null) { + int plyValue = pokedexToInternal[playerMon.number]; + if (plyValue > 255) { + rom[playerOffset] = (byte) 0xFF; + rom[playerOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + + rom[playerOffset + 2] = (byte) (plyValue - 0xFF); + rom[playerOffset + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR1; + } else { + rom[playerOffset] = (byte) plyValue; + rom[playerOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + + writeWord(playerOffset + 2, Gen3Constants.gbaNopOpcode); + } + } + } + + } + + private void applyRunningShoesIndoorsPatch() { + if (romEntry.getValue("RunIndoorsTweakOffset") != 0) { + rom[romEntry.getValue("RunIndoorsTweakOffset")] = 0x00; + } + } + + private void applyFastestTextPatch() { + if(romEntry.codeTweaks.get("InstantTextTweak") != null) { + try { + FileFunctions.applyPatch(rom, romEntry.codeTweaks.get("InstantTextTweak")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } else if (romEntry.getValue("TextSpeedValuesOffset") > 0) { + int tsvOffset = romEntry.getValue("TextSpeedValuesOffset"); + rom[tsvOffset] = 4; // slow = medium + rom[tsvOffset + 1] = 1; // medium = fast + rom[tsvOffset + 2] = 0; // fast = instant + } + } + + private void randomizePCPotion() { + if (romEntry.getValue("PCPotionOffset") != 0) { + writeWord(romEntry.getValue("PCPotionOffset"), this.getNonBadItems().randomNonTM(this.random)); + } + } + + private void applyRunWithoutRunningShoesPatch() { + String prefix = Gen3Constants.getRunningShoesCheckPrefix(romEntry.romType); + int offset = find(prefix); + if (offset != 0) { + // The prefix starts 0x12 bytes from what we want to patch because what comes + // between is region and revision dependent. To start running, the game checks: + // 1. That you're not underwater (RSE only) + // 2. That you're holding the B button + // 3. That the FLAG_SYS_B_DASH flag is set (aka, you've acquired Running Shoes) + // 4. That you're allowed to run in this location + // For #3, if the flag is unset, it jumps to a different part of the + // code to make you walk instead. This simply nops out this jump so the + // game stops caring about the FLAG_SYS_B_DASH flag entirely. + writeWord(offset + 0x12, 0); + } + } + + private void updateTypeEffectiveness() { + List typeEffectivenessTable = readTypeEffectivenessTable(); + log("--Updating Type Effectiveness--"); + for (TypeRelationship relationship : typeEffectivenessTable) { + // Change Ghost 0.5x against Steel to Ghost 1x to Steel + if (relationship.attacker == Type.GHOST && relationship.defender == Type.STEEL) { + relationship.effectiveness = Effectiveness.NEUTRAL; + log("Replaced: Ghost not very effective vs Steel => Ghost neutral vs Steel"); + } + + // Change Dark 0.5x against Steel to Dark 1x to Steel + else if (relationship.attacker == Type.DARK && relationship.defender == Type.STEEL) { + relationship.effectiveness = Effectiveness.NEUTRAL; + log("Replaced: Dark not very effective vs Steel => Dark neutral vs Steel"); + } + } + logBlankLine(); + writeTypeEffectivenessTable(typeEffectivenessTable); + effectivenessUpdated = true; + } + + private List readTypeEffectivenessTable() { + List typeEffectivenessTable = new ArrayList<>(); + int currentOffset = romEntry.getValue("TypeEffectivenessOffset"); + int attackingType = rom[currentOffset]; + // 0xFE marks the end of the table *not* affected by Foresight, while 0xFF marks + // the actual end of the table. Since we don't care about Ghost immunities at all, + // just stop once we reach the Foresight section. + while (attackingType != (byte) 0xFE) { + int defendingType = rom[currentOffset + 1]; + int effectivenessInternal = rom[currentOffset + 2]; + Type attacking = Gen3Constants.typeTable[attackingType]; + Type defending = Gen3Constants.typeTable[defendingType]; + Effectiveness effectiveness = null; + switch (effectivenessInternal) { + case 20: + effectiveness = Effectiveness.DOUBLE; + break; + case 10: + effectiveness = Effectiveness.NEUTRAL; + break; + case 5: + effectiveness = Effectiveness.HALF; + break; + case 0: + effectiveness = Effectiveness.ZERO; + break; + } + if (effectiveness != null) { + TypeRelationship relationship = new TypeRelationship(attacking, defending, effectiveness); + typeEffectivenessTable.add(relationship); + } + currentOffset += 3; + attackingType = rom[currentOffset]; + } + return typeEffectivenessTable; + } + + private void writeTypeEffectivenessTable(List typeEffectivenessTable) { + int currentOffset = romEntry.getValue("TypeEffectivenessOffset"); + for (TypeRelationship relationship : typeEffectivenessTable) { + rom[currentOffset] = Gen3Constants.typeToByte(relationship.attacker); + rom[currentOffset + 1] = Gen3Constants.typeToByte(relationship.defender); + byte effectivenessInternal = 0; + switch (relationship.effectiveness) { + case DOUBLE: + effectivenessInternal = 20; + break; + case NEUTRAL: + effectivenessInternal = 10; + break; + case HALF: + effectivenessInternal = 5; + break; + case ZERO: + effectivenessInternal = 0; + break; + } + rom[currentOffset + 2] = effectivenessInternal; + currentOffset += 3; + } + } + + @Override + public void enableGuaranteedPokemonCatching() { + int offset = find(rom, Gen3Constants.perfectOddsBranchLocator); + if (offset > 0) { + // In Cmd_handleballthrow, the middle of the function checks if the odds of catching a Pokemon + // is greater than 254; if it is, then the Pokemon is automatically caught. In ASM, this is + // represented by: + // cmp r6, #0xFE + // bls oddsLessThanOrEqualTo254 + // The below code just nops these two instructions so that we *always* act like our odds are 255, + // and Pokemon are automatically caught no matter what. + rom[offset] = 0x00; + rom[offset + 1] = 0x00; + rom[offset + 2] = 0x00; + rom[offset + 3] = 0x00; + } + } + + @Override + public boolean isRomValid() { + return romEntry.expectedCRC32 == actualCRC32; + } + + @Override + public BufferedImage getMascotImage() { + Pokemon mascotPk = randomPokemon(); + int mascotPokemon = pokedexToInternal[mascotPk.number]; + int frontSprites = romEntry.getValue("FrontSprites"); + int palettes = romEntry.getValue("PokemonPalettes"); + int fsOffset = readPointer(frontSprites + mascotPokemon * 8); + int palOffset = readPointer(palettes + mascotPokemon * 8); + + byte[] trueFrontSprite = DSDecmp.Decompress(rom, fsOffset); + byte[] truePalette = DSDecmp.Decompress(rom, palOffset); + + // Convert palette into RGB + int[] convPalette = new int[16]; + // Leave palette[0] as 00000000 for transparency + for (int i = 0; i < 15; i++) { + int palValue = readWord(truePalette, i * 2 + 2); + convPalette[i + 1] = GFXFunctions.conv16BitColorToARGB(palValue); + } + + // Make image, 4bpp + return GFXFunctions.drawTiledImage(trueFrontSprite, convPalette, 64, 64, 4); + } + + @Override + public List getAllHeldItems() { + return Gen3Constants.allHeldItems; + } + + @Override + public boolean hasRivalFinalBattle() { + return romEntry.romType == Gen3Constants.RomType_FRLG; + } + + @Override + public List getAllConsumableHeldItems() { + return Gen3Constants.consumableHeldItems; + } + + @Override + public List getSensibleHeldItemsFor(TrainerPokemon tp, boolean consumableOnly, List moves, int[] pokeMoves) { + List items = new ArrayList<>(); + items.addAll(Gen3Constants.generalPurposeConsumableItems); + if (!consumableOnly) { + items.addAll(Gen3Constants.generalPurposeItems); + } + for (int moveIdx : pokeMoves) { + Move move = moves.get(moveIdx); + if (move == null) { + continue; + } + if (GBConstants.physicalTypes.contains(move.type) && move.power > 0) { + items.add(Gen3Items.liechiBerry); + if (!consumableOnly) { + items.addAll(Gen3Constants.typeBoostingItems.get(move.type)); + items.add(Gen3Items.choiceBand); + } + } + if (!GBConstants.physicalTypes.contains(move.type) && move.power > 0) { + items.add(Gen3Items.petayaBerry); + if (!consumableOnly) { + items.addAll(Gen3Constants.typeBoostingItems.get(move.type)); + } + } + } + if (!consumableOnly) { + List speciesItems = Gen3Constants.speciesBoostingItems.get(tp.pokemon.number); + if (speciesItems != null) { + for (int i = 0; i < 6; i++) { // Increase the likelihood of using species specific items. + items.addAll(speciesItems); + } + } + } + return items; + } +} diff --git a/src/com/pkrandom/romhandlers/Gen4RomHandler.java b/src/com/pkrandom/romhandlers/Gen4RomHandler.java new file mode 100755 index 0000000..417799e --- /dev/null +++ b/src/com/pkrandom/romhandlers/Gen4RomHandler.java @@ -0,0 +1,5841 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- Gen4RomHandler.java - randomizer handler for D/P/Pt/HG/SS. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.awt.image.BufferedImage; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import com.pkrandom.*; +import com.pkrandom.constants.*; +import com.pkrandom.exceptions.RandomizationException; +import com.pkrandom.pokemon.*; +import thenewpoketext.PokeTextData; +import thenewpoketext.TextToPoke; + +import com.pkrandom.exceptions.RandomizerIOException; +import com.pkrandom.newnds.NARCArchive; + +public class Gen4RomHandler extends AbstractDSRomHandler { + + public static class Factory extends RomHandler.Factory { + + @Override + public Gen4RomHandler create(Random random, PrintStream logStream) { + return new Gen4RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + return detectNDSRomInner(getROMCodeFromFile(filename), getVersionFromFile(filename)); + } + } + + public Gen4RomHandler(Random random) { + super(random, null); + } + + public Gen4RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + private static class RomFileEntry { + public String path; + public long expectedCRC32; + } + + private static class RomEntry { + private String name; + private String romCode; + private byte version; + private int romType; + private long arm9ExpectedCRC32; + private boolean staticPokemonSupport = false, copyStaticPokemon = false,copyRoamingPokemon = false, + ignoreGameCornerStatics = false, copyText = false; + private Map strings = new HashMap<>(); + private Map tweakFiles = new HashMap<>(); + private Map numbers = new HashMap<>(); + private Map arrayEntries = new HashMap<>(); + private Map files = new HashMap<>(); + private Map overlayExpectedCRC32s = new HashMap<>(); + private List staticPokemon = new ArrayList<>(); + private List roamingPokemon = new ArrayList<>(); + private List marillCryScriptEntries = new ArrayList<>(); + private Map> tmTexts = new HashMap<>(); + private Map tmTextsGameCorner = new HashMap<>(); + private Map tmScriptOffsetsFrontier = new HashMap<>(); + private Map tmTextsFrontier = new HashMap<>(); + + private int getInt(String key) { + if (!numbers.containsKey(key)) { + numbers.put(key, 0); + } + return numbers.get(key); + } + + private String getString(String key) { + if (!strings.containsKey(key)) { + strings.put(key, ""); + } + return strings.get(key); + } + + private String getFile(String key) { + if (!files.containsKey(key)) { + files.put(key, new RomFileEntry()); + } + return files.get(key).path; + } + } + + private static List roms; + + static { + loadROMInfo(); + + } + + private static void loadROMInfo() { + roms = new ArrayList<>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen4_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + if (r[0].equals("Game")) { + current.romCode = r[1]; + } else if (r[0].equals("Version")) { + current.version = Byte.parseByte(r[1]); + } else if (r[0].equals("Type")) { + if (r[1].equalsIgnoreCase("DP")) { + current.romType = Gen4Constants.Type_DP; + } else if (r[1].equalsIgnoreCase("Plat")) { + current.romType = Gen4Constants.Type_Plat; + } else if (r[1].equalsIgnoreCase("HGSS")) { + current.romType = Gen4Constants.Type_HGSS; + } else { + System.err.println("unrecognised rom type: " + r[1]); + } + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.name)) { + // copy from here + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.numbers.putAll(otherEntry.numbers); + current.strings.putAll(otherEntry.strings); + current.files.putAll(otherEntry.files); + if (current.copyStaticPokemon) { + current.staticPokemon.addAll(otherEntry.staticPokemon); + if (current.ignoreGameCornerStatics) { + current.staticPokemon.removeIf(staticPokemon -> staticPokemon instanceof StaticPokemonGameCorner); + } + current.staticPokemonSupport = true; + } else { + current.staticPokemonSupport = false; + } + if (current.copyRoamingPokemon) { + current.roamingPokemon.addAll(otherEntry.roamingPokemon); + } + if (current.copyText) { + current.tmTexts.putAll(otherEntry.tmTexts); + current.tmTextsGameCorner.putAll(otherEntry.tmTextsGameCorner); + current.tmScriptOffsetsFrontier.putAll(otherEntry.tmScriptOffsetsFrontier); + current.tmTextsFrontier.putAll(otherEntry.tmTextsFrontier); + } + current.marillCryScriptEntries.addAll(otherEntry.marillCryScriptEntries); + } + } + } else if (r[0].startsWith("File<")) { + String key = r[0].split("<")[1].split(">")[0]; + String[] values = r[1].substring(1, r[1].length() - 1).split(","); + RomFileEntry entry = new RomFileEntry(); + entry.path = values[0].trim(); + entry.expectedCRC32 = parseRILong("0x" + values[1].trim()); + current.files.put(key, entry); + } else if (r[0].equals("Arm9CRC32")) { + current.arm9ExpectedCRC32 = parseRILong("0x" + r[1]); + } else if (r[0].startsWith("OverlayCRC32<")) { + String keyString = r[0].split("<")[1].split(">")[0]; + int key = parseRIInt(keyString); + long value = parseRILong("0x" + r[1]); + current.overlayExpectedCRC32s.put(key, value); + } else if (r[0].equals("StaticPokemon{}")) { + current.staticPokemon.add(parseStaticPokemon(r[1])); + } else if (r[0].equals("RoamingPokemon{}")) { + current.roamingPokemon.add(parseRoamingPokemon(r[1])); + } else if (r[0].equals("StaticPokemonGameCorner{}")) { + current.staticPokemon.add(parseStaticPokemonGameCorner(r[1])); + } else if (r[0].equals("TMText{}")) { + parseTMText(r[1], current.tmTexts); + } else if (r[0].equals("TMTextGameCorner{}")) { + parseTMTextGameCorner(r[1], current.tmTextsGameCorner); + } else if (r[0].equals("FrontierScriptTMOffsets{}")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + for (String off : offsets) { + String[] parts = off.split("="); + int tmNum = parseRIInt(parts[0]); + int offset = parseRIInt(parts[1]); + current.tmScriptOffsetsFrontier.put(tmNum, offset); + } + } else if (r[0].equals("FrontierTMText{}")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + for (String off : offsets) { + String[] parts = off.split("="); + int tmNum = parseRIInt(parts[0]); + int stringNumber = parseRIInt(parts[1]); + current.tmTextsFrontier.put(tmNum, stringNumber); + } + } else if (r[0].equals("StaticPokemonSupport")) { + int spsupport = parseRIInt(r[1]); + current.staticPokemonSupport = (spsupport > 0); + } else if (r[0].equals("CopyStaticPokemon")) { + int csp = parseRIInt(r[1]); + current.copyStaticPokemon = (csp > 0); + } else if (r[0].equals("CopyRoamingPokemon")) { + int crp = parseRIInt(r[1]); + current.copyRoamingPokemon = (crp > 0); + } else if (r[0].equals("CopyText")) { + int ct = parseRIInt(r[1]); + current.copyText = (ct > 0); + } else if (r[0].equals("IgnoreGameCornerStatics")) { + int ct = parseRIInt(r[1]); + current.ignoreGameCornerStatics = (ct > 0); + } else if (r[0].endsWith("Tweak")) { + current.tweakFiles.put(r[0], r[1]); + } else if (r[0].endsWith("MarillCryScripts")) { + current.marillCryScriptEntries.clear(); + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + for (String off : offsets) { + String[] parts = off.split(":"); + int file = parseRIInt(parts[0]); + int offset = parseRIInt(parts[1]); + ScriptEntry entry = new ScriptEntry(file, offset); + current.marillCryScriptEntries.add(entry); + } + } else { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.arrayEntries.put(r[0], offs); + } + } else if (r[0].endsWith("Offset") || r[0].endsWith("Count") || r[0].endsWith("Number") + || r[0].endsWith("Size") || r[0].endsWith("Index")) { + int offs = parseRIInt(r[1]); + current.numbers.put(r[0], offs); + } else { + current.strings.put(r[0], r[1]); + } + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + System.err.println("File not found!"); + } + + } + + private static int parseRIInt(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Integer.parseInt(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + private static long parseRILong(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Long.parseLong(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + private static StaticPokemon parseStaticPokemon(String staticPokemonString) { + StaticPokemon sp = new StaticPokemon(); + String pattern = "[A-z]+=\\[([0-9]+:0x[0-9a-fA-F]+,?\\s?)+]"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(staticPokemonString); + while (m.find()) { + String[] segments = m.group().split("="); + String[] offsets = segments[1].substring(1, segments[1].length() - 1).split(","); + ScriptEntry[] entries = new ScriptEntry[offsets.length]; + for (int i = 0; i < entries.length; i++) { + String[] parts = offsets[i].split(":"); + entries[i] = new ScriptEntry(parseRIInt(parts[0]), parseRIInt(parts[1])); + } + switch (segments[0]) { + case "Species": + sp.speciesEntries = entries; + break; + case "Level": + sp.levelEntries = entries; + break; + case "Forme": + sp.formeEntries = entries; + break; + } + } + return sp; + } + + private static StaticPokemonGameCorner parseStaticPokemonGameCorner(String staticPokemonString) { + StaticPokemonGameCorner sp = new StaticPokemonGameCorner(); + String pattern = "[A-z]+=\\[([0-9]+:0x[0-9a-fA-F]+,?\\s?)+]"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(staticPokemonString); + while (m.find()) { + String[] segments = m.group().split("="); + String[] offsets = segments[1].substring(1, segments[1].length() - 1).split(","); + switch (segments[0]) { + case "Species": + ScriptEntry[] speciesEntries = new ScriptEntry[offsets.length]; + for (int i = 0; i < speciesEntries.length; i++) { + String[] parts = offsets[i].split(":"); + speciesEntries[i] = new ScriptEntry(parseRIInt(parts[0]), parseRIInt(parts[1])); + } + sp.speciesEntries = speciesEntries; + break; + case "Level": + ScriptEntry[] levelEntries = new ScriptEntry[offsets.length]; + for (int i = 0; i < levelEntries.length; i++) { + String[] parts = offsets[i].split(":"); + levelEntries[i] = new ScriptEntry(parseRIInt(parts[0]), parseRIInt(parts[1])); + } + sp.levelEntries = levelEntries; + break; + case "Text": + TextEntry[] textEntries = new TextEntry[offsets.length]; + for (int i = 0; i < textEntries.length; i++) { + String[] parts = offsets[i].split(":"); + textEntries[i] = new TextEntry(parseRIInt(parts[0]), parseRIInt(parts[1])); + } + sp.textEntries = textEntries; + break; + } + } + return sp; + } + + private static RoamingPokemon parseRoamingPokemon(String roamingPokemonString) { + RoamingPokemon rp = new RoamingPokemon(); + String pattern = "[A-z]+=\\[(0x[0-9a-fA-F]+,?\\s?)+]|[A-z]+=\\[([0-9]+:0x[0-9a-fA-F]+,?\\s?)+]"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(roamingPokemonString); + while (m.find()) { + String[] segments = m.group().split("="); + String[] offsets = segments[1].substring(1, segments[1].length() - 1).split(","); + switch (segments[0]) { + case "Species": + int[] speciesCodeOffsets = new int[offsets.length]; + for (int i = 0; i < speciesCodeOffsets.length; i++) { + speciesCodeOffsets[i] = parseRIInt(offsets[i]); + } + rp.speciesCodeOffsets = speciesCodeOffsets; + break; + case "Level": + int[] levelCodeOffsets = new int[offsets.length]; + for (int i = 0; i < levelCodeOffsets.length; i++) { + levelCodeOffsets[i] = parseRIInt(offsets[i]); + } + rp.levelCodeOffsets = levelCodeOffsets; + break; + case "Script": + ScriptEntry[] scriptEntries = new ScriptEntry[offsets.length]; + for (int i = 0; i < scriptEntries.length; i++) { + String[] parts = offsets[i].split(":"); + scriptEntries[i] = new ScriptEntry(parseRIInt(parts[0]), parseRIInt(parts[1])); + } + rp.speciesScriptOffsets = scriptEntries; + break; + case "Gender": + ScriptEntry[] genderEntries = new ScriptEntry[offsets.length]; + for (int i = 0; i < genderEntries.length; i++) { + String[] parts = offsets[i].split(":"); + genderEntries[i] = new ScriptEntry(parseRIInt(parts[0]), parseRIInt(parts[1])); + } + rp.genderOffsets = genderEntries; + break; + } + } + return rp; + } + + private static void parseTMText(String tmTextString, Map> tmTexts) { + String pattern = "[0-9]+=\\[([0-9]+:[0-9]+,?\\s?)+]"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(tmTextString); + while (m.find()) { + String[] segments = m.group().split("="); + int tmNum = parseRIInt(segments[0]); + String[] entries = segments[1].substring(1, segments[1].length() - 1).split(","); + List textEntries = new ArrayList<>(); + for (String entry : entries) { + String[] textSegments = entry.split(":"); + TextEntry textEntry = new TextEntry(parseRIInt(textSegments[0]), parseRIInt(textSegments[1])); + textEntries.add(textEntry); + } + tmTexts.put(tmNum, textEntries); + } + } + + private static void parseTMTextGameCorner(String tmTextGameCornerString, Map tmTextGameCorner) { + String[] tmTextGameCornerEntries = tmTextGameCornerString.substring(1, tmTextGameCornerString.length() - 1).split(","); + for (String tmTextGameCornerEntry : tmTextGameCornerEntries) { + String[] segments = tmTextGameCornerEntry.trim().split("="); + int tmNum = parseRIInt(segments[0]); + String textEntry = segments[1].substring(1, segments[1].length() - 1); + String[] textSegments = textEntry.split(":"); + TextEntry entry = new TextEntry(parseRIInt(textSegments[0]), parseRIInt(textSegments[1])); + tmTextGameCorner.put(tmNum, entry); + } + } + + // This rom + private Pokemon[] pokes; + private List pokemonListInclFormes; + private List pokemonList; + private Move[] moves; + private NARCArchive pokeNarc, moveNarc; + private NARCArchive msgNarc; + private NARCArchive scriptNarc; + private NARCArchive eventNarc; + private byte[] arm9; + private List abilityNames; + private List itemNames; + private boolean loadedWildMapNames; + private Map wildMapNames, headbuttMapNames; + private ItemList allowedItems, nonBadItems; + private boolean roamerRandomizationEnabled; + private boolean effectivenessUpdated; + private int pickupItemsTableOffset, rarePickupItemsTableOffset; + private long actualArm9CRC32; + private Map actualOverlayCRC32s; + private Map actualFileCRC32s; + + private RomEntry romEntry; + + @Override + protected boolean detectNDSRom(String ndsCode, byte version) { + return detectNDSRomInner(ndsCode, version); + } + + private static boolean detectNDSRomInner(String ndsCode, byte version) { + return entryFor(ndsCode, version) != null; + } + + private static RomEntry entryFor(String ndsCode, byte version) { + for (RomEntry re : roms) { + if (ndsCode.equals(re.romCode) && version == re.version) { + return re; + } + } + return null; + } + + @Override + protected void loadedROM(String romCode, byte version) { + this.romEntry = entryFor(romCode, version); + try { + arm9 = readARM9(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + try { + msgNarc = readNARC(romEntry.getFile("Text")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + try { + scriptNarc = readNARC(romEntry.getFile("Scripts")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + try { + eventNarc = readNARC(romEntry.getFile("Events")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + loadPokemonStats(); + pokemonListInclFormes = Arrays.asList(pokes); + pokemonList = Arrays.asList(Arrays.copyOfRange(pokes,0,Gen4Constants.pokemonCount + 1)); + loadMoves(); + abilityNames = getStrings(romEntry.getInt("AbilityNamesTextOffset")); + itemNames = getStrings(romEntry.getInt("ItemNamesTextOffset")); + loadedWildMapNames = false; + + allowedItems = Gen4Constants.allowedItems.copy(); + nonBadItems = Gen4Constants.nonBadItems.copy(); + + roamerRandomizationEnabled = + (romEntry.romType == Gen4Constants.Type_DP && romEntry.roamingPokemon.size() > 0) || + (romEntry.romType == Gen4Constants.Type_Plat && romEntry.tweakFiles.containsKey("NewRoamerSubroutineTweak")) || + (romEntry.romType == Gen4Constants.Type_HGSS && romEntry.tweakFiles.containsKey("NewRoamerSubroutineTweak")); + + try { + computeCRC32sForRom(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + // We want to guarantee that the catching tutorial in HGSS has Ethan/Lyra's new Pokemon. We also + // want to allow the option of randomizing the enemy Pokemon too. Unfortunately, the latter can + // occur *before* the former, but there's no guarantee that it will even happen. Since we *know* + // we'll need to do this patch eventually, just expand the arm9 here to make things easy. + if (romEntry.romType == Gen4Constants.Type_HGSS && romEntry.tweakFiles.containsKey("NewCatchingTutorialSubroutineTweak")) { + int extendBy = romEntry.getInt("Arm9ExtensionSize"); + arm9 = extendARM9(arm9, extendBy, romEntry.getString("TCMCopyingPrefix"), Gen4Constants.arm9Offset); + genericIPSPatch(arm9, "NewCatchingTutorialSubroutineTweak"); + } + } + + private void loadMoves() { + try { + moveNarc = this.readNARC(romEntry.getFile("MoveData")); + moves = new Move[Gen4Constants.moveCount + 1]; + List moveNames = getStrings(romEntry.getInt("MoveNamesTextOffset")); + for (int i = 1; i <= Gen4Constants.moveCount; i++) { + byte[] moveData = moveNarc.files.get(i); + moves[i] = new Move(); + moves[i].name = moveNames.get(i); + moves[i].number = i; + moves[i].internalId = i; + moves[i].effectIndex = readWord(moveData, 0); + moves[i].hitratio = (moveData[5] & 0xFF); + moves[i].power = moveData[3] & 0xFF; + moves[i].pp = moveData[6] & 0xFF; + moves[i].type = Gen4Constants.typeTable[moveData[4] & 0xFF]; + moves[i].target = readWord(moveData, 8); + moves[i].category = Gen4Constants.moveCategoryIndices[moveData[2] & 0xFF]; + moves[i].priority = moveData[10]; + int flags = moveData[11] & 0xFF; + moves[i].makesContact = (flags & 1) != 0; + moves[i].isPunchMove = Gen4Constants.punchMoves.contains(moves[i].number); + moves[i].isSoundMove = Gen4Constants.soundMoves.contains(moves[i].number); + + if (i == Moves.swift) { + perfectAccuracy = (int)moves[i].hitratio; + } + + if (GlobalConstants.normalMultihitMoves.contains(i)) { + moves[i].hitCount = 3; + } else if (GlobalConstants.doubleHitMoves.contains(i)) { + moves[i].hitCount = 2; + } else if (i == Moves.tripleKick) { + moves[i].hitCount = 2.71; // this assumes the first hit lands + } + + int secondaryEffectChance = moveData[7] & 0xFF; + loadStatChangesFromEffect(moves[i], secondaryEffectChance); + loadStatusFromEffect(moves[i], secondaryEffectChance); + loadMiscMoveInfoFromEffect(moves[i], secondaryEffectChance); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void loadStatChangesFromEffect(Move move, int secondaryEffectChance) { + switch (move.effectIndex) { + case Gen4Constants.noDamageAtkPlusOneEffect: + case Gen4Constants.noDamageDefPlusOneEffect: + case Gen4Constants.noDamageSpAtkPlusOneEffect: + case Gen4Constants.noDamageEvasionPlusOneEffect: + case Gen4Constants.noDamageAtkMinusOneEffect: + case Gen4Constants.noDamageDefMinusOneEffect: + case Gen4Constants.noDamageSpeMinusOneEffect: + case Gen4Constants.noDamageAccuracyMinusOneEffect: + case Gen4Constants.noDamageEvasionMinusOneEffect: + case Gen4Constants.noDamageAtkPlusTwoEffect: + case Gen4Constants.noDamageDefPlusTwoEffect: + case Gen4Constants.noDamageSpePlusTwoEffect: + case Gen4Constants.noDamageSpAtkPlusTwoEffect: + case Gen4Constants.noDamageSpDefPlusTwoEffect: + case Gen4Constants.noDamageAtkMinusTwoEffect: + case Gen4Constants.noDamageDefMinusTwoEffect: + case Gen4Constants.noDamageSpeMinusTwoEffect: + case Gen4Constants.noDamageSpDefMinusTwoEffect: + case Gen4Constants.minimizeEffect: + case Gen4Constants.swaggerEffect: + case Gen4Constants.defenseCurlEffect: + case Gen4Constants.flatterEffect: + case Gen4Constants.chargeEffect: + case Gen4Constants.noDamageAtkAndDefMinusOneEffect: + case Gen4Constants.noDamageDefAndSpDefPlusOneEffect: + case Gen4Constants.noDamageAtkAndDefPlusOneEffect: + case Gen4Constants.noDamageSpAtkAndSpDefPlusOneEffect: + case Gen4Constants.noDamageAtkAndSpePlusOneEffect: + case Gen4Constants.noDamageSpAtkMinusTwoEffect: + if (move.target == 16) { + move.statChangeMoveType = StatChangeMoveType.NO_DAMAGE_USER; + } else { + move.statChangeMoveType = StatChangeMoveType.NO_DAMAGE_TARGET; + } + break; + + case Gen4Constants.damageAtkMinusOneEffect: + case Gen4Constants.damageDefMinusOneEffect: + case Gen4Constants.damageSpeMinusOneEffect: + case Gen4Constants.damageSpAtkMinusOneEffect: + case Gen4Constants.damageSpDefMinusOneEffect: + case Gen4Constants.damageAccuracyMinusOneEffect: + case Gen4Constants.damageSpDefMinusTwoEffect: + move.statChangeMoveType = StatChangeMoveType.DAMAGE_TARGET; + break; + + case Gen4Constants.damageUserDefPlusOneEffect: + case Gen4Constants.damageUserAtkPlusOneEffect: + case Gen4Constants.damageUserAllPlusOneEffect: + case Gen4Constants.damageUserAtkAndDefMinusOneEffect: + case Gen4Constants.damageUserSpAtkMinusTwoEffect: + case Gen4Constants.damageUserSpeMinusOneEffect: + case Gen4Constants.damageUserDefAndSpDefMinusOneEffect: + case Gen4Constants.damageUserSpAtkPlusOneEffect: + move.statChangeMoveType = StatChangeMoveType.DAMAGE_USER; + break; + + default: + // Move does not have a stat-changing effect + return; + } + + switch (move.effectIndex) { + case Gen4Constants.noDamageAtkPlusOneEffect: + case Gen4Constants.damageUserAtkPlusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 1; + break; + case Gen4Constants.noDamageDefPlusOneEffect: + case Gen4Constants.damageUserDefPlusOneEffect: + case Gen4Constants.defenseCurlEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = 1; + break; + case Gen4Constants.noDamageSpAtkPlusOneEffect: + case Gen4Constants.flatterEffect: + case Gen4Constants.damageUserSpAtkPlusOneEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_ATTACK; + move.statChanges[0].stages = 1; + break; + case Gen4Constants.noDamageEvasionPlusOneEffect: + case Gen4Constants.minimizeEffect: + move.statChanges[0].type = StatChangeType.EVASION; + move.statChanges[0].stages = 1; + break; + case Gen4Constants.noDamageAtkMinusOneEffect: + case Gen4Constants.damageAtkMinusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = -1; + break; + case Gen4Constants.noDamageDefMinusOneEffect: + case Gen4Constants.damageDefMinusOneEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = -1; + break; + case Gen4Constants.noDamageSpeMinusOneEffect: + case Gen4Constants.damageSpeMinusOneEffect: + case Gen4Constants.damageUserSpeMinusOneEffect: + move.statChanges[0].type = StatChangeType.SPEED; + move.statChanges[0].stages = -1; + break; + case Gen4Constants.noDamageAccuracyMinusOneEffect: + case Gen4Constants.damageAccuracyMinusOneEffect: + move.statChanges[0].type = StatChangeType.ACCURACY; + move.statChanges[0].stages = -1; + break; + case Gen4Constants.noDamageEvasionMinusOneEffect: + move.statChanges[0].type = StatChangeType.EVASION; + move.statChanges[0].stages = -1; + break; + case Gen4Constants.noDamageAtkPlusTwoEffect: + case Gen4Constants.swaggerEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 2; + break; + case Gen4Constants.noDamageDefPlusTwoEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = 2; + break; + case Gen4Constants.noDamageSpePlusTwoEffect: + move.statChanges[0].type = StatChangeType.SPEED; + move.statChanges[0].stages = 2; + break; + case Gen4Constants.noDamageSpAtkPlusTwoEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_ATTACK; + move.statChanges[0].stages = 2; + break; + case Gen4Constants.noDamageSpDefPlusTwoEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[0].stages = 2; + break; + case Gen4Constants.noDamageAtkMinusTwoEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = -2; + break; + case Gen4Constants.noDamageDefMinusTwoEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = -2; + break; + case Gen4Constants.noDamageSpeMinusTwoEffect: + move.statChanges[0].type = StatChangeType.SPEED; + move.statChanges[0].stages = -2; + break; + case Gen4Constants.noDamageSpDefMinusTwoEffect: + case Gen4Constants.damageSpDefMinusTwoEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[0].stages = -2; + break; + case Gen4Constants.damageSpAtkMinusOneEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_ATTACK; + move.statChanges[0].stages = -1; + break; + case Gen4Constants.damageSpDefMinusOneEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[0].stages = -1; + break; + case Gen4Constants.damageUserAllPlusOneEffect: + move.statChanges[0].type = StatChangeType.ALL; + move.statChanges[0].stages = 1; + break; + case Gen4Constants.chargeEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[0].stages = 1; + break; + case Gen4Constants.damageUserAtkAndDefMinusOneEffect: + case Gen4Constants.noDamageAtkAndDefMinusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = -1; + move.statChanges[1].type = StatChangeType.DEFENSE; + move.statChanges[1].stages = -1; + break; + case Gen4Constants.damageUserSpAtkMinusTwoEffect: + case Gen4Constants.noDamageSpAtkMinusTwoEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_ATTACK; + move.statChanges[0].stages = -2; + break; + case Gen4Constants.noDamageDefAndSpDefPlusOneEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = 1; + move.statChanges[1].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[1].stages = 1; + break; + case Gen4Constants.noDamageAtkAndDefPlusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 1; + move.statChanges[1].type = StatChangeType.DEFENSE; + move.statChanges[1].stages = 1; + break; + case Gen4Constants.noDamageSpAtkAndSpDefPlusOneEffect: + move.statChanges[0].type = StatChangeType.SPECIAL_ATTACK; + move.statChanges[0].stages = 1; + move.statChanges[1].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[1].stages = 1; + break; + case Gen4Constants.noDamageAtkAndSpePlusOneEffect: + move.statChanges[0].type = StatChangeType.ATTACK; + move.statChanges[0].stages = 1; + move.statChanges[1].type = StatChangeType.SPEED; + move.statChanges[1].stages = 1; + break; + case Gen4Constants.damageUserDefAndSpDefMinusOneEffect: + move.statChanges[0].type = StatChangeType.DEFENSE; + move.statChanges[0].stages = -1; + move.statChanges[1].type = StatChangeType.SPECIAL_DEFENSE; + move.statChanges[1].stages = -1; + break; + } + + if (move.statChangeMoveType == StatChangeMoveType.DAMAGE_TARGET || move.statChangeMoveType == StatChangeMoveType.DAMAGE_USER) { + for (int i = 0; i < move.statChanges.length; i++) { + if (move.statChanges[i].type != StatChangeType.NONE) { + move.statChanges[i].percentChance = secondaryEffectChance; + if (move.statChanges[i].percentChance == 0.0) { + move.statChanges[i].percentChance = 100.0; + } + } + } + } + } + + private void loadStatusFromEffect(Move move, int secondaryEffectChance) { + switch (move.effectIndex) { + case Gen4Constants.noDamageSleepEffect: + case Gen4Constants.toxicEffect: + case Gen4Constants.noDamageConfusionEffect: + case Gen4Constants.noDamagePoisonEffect: + case Gen4Constants.noDamageParalyzeEffect: + case Gen4Constants.noDamageBurnEffect: + case Gen4Constants.swaggerEffect: + case Gen4Constants.flatterEffect: + case Gen4Constants.teeterDanceEffect: + move.statusMoveType = StatusMoveType.NO_DAMAGE; + break; + + case Gen4Constants.damagePoisonEffect: + case Gen4Constants.damageBurnEffect: + case Gen4Constants.damageFreezeEffect: + case Gen4Constants.damageParalyzeEffect: + case Gen4Constants.damageConfusionEffect: + case Gen4Constants.twineedleEffect: + case Gen4Constants.damageBurnAndThawUserEffect: + case Gen4Constants.thunderEffect: + case Gen4Constants.blazeKickEffect: + case Gen4Constants.poisonFangEffect: + case Gen4Constants.damagePoisonWithIncreasedCritEffect: + case Gen4Constants.flareBlitzEffect: + case Gen4Constants.blizzardEffect: + case Gen4Constants.voltTackleEffect: + case Gen4Constants.bounceEffect: + case Gen4Constants.chatterEffect: + case Gen4Constants.fireFangEffect: + case Gen4Constants.iceFangEffect: + case Gen4Constants.thunderFangEffect: + move.statusMoveType = StatusMoveType.DAMAGE; + break; + + default: + // Move does not have a status effect + return; + } + + switch (move.effectIndex) { + case Gen4Constants.noDamageSleepEffect: + move.statusType = StatusType.SLEEP; + break; + case Gen4Constants.damagePoisonEffect: + case Gen4Constants.noDamagePoisonEffect: + case Gen4Constants.twineedleEffect: + case Gen4Constants.damagePoisonWithIncreasedCritEffect: + move.statusType = StatusType.POISON; + break; + case Gen4Constants.damageBurnEffect: + case Gen4Constants.damageBurnAndThawUserEffect: + case Gen4Constants.noDamageBurnEffect: + case Gen4Constants.blazeKickEffect: + case Gen4Constants.flareBlitzEffect: + case Gen4Constants.fireFangEffect: + move.statusType = StatusType.BURN; + break; + case Gen4Constants.damageFreezeEffect: + case Gen4Constants.blizzardEffect: + case Gen4Constants.iceFangEffect: + move.statusType = StatusType.FREEZE; + break; + case Gen4Constants.damageParalyzeEffect: + case Gen4Constants.noDamageParalyzeEffect: + case Gen4Constants.thunderEffect: + case Gen4Constants.voltTackleEffect: + case Gen4Constants.bounceEffect: + case Gen4Constants.thunderFangEffect: + move.statusType = StatusType.PARALYZE; + break; + case Gen4Constants.toxicEffect: + case Gen4Constants.poisonFangEffect: + move.statusType = StatusType.TOXIC_POISON; + break; + case Gen4Constants.noDamageConfusionEffect: + case Gen4Constants.damageConfusionEffect: + case Gen4Constants.swaggerEffect: + case Gen4Constants.flatterEffect: + case Gen4Constants.teeterDanceEffect: + case Gen4Constants.chatterEffect: + move.statusType = StatusType.CONFUSION; + break; + } + + if (move.statusMoveType == StatusMoveType.DAMAGE) { + move.statusPercentChance = secondaryEffectChance; + if (move.statusPercentChance == 0.0) { + if (move.number == Moves.chatter) { + move.statusPercentChance = 1.0; + } else { + move.statusPercentChance = 100.0; + } + } + } + } + + private void loadMiscMoveInfoFromEffect(Move move, int secondaryEffectChance) { + switch (move.effectIndex) { + case Gen4Constants.increasedCritEffect: + case Gen4Constants.blazeKickEffect: + case Gen4Constants.damagePoisonWithIncreasedCritEffect: + move.criticalChance = CriticalChance.INCREASED; + break; + + case Gen4Constants.futureSightAndDoomDesireEffect: + move.criticalChance = CriticalChance.NONE; + + case Gen4Constants.flinchEffect: + case Gen4Constants.snoreEffect: + case Gen4Constants.twisterEffect: + case Gen4Constants.stompEffect: + case Gen4Constants.fakeOutEffect: + case Gen4Constants.fireFangEffect: + case Gen4Constants.iceFangEffect: + case Gen4Constants.thunderFangEffect: + move.flinchPercentChance = secondaryEffectChance; + break; + + case Gen4Constants.damageAbsorbEffect: + case Gen4Constants.dreamEaterEffect: + move.absorbPercent = 50; + break; + + case Gen4Constants.damageRecoil25PercentEffect: + move.recoilPercent = 25; + break; + + case Gen4Constants.damageRecoil33PercentEffect: + case Gen4Constants.flareBlitzEffect: + case Gen4Constants.voltTackleEffect: + move.recoilPercent = 33; + break; + + case Gen4Constants.damageRecoil50PercentEffect: + move.recoilPercent = 50; + break; + + case Gen4Constants.bindingEffect: + case Gen4Constants.trappingEffect: + move.isTrapMove = true; + break; + + case Gen4Constants.skullBashEffect: + case Gen4Constants.solarbeamEffect: + case Gen4Constants.flyEffect: + case Gen4Constants.diveEffect: + case Gen4Constants.digEffect: + case Gen4Constants.bounceEffect: + case Gen4Constants.shadowForceEffect: + move.isChargeMove = true; + break; + + case Gen3Constants.rechargeEffect: + move.isRechargeMove = true; + break; + + case Gen4Constants.razorWindEffect: + move.criticalChance = CriticalChance.INCREASED; + move.isChargeMove = true; + break; + + case Gen4Constants.skyAttackEffect: + move.criticalChance = CriticalChance.INCREASED; + move.flinchPercentChance = secondaryEffectChance; + move.isChargeMove = true; + break; + } + } + + private void loadPokemonStats() { + try { + String pstatsnarc = romEntry.getFile("PokemonStats"); + pokeNarc = this.readNARC(pstatsnarc); + String[] pokeNames = readPokemonNames(); + int formeCount = Gen4Constants.getFormeCount(romEntry.romType); + pokes = new Pokemon[Gen4Constants.pokemonCount + formeCount + 1]; + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i], pokeNarc.files.get(i)); + // Name? + pokes[i].name = pokeNames[i]; + } + + int i = Gen4Constants.pokemonCount + 1; + for (int k: Gen4Constants.formeMappings.keySet()) { + if (i >= pokes.length) { + break; + } + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i], pokeNarc.files.get(k)); + pokes[i].name = pokeNames[Gen4Constants.formeMappings.get(k).baseForme]; + pokes[i].baseForme = pokes[Gen4Constants.formeMappings.get(k).baseForme]; + pokes[i].formeNumber = Gen4Constants.formeMappings.get(k).formeNumber; + pokes[i].formeSuffix = Gen4Constants.formeSuffixes.get(k); + i = i + 1; + } + + populateEvolutions(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + private void loadBasicPokeStats(Pokemon pkmn, byte[] stats) { + pkmn.hp = stats[Gen4Constants.bsHPOffset] & 0xFF; + pkmn.attack = stats[Gen4Constants.bsAttackOffset] & 0xFF; + pkmn.defense = stats[Gen4Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = stats[Gen4Constants.bsSpeedOffset] & 0xFF; + pkmn.spatk = stats[Gen4Constants.bsSpAtkOffset] & 0xFF; + pkmn.spdef = stats[Gen4Constants.bsSpDefOffset] & 0xFF; + // Type + pkmn.primaryType = Gen4Constants.typeTable[stats[Gen4Constants.bsPrimaryTypeOffset] & 0xFF]; + pkmn.secondaryType = Gen4Constants.typeTable[stats[Gen4Constants.bsSecondaryTypeOffset] & 0xFF]; + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + pkmn.catchRate = stats[Gen4Constants.bsCatchRateOffset] & 0xFF; + pkmn.growthCurve = ExpCurve.fromByte(stats[Gen4Constants.bsGrowthCurveOffset]); + + // Abilities + pkmn.ability1 = stats[Gen4Constants.bsAbility1Offset] & 0xFF; + pkmn.ability2 = stats[Gen4Constants.bsAbility2Offset] & 0xFF; + + // Held Items? + int item1 = readWord(stats, Gen4Constants.bsCommonHeldItemOffset); + int item2 = readWord(stats, Gen4Constants.bsRareHeldItemOffset); + + if (item1 == item2) { + // guaranteed + pkmn.guaranteedHeldItem = item1; + pkmn.commonHeldItem = 0; + pkmn.rareHeldItem = 0; + } else { + pkmn.guaranteedHeldItem = 0; + pkmn.commonHeldItem = item1; + pkmn.rareHeldItem = item2; + } + pkmn.darkGrassHeldItem = -1; + + pkmn.genderRatio = stats[Gen4Constants.bsGenderRatioOffset] & 0xFF; + + int cosmeticForms = Gen4Constants.cosmeticForms.getOrDefault(pkmn.number,0); + if (cosmeticForms > 0 && romEntry.romType != Gen4Constants.Type_DP) { + pkmn.cosmeticForms = cosmeticForms; + } + } + + private String[] readPokemonNames() { + String[] pokeNames = new String[Gen4Constants.pokemonCount + 1]; + List nameList = getStrings(romEntry.getInt("PokemonNamesTextOffset")); + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + pokeNames[i] = nameList.get(i); + } + return pokeNames; + } + + @Override + protected void savingROM() { + savePokemonStats(); + saveMoves(); + try { + writeARM9(arm9); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + try { + writeNARC(romEntry.getFile("Text"), msgNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + try { + writeNARC(romEntry.getFile("Scripts"), scriptNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + try { + writeNARC(romEntry.getFile("Events"), eventNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void saveMoves() { + for (int i = 1; i <= Gen4Constants.moveCount; i++) { + byte[] data = moveNarc.files.get(i); + writeWord(data, 0, moves[i].effectIndex); + data[2] = Gen4Constants.moveCategoryToByte(moves[i].category); + data[3] = (byte) moves[i].power; + data[4] = Gen4Constants.typeToByte(moves[i].type); + int hitratio = (int) Math.round(moves[i].hitratio); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 100) { + hitratio = 100; + } + data[5] = (byte) hitratio; + data[6] = (byte) moves[i].pp; + } + + try { + this.writeNARC(romEntry.getFile("MoveData"), moveNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + private void savePokemonStats() { + // Update the "a/an X" list too, if it exists + List namesList = getStrings(romEntry.getInt("PokemonNamesTextOffset")); + int formeCount = Gen4Constants.getFormeCount(romEntry.romType); + if (romEntry.getString("HasExtraPokemonNames").equalsIgnoreCase("Yes")) { + List namesList2 = getStrings(romEntry.getInt("PokemonNamesTextOffset") + 1); + for (int i = 1; i <= Gen4Constants.pokemonCount + formeCount; i++) { + if (i > Gen4Constants.pokemonCount) { + saveBasicPokeStats(pokes[i], pokeNarc.files.get(i + Gen4Constants.formeOffset)); + continue; + } + saveBasicPokeStats(pokes[i], pokeNarc.files.get(i)); + String oldName = namesList.get(i); + namesList.set(i, pokes[i].name); + namesList2.set(i, namesList2.get(i).replace(oldName, pokes[i].name)); + } + setStrings(romEntry.getInt("PokemonNamesTextOffset") + 1, namesList2, false); + } else { + for (int i = 1; i <= Gen4Constants.pokemonCount + formeCount; i++) { + if (i > Gen4Constants.pokemonCount) { + saveBasicPokeStats(pokes[i], pokeNarc.files.get(i + Gen4Constants.formeOffset)); + continue; + } + saveBasicPokeStats(pokes[i], pokeNarc.files.get(i)); + namesList.set(i, pokes[i].name); + } + } + setStrings(romEntry.getInt("PokemonNamesTextOffset"), namesList, false); + + try { + String pstatsnarc = romEntry.getFile("PokemonStats"); + this.writeNARC(pstatsnarc, pokeNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + writeEvolutions(); + + } + + private void saveBasicPokeStats(Pokemon pkmn, byte[] stats) { + stats[Gen4Constants.bsHPOffset] = (byte) pkmn.hp; + stats[Gen4Constants.bsAttackOffset] = (byte) pkmn.attack; + stats[Gen4Constants.bsDefenseOffset] = (byte) pkmn.defense; + stats[Gen4Constants.bsSpeedOffset] = (byte) pkmn.speed; + stats[Gen4Constants.bsSpAtkOffset] = (byte) pkmn.spatk; + stats[Gen4Constants.bsSpDefOffset] = (byte) pkmn.spdef; + stats[Gen4Constants.bsPrimaryTypeOffset] = Gen4Constants.typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + stats[Gen4Constants.bsSecondaryTypeOffset] = stats[Gen4Constants.bsPrimaryTypeOffset]; + } else { + stats[Gen4Constants.bsSecondaryTypeOffset] = Gen4Constants.typeToByte(pkmn.secondaryType); + } + stats[Gen4Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + stats[Gen4Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + + stats[Gen4Constants.bsAbility1Offset] = (byte) pkmn.ability1; + stats[Gen4Constants.bsAbility2Offset] = (byte) pkmn.ability2; + + // Held items + if (pkmn.guaranteedHeldItem > 0) { + writeWord(stats, Gen4Constants.bsCommonHeldItemOffset, pkmn.guaranteedHeldItem); + writeWord(stats, Gen4Constants.bsRareHeldItemOffset, pkmn.guaranteedHeldItem); + } else { + writeWord(stats, Gen4Constants.bsCommonHeldItemOffset, pkmn.commonHeldItem); + writeWord(stats, Gen4Constants.bsRareHeldItemOffset, pkmn.rareHeldItem); + } + } + + @Override + public List getPokemon() { + return pokemonList; + } + + @Override + public List getPokemonInclFormes() { + return pokemonListInclFormes; // No formes for now + } + + @Override + public List getAltFormes() { + int formeCount = Gen4Constants.getFormeCount(romEntry.romType); + return pokemonListInclFormes.subList(Gen4Constants.pokemonCount + 1, Gen4Constants.pokemonCount + formeCount + 1); + } + + @Override + public List getMegaEvolutions() { + return new ArrayList<>(); + } + + @Override + public Pokemon getAltFormeOfPokemon(Pokemon pk, int forme) { + int pokeNum = Gen4Constants.getAbsolutePokeNumByBaseForme(pk.number,forme); + return pokeNum != 0 ? pokes[pokeNum] : pk; + } + + @Override + public List getIrregularFormes() { + return new ArrayList<>(); + } + + @Override + public boolean hasFunctionalFormes() { + return romEntry.romType != Gen4Constants.Type_DP; + } + + @Override + public List getStarters() { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + List tailOffsets = RomFunctions.search(arm9, Gen4Constants.hgssStarterCodeSuffix); + if (tailOffsets.size() == 1) { + // Found starters + int starterOffset = tailOffsets.get(0) - 13; + int poke1 = readWord(arm9, starterOffset); + int poke2 = readWord(arm9, starterOffset + 4); + int poke3 = readWord(arm9, starterOffset + 8); + return Arrays.asList(pokes[poke1], pokes[poke2], pokes[poke3]); + } else { + return Arrays.asList(pokes[Species.chikorita], pokes[Species.cyndaquil], + pokes[Species.totodile]); + } + } else { + try { + byte[] starterData = readOverlay(romEntry.getInt("StarterPokemonOvlNumber")); + int poke1 = readWord(starterData, romEntry.getInt("StarterPokemonOffset")); + int poke2 = readWord(starterData, romEntry.getInt("StarterPokemonOffset") + 4); + int poke3 = readWord(starterData, romEntry.getInt("StarterPokemonOffset") + 8); + return Arrays.asList(pokes[poke1], pokes[poke2], pokes[poke3]); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + } + + @Override + public boolean setStarters(List newStarters) { + if (newStarters.size() != 3) { + return false; + } + + if (romEntry.romType == Gen4Constants.Type_HGSS) { + List tailOffsets = RomFunctions.search(arm9, Gen4Constants.hgssStarterCodeSuffix); + if (tailOffsets.size() == 1) { + // Found starters + int starterOffset = tailOffsets.get(0) - 13; + writeWord(arm9, starterOffset, newStarters.get(0).number); + writeWord(arm9, starterOffset + 4, newStarters.get(1).number); + writeWord(arm9, starterOffset + 8, newStarters.get(2).number); + // Go fix the rival scripts, which rely on fixed pokemon numbers + // The logic to be changed each time is roughly: + // Set 0x800C = player starter + // If(0x800C==152) { trainerbattle rival w/ cynda } + // ElseIf(0x800C==155) { trainerbattle rival w/ totodile } + // Else { trainerbattle rival w/ chiko } + // So we basically have to adjust the 152 and the 155. + int[] filesWithRivalScript = Gen4Constants.hgssFilesWithRivalScript; + // below code represents a rival script for sure + // it means: StoreStarter2 0x800C; If 0x800C 152; CheckLR B_!= + // + byte[] magic = Gen4Constants.hgssRivalScriptMagic; + NARCArchive scriptNARC = scriptNarc; + for (int fileCheck : filesWithRivalScript) { + byte[] file = scriptNARC.files.get(fileCheck); + List rivalOffsets = RomFunctions.search(file, magic); + if (rivalOffsets.size() == 1) { + // found, adjust + int baseOffset = rivalOffsets.get(0); + // Replace 152 (chiko) with first starter + writeWord(file, baseOffset + 8, newStarters.get(0).number); + int jumpAmount = readLong(file, baseOffset + 13); + int secondBase = jumpAmount + baseOffset + 17; + // TODO find out what this constant 0x11 is and remove + // it + if (file[secondBase] != 0x11 || (file[secondBase + 4] & 0xFF) != Species.cyndaquil) { + // This isn't what we were expecting... + } else { + // Replace 155 (cynda) with 2nd starter + writeWord(file, secondBase + 4, newStarters.get(1).number); + } + } + } + // Fix starter text + List spStrings = getStrings(romEntry.getInt("StarterScreenTextOffset")); + String[] intros = new String[] { "So, you like", "You’ll take", "Do you want" }; + for (int i = 0; i < 3; i++) { + Pokemon newStarter = newStarters.get(i); + int color = (i == 0) ? 3 : i; + String newStarterDesc = "Professor Elm: " + intros[i] + " \\vFF00\\z000" + color + newStarter.name + + "\\vFF00\\z0000,\\nthe " + newStarter.primaryType.camelCase() + "-type Pokémon?"; + spStrings.set(i + 1, newStarterDesc); + String altStarterDesc = "\\vFF00\\z000" + color + newStarter.name + "\\vFF00\\z0000, the " + + newStarter.primaryType.camelCase() + "-type Pokémon, is\\nin this Poké Ball!"; + spStrings.set(i + 4, altStarterDesc); + } + setStrings(romEntry.getInt("StarterScreenTextOffset"), spStrings); + + + try { + // Fix starter cries + byte[] starterPokemonOverlay = readOverlay(romEntry.getInt("StarterPokemonOvlNumber")); + String spCriesPrefix = Gen4Constants.starterCriesPrefix; + int offset = find(starterPokemonOverlay, spCriesPrefix); + if (offset > 0) { + offset += spCriesPrefix.length() / 2; // because it was a prefix + for (Pokemon newStarter: newStarters) { + writeLong(starterPokemonOverlay, offset, newStarter.number); + offset += 4; + } + } + writeOverlay(romEntry.getInt("StarterPokemonOvlNumber"), starterPokemonOverlay); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return true; + } else { + return false; + } + } else { + try { + byte[] starterData = readOverlay(romEntry.getInt("StarterPokemonOvlNumber")); + writeWord(starterData, romEntry.getInt("StarterPokemonOffset"), newStarters.get(0).number); + writeWord(starterData, romEntry.getInt("StarterPokemonOffset") + 4, newStarters.get(1).number); + writeWord(starterData, romEntry.getInt("StarterPokemonOffset") + 8, newStarters.get(2).number); + + if (romEntry.romType == Gen4Constants.Type_DP || romEntry.romType == Gen4Constants.Type_Plat) { + String starterPokemonGraphicsPrefix = romEntry.getString("StarterPokemonGraphicsPrefix"); + int offset = find(starterData, starterPokemonGraphicsPrefix); + if (offset > 0) { + + // The original subroutine for handling the starter graphics is optimized by the compiler to use + // a value as a pointer offset and then adding to that value to get the Pokemon's index. + // We will keep this logic, but in order to make place for an extra instruction that will let + // us set the Pokemon index to any Gen 4 value we want, we change the base address of the + // pointer that the offset is used for; this also requires some changes to the instructions + // that utilize this pointer. + offset += starterPokemonGraphicsPrefix.length() / 2; + + // Move down a section of instructions to make place for an add instruction that modifies the + // pointer. A PC-relative load and a BL have to be slightly modified to point to the correct + // thing. + writeWord(starterData, offset+0xC, readWord(starterData, offset+0xA)); + if (offset % 4 == 0) { + starterData[offset+0xC] = (byte)(starterData[offset+0xC] - 1); + } + writeWord(starterData, offset+0xA, readWord(starterData, offset+0x8)); + starterData[offset+0xA] = (byte)(starterData[offset+0xA] - 1); + writeWord(starterData, offset+0x8, readWord(starterData, offset+0x6)); + writeWord(starterData, offset+0x6, readWord(starterData, offset+0x4)); + writeWord(starterData, offset+0x4, readWord(starterData, offset+0x2)); + // This instruction normally uses the value in r0 (0x200) as an offset for an ldr that uses + // the pointer as its base address; we change this to not use an offset at all because we + // change the instruction before it to add that 0x200 to the base address. + writeWord(starterData, offset+0x2, 0x6828); + writeWord(starterData, offset, 0x182D); + + offset += 0x16; + // Change another ldr to not use any offset since we changed the base address + writeWord(starterData, offset, 0x6828); + + offset += 0xA; + + // This is where we write the actual starter numbers, as two adds/subs + + for (int i = 0; i < 3; i++) { + // The offset that we want to use for the pointer is 4, then 8, then 0xC. + // We take the difference of the Pokemon's index and the offset, because we want to add + // (or subtract) that to/from the offset to get the Pokemon's index later. + int starterDiff = newStarters.get(i).number - (4*(i+1)); + + // Prepare two "add r0, #0x0" instructions where we'll modify the immediate + int instr1 = 0x3200; + int instr2 = 0x3200; + + if (starterDiff < 0) { + // Pokemon's index is below the offset, change to a sub instruction + instr1 |= 0x800; + starterDiff = Math.abs(starterDiff); + } else if (starterDiff > 255) { + // Pokemon's index is above (offset + 255), need to utilize the second add instruction + instr2 |= 0xFF; + starterDiff -= 255; + } + + // Modify the first add instruction's immediate value + instr1 |= (starterDiff & 0xFF); + + // Change the original offset that's loaded, then move an instruction up one step + // and insert our add instructions + starterData[offset] = (byte)(4*(i+1)); + writeWord(starterData, offset+2, readWord(starterData, offset+4)); + writeWord(starterData, offset+4, instr1); + writeWord(starterData, offset+8, instr2); + + // Repeat for each starter + offset += 0xE; + } + + // Change a loaded value to be 1 instead of 0x81 because we changed the pointer + starterData[offset] = 1; + + // Also need to change one usage of the pointer we changed, in the inner function + String starterPokemonGraphicsPrefixInner = romEntry.getString("StarterPokemonGraphicsPrefixInner"); + offset = find(starterData, starterPokemonGraphicsPrefixInner); + + if (offset > 0) { + offset += starterPokemonGraphicsPrefixInner.length() / 2; + starterData[offset+1] = 0x68; + } + } + } + + writeOverlay(romEntry.getInt("StarterPokemonOvlNumber"), starterData); + // Patch DPPt-style rival scripts + // these have a series of IfJump commands + // following pokemon IDs + // the jumps either go to trainer battles, or a HoF times + // checker, or the StarterBattle command (Pt only) + // the HoF times checker case is for the Fight Area or Survival + // Area (depending on version). + // the StarterBattle case is for Route 201 in Pt. + int[] filesWithRivalScript = (romEntry.romType == Gen4Constants.Type_Plat) ? Gen4Constants.ptFilesWithRivalScript + : Gen4Constants.dpFilesWithRivalScript; + byte[] magic = Gen4Constants.dpptRivalScriptMagic; + NARCArchive scriptNARC = scriptNarc; + for (int fileCheck : filesWithRivalScript) { + byte[] file = scriptNARC.files.get(fileCheck); + List rivalOffsets = RomFunctions.search(file, magic); + if (rivalOffsets.size() > 0) { + for (int baseOffset : rivalOffsets) { + // found, check for trainer battle or HoF + // check at jump + int jumpLoc = baseOffset + magic.length; + int jumpTo = readLong(file, jumpLoc) + jumpLoc + 4; + // TODO find out what these constants are and remove + // them + if (readWord(file, jumpTo) != 0xE5 && readWord(file, jumpTo) != 0x28F + && (readWord(file, jumpTo) != 0x125 || romEntry.romType != Gen4Constants.Type_Plat)) { + continue; // not a rival script + } + // Replace the two starter-words 387 and 390 + writeWord(file, baseOffset + 0x8, newStarters.get(0).number); + writeWord(file, baseOffset + 0x15, newStarters.get(1).number); + } + } + } + // Tag battles with rival or friend + // Have their own script magic + // 2 for Lucas/Dawn (=4 occurrences), 1 or 2 for Barry + byte[] tagBattleMagic = Gen4Constants.dpptTagBattleScriptMagic1; + byte[] tagBattleMagic2 = Gen4Constants.dpptTagBattleScriptMagic2; + int[] filesWithTagBattleScript = (romEntry.romType == Gen4Constants.Type_Plat) ? Gen4Constants.ptFilesWithTagScript + : Gen4Constants.dpFilesWithTagScript; + for (int fileCheck : filesWithTagBattleScript) { + byte[] file = scriptNARC.files.get(fileCheck); + List tbOffsets = RomFunctions.search(file, tagBattleMagic); + if (tbOffsets.size() > 0) { + for (int baseOffset : tbOffsets) { + // found, check for second part + int secondPartStart = baseOffset + tagBattleMagic.length + 2; + if (secondPartStart + tagBattleMagic2.length > file.length) { + continue; // match failed + } + boolean valid = true; + for (int spo = 0; spo < tagBattleMagic2.length; spo++) { + if (file[secondPartStart + spo] != tagBattleMagic2[spo]) { + valid = false; + break; + } + } + if (!valid) { + continue; + } + // Make sure the jump following the second + // part jumps to a command + int jumpLoc = secondPartStart + tagBattleMagic2.length; + int jumpTo = readLong(file, jumpLoc) + jumpLoc + 4; + // TODO find out what this constant is and remove it + if (readWord(file, jumpTo) != 0x1B) { + continue; // not a tag battle script + } + // Replace the two starter-words + if (readWord(file, baseOffset + 0x21) == Species.turtwig) { + // first starter + writeWord(file, baseOffset + 0x21, newStarters.get(0).number); + } else { + // third starter + writeWord(file, baseOffset + 0x21, newStarters.get(2).number); + } + // second starter + writeWord(file, baseOffset + 0xE, newStarters.get(1).number); + } + } + } + // Fix starter script text + // The starter picking screen + List spStrings = getStrings(romEntry.getInt("StarterScreenTextOffset")); + // Get pokedex info + List pokedexSpeciesStrings = getStrings(romEntry.getInt("PokedexSpeciesTextOffset")); + for (int i = 0; i < 3; i++) { + Pokemon newStarter = newStarters.get(i); + int color = (i == 0) ? 3 : i; + String newStarterDesc = "\\vFF00\\z000" + color + pokedexSpeciesStrings.get(newStarter.number) + + " " + newStarter.name + "\\vFF00\\z0000!\\nWill you take this Pokémon?"; + spStrings.set(i + 1, newStarterDesc); + } + // rewrite starter picking screen + setStrings(romEntry.getInt("StarterScreenTextOffset"), spStrings); + if (romEntry.romType == Gen4Constants.Type_DP) { + // what rival says after we get the Pokemon + List lakeStrings = getStrings(romEntry.getInt("StarterLocationTextOffset")); + lakeStrings + .set(Gen4Constants.dpStarterStringIndex, + "\\v0103\\z0000: Fwaaah!\\nYour Pokémon totally rocked!\\pBut mine was way tougher\\nthan yours!\\p...They were other people’s\\nPokémon, though...\\pBut we had to use them...\\nThey won’t mind, will they?\\p"); + setStrings(romEntry.getInt("StarterLocationTextOffset"), lakeStrings); + } else { + // what rival says after we get the Pokemon + List r201Strings = getStrings(romEntry.getInt("StarterLocationTextOffset")); + r201Strings.set(Gen4Constants.ptStarterStringIndex, + "\\v0103\\z0000\\z0000: Then, I choose you!\\nI’m picking this one!\\p"); + setStrings(romEntry.getInt("StarterLocationTextOffset"), r201Strings); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return true; + } + } + + @Override + public boolean supportsStarterHeldItems() { + return romEntry.romType == Gen4Constants.Type_DP || romEntry.romType == Gen4Constants.Type_Plat; + } + + @Override + public List getStarterHeldItems() { + int starterScriptNumber = romEntry.getInt("StarterPokemonScriptOffset"); + int starterHeldItemOffset = romEntry.getInt("StarterPokemonHeldItemOffset"); + byte[] file = scriptNarc.files.get(starterScriptNumber); + int item = FileFunctions.read2ByteInt(file, starterHeldItemOffset); + return Arrays.asList(item); + } + + @Override + public void setStarterHeldItems(List items) { + int starterScriptNumber = romEntry.getInt("StarterPokemonScriptOffset"); + int starterHeldItemOffset = romEntry.getInt("StarterPokemonHeldItemOffset"); + byte[] file = scriptNarc.files.get(starterScriptNumber); + FileFunctions.write2ByteInt(file, starterHeldItemOffset, items.get(0)); + } + + @Override + public List getMoves() { + return Arrays.asList(moves); + } + + @Override + public List getEncounters(boolean useTimeOfDay) { + if (!loadedWildMapNames) { + loadWildMapNames(); + } + + try { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + return getEncountersHGSS(useTimeOfDay); + } else { + return getEncountersDPPt(useTimeOfDay); + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + } + + private List getEncountersDPPt(boolean useTimeOfDay) throws IOException { + // Determine file to use + String encountersFile = romEntry.getFile("WildPokemon"); + + NARCArchive encounterData = readNARC(encountersFile); + List encounters = new ArrayList<>(); + // Credit for + // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-sinnoh.py + // for the structure for this. + int c = -1; + for (byte[] b : encounterData.files) { + c++; + if (!wildMapNames.containsKey(c)) { + wildMapNames.put(c, "? Unknown ?"); + } + String mapName = wildMapNames.get(c); + int grassRate = readLong(b, 0); + if (grassRate != 0) { + // up to 4 + List grassEncounters = readEncountersDPPt(b, 4, 12); + EncounterSet grass = new EncounterSet(); + grass.displayName = mapName + " Grass/Cave"; + grass.encounters = grassEncounters; + grass.rate = grassRate; + grass.offset = c; + encounters.add(grass); + + // Time of day replacements? + if (useTimeOfDay) { + for (int i = 0; i < 4; i++) { + int pknum = readLong(b, 108 + 4 * i); + if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { + Pokemon pk = pokes[pknum]; + Encounter enc = new Encounter(); + enc.level = grassEncounters.get(Gen4Constants.dpptAlternateSlots[i + 2]).level; + enc.pokemon = pk; + grassEncounters.add(enc); + } + } + } + // (if useTimeOfDay is off, just override them later) + + // Other conditional replacements (swarm, radar, GBA) + EncounterSet conds = new EncounterSet(); + conds.displayName = mapName + " Swarm/Radar/GBA"; + conds.rate = grassRate; + conds.offset = c; + for (int i = 0; i < 20; i++) { + if (i >= 2 && i <= 5) { + // Time of day slot, handled already + continue; + } + int offs = 100 + i * 4 + (i >= 10 ? 24 : 0); + int pknum = readLong(b, offs); + if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { + Pokemon pk = pokes[pknum]; + Encounter enc = new Encounter(); + enc.level = grassEncounters.get(Gen4Constants.dpptAlternateSlots[i]).level; + enc.pokemon = pk; + conds.encounters.add(enc); + } + } + if (conds.encounters.size() > 0) { + encounters.add(conds); + } + } + + // up to 204, 5 sets of "sea" encounters to go + int offset = 204; + for (int i = 0; i < 5; i++) { + int rate = readLong(b, offset); + offset += 4; + List encountersHere = readSeaEncountersDPPt(b, offset, 5); + offset += 40; + if (rate == 0 || i == 1) { + continue; + } + EncounterSet other = new EncounterSet(); + other.displayName = mapName + " " + Gen4Constants.dpptWaterSlotSetNames[i]; + other.offset = c; + other.encounters = encountersHere; + other.rate = rate; + encounters.add(other); + } + } + + // Now do the extra encounters (Feebas tiles, honey trees, Great Marsh rotating Pokemon, etc.) + String extraEncountersFile = romEntry.getFile("ExtraEncounters"); + NARCArchive extraEncounterData = readNARC(extraEncountersFile); + + // Feebas tiles + byte[] feebasData = extraEncounterData.files.get(0); + EncounterSet feebasEncounters = readExtraEncountersDPPt(feebasData, 0, 1); + byte[] encounterOverlay = readOverlay(romEntry.getInt("EncounterOvlNumber")); + int offset = find(encounterOverlay, Gen4Constants.feebasLevelPrefixDPPt); + if (offset > 0) { + offset += Gen4Constants.feebasLevelPrefixDPPt.length() / 2; // because it was a prefix + for (Encounter enc : feebasEncounters.encounters) { + enc.maxLevel = encounterOverlay[offset]; + enc.level = encounterOverlay[offset + 4]; + } + } + feebasEncounters.displayName = "Mt. Coronet Feebas Tiles"; + encounters.add(feebasEncounters); + + // Honey trees + int[] honeyTreeOffsets = romEntry.arrayEntries.get("HoneyTreeOffsets"); + for (int i = 0; i < honeyTreeOffsets.length; i++) { + byte[] honeyTreeData = extraEncounterData.files.get(honeyTreeOffsets[i]); + EncounterSet honeyTreeEncounters = readExtraEncountersDPPt(honeyTreeData, 0, 6); + offset = find(encounterOverlay, Gen4Constants.honeyTreeLevelPrefixDPPt); + if (offset > 0) { + offset += Gen4Constants.honeyTreeLevelPrefixDPPt.length() / 2; // because it was a prefix + + // To make different min levels work, we rewrite some assembly code in + // setEncountersDPPt, which has the side effect of making reading the min + // level easier. In case the original code is still there, just hardcode + // the min level used in the vanilla game, since extracting it is hard. + byte level; + if (encounterOverlay[offset + 46] == 0x0B && encounterOverlay[offset + 47] == 0x2E) { + level = 5; + } else { + level = encounterOverlay[offset + 46]; + } + for (Encounter enc : honeyTreeEncounters.encounters) { + enc.maxLevel = encounterOverlay[offset + 102]; + enc.level = level; + } + } + honeyTreeEncounters.displayName = "Honey Tree Group " + (i + 1); + encounters.add(honeyTreeEncounters); + } + + // Trophy Garden rotating Pokemon (Mr. Backlot) + byte[] trophyGardenData = extraEncounterData.files.get(8); + EncounterSet trophyGardenEncounters = readExtraEncountersDPPt(trophyGardenData, 0, 16); + + // Trophy Garden rotating Pokemon get their levels from the regular Trophy Garden grass encounters, + // indices 6 and 7. To make the logs nice, read in these encounters for this area and set the level + // and maxLevel for the rotating encounters appropriately. + int trophyGardenGrassEncounterIndex = Gen4Constants.getTrophyGardenGrassEncounterIndex(romEntry.romType); + EncounterSet trophyGardenGrassEncounterSet = encounters.get(trophyGardenGrassEncounterIndex); + int level1 = trophyGardenGrassEncounterSet.encounters.get(6).level; + int level2 = trophyGardenGrassEncounterSet.encounters.get(7).level; + for (Encounter enc : trophyGardenEncounters.encounters) { + enc.level = Math.min(level1, level2); + if (level1 != level2) { + enc.maxLevel = Math.max(level1, level2); + } + } + trophyGardenEncounters.displayName = "Trophy Garden Rotating Pokemon (via Mr. Backlot)"; + encounters.add(trophyGardenEncounters); + + // Great Marsh rotating Pokemon + int[] greatMarshOffsets = new int[]{9, 10}; + for (int i = 0; i < greatMarshOffsets.length; i++) { + byte[] greatMarshData = extraEncounterData.files.get(greatMarshOffsets[i]); + EncounterSet greatMarshEncounters = readExtraEncountersDPPt(greatMarshData, 0, 32); + + // Great Marsh rotating Pokemon get their levels from the regular Great Marsh grass encounters, + // indices 6 and 7. To make the logs nice, read in these encounters for all areas and set the + // level and maxLevel for the rotating encounters appropriately. + int level = 100; + int maxLevel = 0; + List marshGrassEncounterIndices = Gen4Constants.getMarshGrassEncounterIndices(romEntry.romType); + for (int j = 0; j < marshGrassEncounterIndices.size(); j++) { + EncounterSet marshGrassEncounterSet = encounters.get(marshGrassEncounterIndices.get(j)); + int currentLevel = marshGrassEncounterSet.encounters.get(6).level; + if (currentLevel < level) { + level = currentLevel; + } + if (currentLevel > maxLevel) { + maxLevel = currentLevel; + } + currentLevel = marshGrassEncounterSet.encounters.get(7).level; + if (currentLevel < level) { + level = currentLevel; + } + if (currentLevel > maxLevel) { + maxLevel = currentLevel; + } + } + for (Encounter enc : greatMarshEncounters.encounters) { + enc.level = level; + enc.maxLevel = maxLevel; + } + String pokedexStatus = i == 0 ? "(Post-National Dex)" : "(Pre-National Dex)"; + greatMarshEncounters.displayName = "Great Marsh Rotating Pokemon " + pokedexStatus; + encounters.add(greatMarshEncounters); + } + return encounters; + } + + private List readEncountersDPPt(byte[] data, int offset, int amount) { + List encounters = new ArrayList<>(); + for (int i = 0; i < amount; i++) { + int level = readLong(data, offset + i * 8); + int pokemon = readLong(data, offset + 4 + i * 8); + Encounter enc = new Encounter(); + enc.level = level; + enc.pokemon = pokes[pokemon]; + encounters.add(enc); + } + return encounters; + } + + private List readSeaEncountersDPPt(byte[] data, int offset, int amount) { + List encounters = new ArrayList<>(); + for (int i = 0; i < amount; i++) { + int level = readLong(data, offset + i * 8); + int pokemon = readLong(data, offset + 4 + i * 8); + Encounter enc = new Encounter(); + enc.level = level >> 8; + enc.maxLevel = level & 0xFF; + enc.pokemon = pokes[pokemon]; + encounters.add(enc); + } + return encounters; + } + + private EncounterSet readExtraEncountersDPPt(byte[] data, int offset, int amount) { + EncounterSet es = new EncounterSet(); + es.rate = 1; + for (int i = 0; i < amount; i++) { + int pokemon = readLong(data, offset + i * 4); + Encounter e = new Encounter(); + e.level = 1; + e.pokemon = pokes[pokemon]; + es.encounters.add(e); + } + return es; + } + + private List getEncountersHGSS(boolean useTimeOfDay) throws IOException { + String encountersFile = romEntry.getFile("WildPokemon"); + NARCArchive encounterData = readNARC(encountersFile); + List encounters = new ArrayList<>(); + // Credit for + // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-johto.py + // for the structure for this. + int[] amounts = new int[] { 0, 5, 2, 5, 5, 5 }; + int c = -1; + for (byte[] b : encounterData.files) { + c++; + if (!wildMapNames.containsKey(c)) { + wildMapNames.put(c, "? Unknown ?"); + } + String mapName = wildMapNames.get(c); + int[] rates = new int[6]; + rates[0] = b[0] & 0xFF; + rates[1] = b[1] & 0xFF; + rates[2] = b[2] & 0xFF; + rates[3] = b[3] & 0xFF; + rates[4] = b[4] & 0xFF; + rates[5] = b[5] & 0xFF; + // Up to 8 after the rates + // Grass has to be handled on its own because the levels + // are reused for every time of day + int[] grassLevels = new int[12]; + for (int i = 0; i < 12; i++) { + grassLevels[i] = b[8 + i] & 0xFF; + } + // Up to 20 now (12 for levels) + Pokemon[][] grassPokes = new Pokemon[3][12]; + grassPokes[0] = readPokemonHGSS(b, 20, 12); + grassPokes[1] = readPokemonHGSS(b, 44, 12); + grassPokes[2] = readPokemonHGSS(b, 68, 12); + // Up to 92 now (12*2*3 for pokemon) + if (rates[0] != 0) { + if (!useTimeOfDay) { + // Just write "day" encounters + List grassEncounters = stitchEncsToLevels(grassPokes[1], grassLevels); + EncounterSet grass = new EncounterSet(); + grass.encounters = grassEncounters; + grass.rate = rates[0]; + grass.displayName = mapName + " Grass/Cave"; + encounters.add(grass); + } else { + for (int i = 0; i < 3; i++) { + EncounterSet grass = new EncounterSet(); + grass.encounters = stitchEncsToLevels(grassPokes[i], grassLevels); + grass.rate = rates[0]; + grass.displayName = mapName + " " + Gen4Constants.hgssTimeOfDayNames[i] + " Grass/Cave"; + encounters.add(grass); + } + } + } + + // Hoenn/Sinnoh Radio + EncounterSet radio = readOptionalEncountersHGSS(b, 92, 4); + radio.displayName = mapName + " Hoenn/Sinnoh Radio"; + if (radio.encounters.size() > 0) { + encounters.add(radio); + } + + // Up to 100 now... 2*2*2 for radio pokemon + // Time to handle Surfing, Rock Smash, Rods + int offset = 100; + for (int i = 1; i < 6; i++) { + List encountersHere = readSeaEncountersHGSS(b, offset, amounts[i]); + offset += 4 * amounts[i]; + if (rates[i] != 0) { + // Valid area. + EncounterSet other = new EncounterSet(); + other.encounters = encountersHere; + other.displayName = mapName + " " + Gen4Constants.hgssNonGrassSetNames[i]; + other.rate = rates[i]; + encounters.add(other); + } + } + + // Swarms + EncounterSet swarms = readOptionalEncountersHGSS(b, offset, 2); + swarms.displayName = mapName + " Swarms"; + if (swarms.encounters.size() > 0) { + encounters.add(swarms); + } + EncounterSet nightFishingReplacement = readOptionalEncountersHGSS(b, offset + 4, 1); + nightFishingReplacement.displayName = mapName + " Night Fishing Replacement"; + if (nightFishingReplacement.encounters.size() > 0) { + encounters.add(nightFishingReplacement); + } + EncounterSet fishingSwarms = readOptionalEncountersHGSS(b, offset + 6, 1); + fishingSwarms.displayName = mapName + " Fishing Swarm"; + if (fishingSwarms.encounters.size() > 0) { + encounters.add(fishingSwarms); + } + } + + // Headbutt Encounters + String headbuttEncountersFile = romEntry.getFile("HeadbuttPokemon"); + NARCArchive headbuttEncounterData = readNARC(headbuttEncountersFile); + c = -1; + for (byte[] b : headbuttEncounterData.files) { + c++; + + // Each headbutt encounter file starts with four bytes, which I believe are used + // to indicate the number of "normal" and "special" trees that are available in + // this area. For areas that don't contain any headbutt encounters, these four + // bytes constitute the only four bytes in the file, so we can stop looking at + // this file in this case. + if (b.length == 4) { + continue; + } + + String mapName = headbuttMapNames.get(c); + EncounterSet headbuttEncounters = readHeadbuttEncountersHGSS(b, 4, 18); + headbuttEncounters.displayName = mapName + " Headbutt"; + + // Map 24 is an unused version of Route 16, but it still has valid headbutt encounter data. + // Avoid adding it to the list of encounters to prevent confusion. + if (headbuttEncounters.encounters.size() > 0 && c != 24) { + encounters.add(headbuttEncounters); + } + } + + // Bug Catching Contest Encounters + String bccEncountersFile = romEntry.getFile("BCCWilds"); + byte[] bccEncountersData = readFile(bccEncountersFile); + EncounterSet bccEncountersPreNationalDex = readBCCEncountersHGSS(bccEncountersData, 0, 10); + bccEncountersPreNationalDex.displayName = "Bug Catching Contest (Pre-National Dex)"; + if (bccEncountersPreNationalDex.encounters.size() > 0) { + encounters.add(bccEncountersPreNationalDex); + } + EncounterSet bccEncountersPostNationalDexTues = readBCCEncountersHGSS(bccEncountersData, 80, 10); + bccEncountersPostNationalDexTues.displayName = "Bug Catching Contest (Post-National Dex, Tuesdays)"; + if (bccEncountersPostNationalDexTues.encounters.size() > 0) { + encounters.add(bccEncountersPostNationalDexTues); + } + EncounterSet bccEncountersPostNationalDexThurs = readBCCEncountersHGSS(bccEncountersData, 160, 10); + bccEncountersPostNationalDexThurs.displayName = "Bug Catching Contest (Post-National Dex, Thursdays)"; + if (bccEncountersPostNationalDexThurs.encounters.size() > 0) { + encounters.add(bccEncountersPostNationalDexThurs); + } + EncounterSet bccEncountersPostNationalDexSat = readBCCEncountersHGSS(bccEncountersData, 240, 10); + bccEncountersPostNationalDexSat.displayName = "Bug Catching Contest (Post-National Dex, Saturdays)"; + if (bccEncountersPostNationalDexSat.encounters.size() > 0) { + encounters.add(bccEncountersPostNationalDexSat); + } + return encounters; + } + + private EncounterSet readOptionalEncountersHGSS(byte[] data, int offset, int amount) { + EncounterSet es = new EncounterSet(); + es.rate = 1; + for (int i = 0; i < amount; i++) { + int pokemon = readWord(data, offset + i * 2); + if (pokemon != 0) { + Encounter e = new Encounter(); + e.level = 1; + e.pokemon = pokes[pokemon]; + es.encounters.add(e); + } + } + return es; + } + + private Pokemon[] readPokemonHGSS(byte[] data, int offset, int amount) { + Pokemon[] pokesHere = new Pokemon[amount]; + for (int i = 0; i < amount; i++) { + pokesHere[i] = pokes[readWord(data, offset + i * 2)]; + } + return pokesHere; + } + + private List readSeaEncountersHGSS(byte[] data, int offset, int amount) { + List encounters = new ArrayList<>(); + for (int i = 0; i < amount; i++) { + int level = readWord(data, offset + i * 4); + int pokemon = readWord(data, offset + 2 + i * 4); + Encounter enc = new Encounter(); + enc.level = level & 0xFF; + enc.maxLevel = level >> 8; + enc.pokemon = pokes[pokemon]; + encounters.add(enc); + } + return encounters; + } + + private EncounterSet readHeadbuttEncountersHGSS(byte[] data, int offset, int amount) { + EncounterSet es = new EncounterSet(); + es.rate = 1; + for (int i = 0; i < amount; i++) { + int pokemon = readWord(data, offset + i * 4); + if (pokemon != 0) { + Encounter enc = new Encounter(); + enc.level = data[offset + 2 + i * 4]; + enc.maxLevel = data[offset + 3 + i * 4]; + enc.pokemon = pokes[pokemon]; + es.encounters.add(enc); + } + } + return es; + } + + private EncounterSet readBCCEncountersHGSS(byte[] data, int offset, int amount) { + EncounterSet es = new EncounterSet(); + es.rate = 1; + for (int i = 0; i < amount; i++) { + int pokemon = readWord(data, offset + i * 8); + if (pokemon != 0) { + Encounter enc = new Encounter(); + enc.level = data[offset + 2 + i * 8]; + enc.maxLevel = data[offset + 3 + i * 8]; + enc.pokemon = pokes[pokemon]; + es.encounters.add(enc); + } + } + return es; + } + + private List readTimeBasedRodEncountersHGSS(byte[] data, int offset, Pokemon replacement, int replacementIndex) { + List encounters = new ArrayList<>(); + List rodMorningDayEncounters = readSeaEncountersHGSS(data, offset, 5); + EncounterSet rodMorningDay = new EncounterSet(); + rodMorningDay.encounters = rodMorningDayEncounters; + encounters.add(rodMorningDay); + + List rodNightEncounters = new ArrayList<>(rodMorningDayEncounters); + Encounter replacedEncounter = cloneEncounterAndReplacePokemon(rodMorningDayEncounters.get(replacementIndex), replacement); + rodNightEncounters.set(replacementIndex, replacedEncounter); + EncounterSet rodNight = new EncounterSet(); + rodNight.encounters = rodNightEncounters; + encounters.add(rodNight); + return encounters; + } + + private Encounter cloneEncounterAndReplacePokemon(Encounter enc, Pokemon pkmn) { + Encounter clone = new Encounter(); + clone.level = enc.level; + clone.maxLevel = enc.maxLevel; + clone.pokemon = pkmn; + return clone; + } + + @Override + public void setEncounters(boolean useTimeOfDay, List encounters) { + try { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + setEncountersHGSS(useTimeOfDay, encounters); + updatePokedexAreaDataHGSS(encounters); + } else { + setEncountersDPPt(useTimeOfDay, encounters); + updatePokedexAreaDataDPPt(encounters); + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + } + + private void setEncountersDPPt(boolean useTimeOfDay, List encounterList) throws IOException { + // Determine file to use + String encountersFile = romEntry.getFile("WildPokemon"); + NARCArchive encounterData = readNARC(encountersFile); + Iterator encounters = encounterList.iterator(); + // Credit for + // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-sinnoh.py + // for the structure for this. + for (byte[] b : encounterData.files) { + int grassRate = readLong(b, 0); + if (grassRate != 0) { + // grass encounters are a-go + EncounterSet grass = encounters.next(); + writeEncountersDPPt(b, 4, grass.encounters, 12); + + // Time of day encounters? + int todEncounterSlot = 12; + for (int i = 0; i < 4; i++) { + int pknum = readLong(b, 108 + 4 * i); + if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { + // Valid time of day slot + if (useTimeOfDay) { + // Get custom randomized encounter + Pokemon pk = grass.encounters.get(todEncounterSlot++).pokemon; + writeLong(b, 108 + 4 * i, pk.number); + } else { + // Copy the original slot's randomized encounter + Pokemon pk = grass.encounters.get(Gen4Constants.dpptAlternateSlots[i + 2]).pokemon; + writeLong(b, 108 + 4 * i, pk.number); + } + } + } + + // Other conditional encounters? + Iterator condEncounters = null; + for (int i = 0; i < 20; i++) { + if (i >= 2 && i <= 5) { + // Time of day slot, handled already + continue; + } + int offs = 100 + i * 4 + (i >= 10 ? 24 : 0); + int pknum = readLong(b, offs); + if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { + // This slot is used, grab a replacement. + if (condEncounters == null) { + // Fetch the set of conditional encounters for this + // area now that we know it's necessary and exists. + condEncounters = encounters.next().encounters.iterator(); + } + Pokemon pk = condEncounters.next().pokemon; + writeLong(b, offs, pk.number); + } + } + } + // up to 204, 5 special ones to go + // This is for surf, filler, old rod, good rod, super rod + // so we skip index 1 (filler) + int offset = 204; + for (int i = 0; i < 5; i++) { + int rate = readLong(b, offset); + offset += 4; + if (rate == 0 || i == 1) { + offset += 40; + continue; + } + + EncounterSet other = encounters.next(); + writeSeaEncountersDPPt(b, offset, other.encounters); + offset += 40; + } + } + + // Save + writeNARC(encountersFile, encounterData); + + // Now do the extra encounters (Feebas tiles, honey trees, Great Marsh rotating Pokemon, etc.) + String extraEncountersFile = romEntry.getFile("ExtraEncounters"); + NARCArchive extraEncounterData = readNARC(extraEncountersFile); + + // Feebas tiles + byte[] feebasData = extraEncounterData.files.get(0); + EncounterSet feebasEncounters = encounters.next(); + byte[] encounterOverlay = readOverlay(romEntry.getInt("EncounterOvlNumber")); + int offset = find(encounterOverlay, Gen4Constants.feebasLevelPrefixDPPt); + if (offset > 0) { + offset += Gen4Constants.feebasLevelPrefixDPPt.length() / 2; // because it was a prefix + encounterOverlay[offset] = (byte) feebasEncounters.encounters.get(0).maxLevel; + encounterOverlay[offset + 4] = (byte) feebasEncounters.encounters.get(0).level; + } + writeExtraEncountersDPPt(feebasData, 0, feebasEncounters.encounters); + + // Honey trees + int[] honeyTreeOffsets = romEntry.arrayEntries.get("HoneyTreeOffsets"); + for (int i = 0; i < honeyTreeOffsets.length; i++) { + byte[] honeyTreeData = extraEncounterData.files.get(honeyTreeOffsets[i]); + EncounterSet honeyTreeEncounters = encounters.next(); + offset = find(encounterOverlay, Gen4Constants.honeyTreeLevelPrefixDPPt); + if (offset > 0) { + offset += Gen4Constants.honeyTreeLevelPrefixDPPt.length() / 2; // because it was a prefix + int level = honeyTreeEncounters.encounters.get(0).level; + int maxLevel = honeyTreeEncounters.encounters.get(0).maxLevel; + + // The original code makes it impossible for certain min levels + // from being used in the assembly, but there's also a hardcoded + // check for the original level range that we don't want. So we + // can use that space to just do "mov r0, level", nop out the rest + // of the check, then change "mov r0, r6, #5" to "mov r0, r0, r6". + encounterOverlay[offset + 46] = (byte) level; + encounterOverlay[offset + 47] = 0x20; + encounterOverlay[offset + 48] = 0x00; + encounterOverlay[offset + 49] = 0x00; + encounterOverlay[offset + 50] = 0x00; + encounterOverlay[offset + 51] = 0x00; + encounterOverlay[offset + 52] = 0x00; + encounterOverlay[offset + 53] = 0x00; + encounterOverlay[offset + 54] = (byte) 0x80; + encounterOverlay[offset + 55] = 0x19; + + encounterOverlay[offset + 102] = (byte) maxLevel; + + // In the above comment, r6 is a random number between 0 and + // (maxLevel - level). To calculate this number, the game rolls + // a random number between 0 and 0xFFFF and then divides it by + // 0x1746; this produces values between 0 and 10, the original + // level range. We need to replace the 0x1746 with our own + // constant that has the same effect. + int newRange = maxLevel - level; + int divisor = (0xFFFF / (newRange + 1)) + 1; + FileFunctions.writeFullInt(encounterOverlay, offset + 148, divisor); + } + writeExtraEncountersDPPt(honeyTreeData, 0, honeyTreeEncounters.encounters); + } + + // Trophy Garden rotating Pokemon (Mr. Backlot) + byte[] trophyGardenData = extraEncounterData.files.get(8); + EncounterSet trophyGardenEncounters = encounters.next(); + + // The game will softlock if all the Pokemon here are the same species. As an + // emergency mitigation, just randomly pick a different species in case this + // happens. This is very unlikely to happen in practice, even with very + // restrictive settings, so it should be okay that we're breaking logic here. + while (trophyGardenEncounters.encounters.stream().distinct().count() == 1) { + trophyGardenEncounters.encounters.get(0).pokemon = randomPokemon(); + } + writeExtraEncountersDPPt(trophyGardenData, 0, trophyGardenEncounters.encounters); + + // Great Marsh rotating Pokemon + int[] greatMarshOffsets = new int[]{9, 10}; + for (int i = 0; i < greatMarshOffsets.length; i++) { + byte[] greatMarshData = extraEncounterData.files.get(greatMarshOffsets[i]); + EncounterSet greatMarshEncounters = encounters.next(); + writeExtraEncountersDPPt(greatMarshData, 0, greatMarshEncounters.encounters); + } + + // Save + writeOverlay(romEntry.getInt("EncounterOvlNumber"), encounterOverlay); + writeNARC(extraEncountersFile, extraEncounterData); + + } + + private void writeEncountersDPPt(byte[] data, int offset, List encounters, int enclength) { + for (int i = 0; i < enclength; i++) { + Encounter enc = encounters.get(i); + writeLong(data, offset + i * 8, enc.level); + writeLong(data, offset + i * 8 + 4, enc.pokemon.number); + } + } + + private void writeSeaEncountersDPPt(byte[] data, int offset, List encounters) { + int enclength = encounters.size(); + for (int i = 0; i < enclength; i++) { + Encounter enc = encounters.get(i); + writeLong(data, offset + i * 8, (enc.level << 8) + enc.maxLevel); + writeLong(data, offset + i * 8 + 4, enc.pokemon.number); + } + } + + private void writeExtraEncountersDPPt(byte[] data, int offset, List encounters) { + int enclength = encounters.size(); + for (int i = 0; i < enclength; i++) { + Encounter enc = encounters.get(i); + writeLong(data, offset + i * 4, enc.pokemon.number); + } + } + + private void setEncountersHGSS(boolean useTimeOfDay, List encounterList) throws IOException { + String encountersFile = romEntry.getFile("WildPokemon"); + NARCArchive encounterData = readNARC(encountersFile); + Iterator encounters = encounterList.iterator(); + // Credit for + // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-johto.py + // for the structure for this. + int[] amounts = new int[] { 0, 5, 2, 5, 5, 5 }; + for (byte[] b : encounterData.files) { + int[] rates = new int[6]; + rates[0] = b[0] & 0xFF; + rates[1] = b[1] & 0xFF; + rates[2] = b[2] & 0xFF; + rates[3] = b[3] & 0xFF; + rates[4] = b[4] & 0xFF; + rates[5] = b[5] & 0xFF; + // Up to 20 after the rates & levels + // Grass has to be handled on its own because the levels + // are reused for every time of day + if (rates[0] != 0) { + if (!useTimeOfDay) { + // Get a single set of encounters... + // Write the encounters we get 3x for morning, day, night + EncounterSet grass = encounters.next(); + writeGrassEncounterLevelsHGSS(b, 8, grass.encounters); + writePokemonHGSS(b, 20, grass.encounters); + writePokemonHGSS(b, 44, grass.encounters); + writePokemonHGSS(b, 68, grass.encounters); + } else { + EncounterSet grass = encounters.next(); + writeGrassEncounterLevelsHGSS(b, 8, grass.encounters); + writePokemonHGSS(b, 20, grass.encounters); + for (int i = 1; i < 3; i++) { + grass = encounters.next(); + writePokemonHGSS(b, 20 + i * 24, grass.encounters); + } + } + } + + // Write radio pokemon + writeOptionalEncountersHGSS(b, 92, 4, encounters); + + // Up to 100 now... 2*2*2 for radio pokemon + // Write surf, rock smash, and rods + int offset = 100; + for (int i = 1; i < 6; i++) { + if (rates[i] != 0) { + // Valid area. + EncounterSet other = encounters.next(); + writeSeaEncountersHGSS(b, offset, other.encounters); + } + offset += 4 * amounts[i]; + } + + // Write swarm pokemon + writeOptionalEncountersHGSS(b, offset, 2, encounters); + writeOptionalEncountersHGSS(b, offset + 4, 1, encounters); + writeOptionalEncountersHGSS(b, offset + 6, 1, encounters); + } + + // Save + writeNARC(encountersFile, encounterData); + + // Write Headbutt encounters + String headbuttEncountersFile = romEntry.getFile("HeadbuttPokemon"); + NARCArchive headbuttEncounterData = readNARC(headbuttEncountersFile); + int c = -1; + for (byte[] b : headbuttEncounterData.files) { + c++; + + // In getEncountersHGSS, we ignored maps with no headbutt encounter data, + // and we also ignored map 24 for being unused. We need to ignore them + // here as well to keep encounters.next() in sync with the correct file. + if (b.length == 4 || c == 24) { + continue; + } + + EncounterSet headbutt = encounters.next(); + writeHeadbuttEncountersHGSS(b, 4, headbutt.encounters); + } + + // Save + writeNARC(headbuttEncountersFile, headbuttEncounterData); + + // Write Bug Catching Contest encounters + String bccEncountersFile = romEntry.getFile("BCCWilds"); + byte[] bccEncountersData = readFile(bccEncountersFile); + EncounterSet bccEncountersPreNationalDex = encounters.next(); + writeBCCEncountersHGSS(bccEncountersData, 0, bccEncountersPreNationalDex.encounters); + EncounterSet bccEncountersPostNationalDexTues = encounters.next(); + writeBCCEncountersHGSS(bccEncountersData, 80, bccEncountersPostNationalDexTues.encounters); + EncounterSet bccEncountersPostNationalDexThurs = encounters.next(); + writeBCCEncountersHGSS(bccEncountersData, 160, bccEncountersPostNationalDexThurs.encounters); + EncounterSet bccEncountersPostNationalDexSat = encounters.next(); + writeBCCEncountersHGSS(bccEncountersData, 240, bccEncountersPostNationalDexSat.encounters); + + // Save + writeFile(bccEncountersFile, bccEncountersData); + } + + private void writeOptionalEncountersHGSS(byte[] data, int offset, int amount, Iterator encounters) { + Iterator eIter = null; + for (int i = 0; i < amount; i++) { + int origPokemon = readWord(data, offset + i * 2); + if (origPokemon != 0) { + // Need an encounter set, yes. + if (eIter == null) { + eIter = encounters.next().encounters.iterator(); + } + Encounter here = eIter.next(); + writeWord(data, offset + i * 2, here.pokemon.number); + } + } + + } + + private void writeGrassEncounterLevelsHGSS(byte[] data, int offset, List encounters) { + int enclength = encounters.size(); + for (int i = 0; i < enclength; i++) { + data[offset + i] = (byte) encounters.get(i).level; + } + + } + + private void writePokemonHGSS(byte[] data, int offset, List encounters) { + int enclength = encounters.size(); + for (int i = 0; i < enclength; i++) { + writeWord(data, offset + i * 2, encounters.get(i).pokemon.number); + } + + } + + private void writeSeaEncountersHGSS(byte[] data, int offset, List encounters) { + int enclength = encounters.size(); + for (int i = 0; i < enclength; i++) { + Encounter enc = encounters.get(i); + data[offset + i * 4] = (byte) enc.level; + data[offset + i * 4 + 1] = (byte) enc.maxLevel; + writeWord(data, offset + i * 4 + 2, enc.pokemon.number); + } + + } + + private void writeHeadbuttEncountersHGSS(byte[] data, int offset, List encounters) { + int enclength = encounters.size(); + for (int i = 0; i < enclength; i++) { + Encounter enc = encounters.get(i); + writeWord(data, offset + i * 4, enc.pokemon.number); + data[offset + 2 + i * 4] = (byte) enc.level; + data[offset + 3 + i * 4] = (byte) enc.maxLevel; + } + } + + private void writeBCCEncountersHGSS(byte[] data, int offset, List encounters) { + int enclength = encounters.size(); + for (int i = 0; i < enclength; i++) { + Encounter enc = encounters.get(i); + writeWord(data, offset + i * 8, enc.pokemon.number); + data[offset + 2 + i * 8] = (byte) enc.level; + data[offset + 3 + i * 8] = (byte) enc.maxLevel; + } + } + + private List stitchEncsToLevels(Pokemon[] pokemon, int[] levels) { + List encounters = new ArrayList<>(); + for (int i = 0; i < pokemon.length; i++) { + Encounter enc = new Encounter(); + enc.level = levels[i]; + enc.pokemon = pokemon[i]; + encounters.add(enc); + } + return encounters; + } + + private void loadWildMapNames() { + try { + wildMapNames = new HashMap<>(); + headbuttMapNames = new HashMap<>(); + byte[] internalNames = this.readFile(romEntry.getFile("MapTableFile")); + int numMapHeaders = internalNames.length / 16; + int baseMHOffset = romEntry.getInt("MapTableARM9Offset"); + List allMapNames = getStrings(romEntry.getInt("MapNamesTextOffset")); + int mapNameIndexSize = romEntry.getInt("MapTableNameIndexSize"); + for (int map = 0; map < numMapHeaders; map++) { + int baseOffset = baseMHOffset + map * 24; + int mapNameIndex = (mapNameIndexSize == 2) ? readWord(arm9, baseOffset + 18) + : (arm9[baseOffset + 18] & 0xFF); + String mapName = allMapNames.get(mapNameIndex); + if (romEntry.romType == Gen4Constants.Type_HGSS) { + int wildSet = arm9[baseOffset] & 0xFF; + if (wildSet != 255) { + wildMapNames.put(wildSet, mapName); + } + headbuttMapNames.put(map, mapName); + } else { + int wildSet = readWord(arm9, baseOffset + 14); + if (wildSet != 65535) { + wildMapNames.put(wildSet, mapName); + } + } + } + loadedWildMapNames = true; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + private void updatePokedexAreaDataDPPt(List encounters) throws IOException { + String encountersFile = romEntry.getFile("WildPokemon"); + NARCArchive encounterData = readNARC(encountersFile); + + // Initialize empty area data + Set[][] dungeonAreaData = new Set[Gen4Constants.pokemonCount + 1][3]; + Set[] dungeonSpecialPreNationalData = new Set[Gen4Constants.pokemonCount + 1]; + Set[] dungeonSpecialPostNationalData = new Set[Gen4Constants.pokemonCount + 1]; + Set[][] overworldAreaData = new Set[Gen4Constants.pokemonCount + 1][3]; + Set[] overworldSpecialPreNationalData = new Set[Gen4Constants.pokemonCount + 1]; + Set[] overworldSpecialPostNationalData = new Set[Gen4Constants.pokemonCount + 1]; + + for (int pk = 1; pk <= Gen4Constants.pokemonCount; pk++) { + for (int time = 0; time < 3; time++) { + dungeonAreaData[pk][time] = new TreeSet<>(); + overworldAreaData[pk][time] = new TreeSet<>(); + } + dungeonSpecialPreNationalData[pk] = new TreeSet<>(); + dungeonSpecialPostNationalData[pk] = new TreeSet<>(); + overworldSpecialPreNationalData[pk] = new TreeSet<>(); + overworldSpecialPostNationalData[pk] = new TreeSet<>(); + } + + for (int c = 0; c < encounterData.files.size(); c++) { + Set[][] target; + Set[] specialTarget; + int index; + if (Gen4Constants.dpptOverworldDexMaps[c] != -1) { + target = overworldAreaData; + specialTarget = overworldSpecialPostNationalData; + index = Gen4Constants.dpptOverworldDexMaps[c]; + } else if (Gen4Constants.dpptDungeonDexMaps[c] != -1) { + target = dungeonAreaData; + specialTarget = dungeonSpecialPostNationalData; + index = Gen4Constants.dpptDungeonDexMaps[c]; + } else { + continue; + } + + byte[] b = encounterData.files.get(c); + + int grassRate = readLong(b, 0); + if (grassRate != 0) { + // up to 4 + List grassEncounters = readEncountersDPPt(b, 4, 12); + + for (int i = 0; i < 12; i++) { + int pknum = grassEncounters.get(i).pokemon.number; + if (i == 2 || i == 3) { + // morning only - time of day data for day/night for + // these slots + target[pknum][0].add(index); + } else { + // all times of day + target[pknum][0].add(index); + target[pknum][1].add(index); + target[pknum][2].add(index); + } + } + + // time of day data for slots 2 and 3 day/night + for (int i = 0; i < 4; i++) { + int pknum = readLong(b, 108 + 4 * i); + if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { + target[pknum][i > 1 ? 2 : 1].add(index); + } + } + + // For Swarm/Radar/GBA encounters, only Poke Radar encounters appear in the dex + for (int i = 6; i < 10; i++) { + int offs = 100 + i * 4; + int pknum = readLong(b, offs); + if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { + specialTarget[pknum].add(index); + } + } + } + + // up to 204, 5 sets of "sea" encounters to go + int offset = 204; + for (int i = 0; i < 5; i++) { + int rate = readLong(b, offset); + offset += 4; + List encountersHere = readSeaEncountersDPPt(b, offset, 5); + offset += 40; + if (rate == 0 || i == 1) { + continue; + } + for (Encounter enc : encountersHere) { + target[enc.pokemon.number][0].add(index); + target[enc.pokemon.number][1].add(index); + target[enc.pokemon.number][2].add(index); + } + } + } + + // Handle the "special" encounters that aren't in the encounter GARC + for (EncounterSet es : encounters) { + if (es.displayName.contains("Mt. Coronet Feebas Tiles")) { + for (Encounter enc : es.encounters) { + dungeonSpecialPreNationalData[enc.pokemon.number].add(Gen4Constants.dpptMtCoronetDexIndex); + dungeonSpecialPostNationalData[enc.pokemon.number].add(Gen4Constants.dpptMtCoronetDexIndex); + } + } else if (es.displayName.contains("Honey Tree Group 1") || es.displayName.contains("Honey Tree Group 2")) { + for (Encounter enc : es.encounters) { + dungeonSpecialPreNationalData[enc.pokemon.number].add(Gen4Constants.dpptFloaromaMeadowDexIndex); + dungeonSpecialPostNationalData[enc.pokemon.number].add(Gen4Constants.dpptFloaromaMeadowDexIndex); + overworldSpecialPreNationalData[enc.pokemon.number].addAll(Gen4Constants.dpptOverworldHoneyTreeDexIndicies); + overworldSpecialPostNationalData[enc.pokemon.number].addAll(Gen4Constants.dpptOverworldHoneyTreeDexIndicies); + } + } else if (es.displayName.contains("Trophy Garden Rotating Pokemon")) { + for (Encounter enc : es.encounters) { + dungeonSpecialPostNationalData[enc.pokemon.number].add(Gen4Constants.dpptTrophyGardenDexIndex); + } + } else if (es.displayName.contains("Great Marsh Rotating Pokemon (Post-National Dex)")) { + for (Encounter enc : es.encounters) { + dungeonSpecialPostNationalData[enc.pokemon.number].add(Gen4Constants.dpptGreatMarshDexIndex); + } + } else if (es.displayName.contains("Great Marsh Rotating Pokemon (Pre-National Dex)")) { + for (Encounter enc : es.encounters) { + dungeonSpecialPreNationalData[enc.pokemon.number].add(Gen4Constants.dpptGreatMarshDexIndex); + } + } + } + + // Write new area data to its file + // Area data format credit to Ganix + String pokedexAreaDataFile = romEntry.getFile("PokedexAreaData"); + NARCArchive pokedexAreaData = readNARC(pokedexAreaDataFile); + int dungeonDataIndex = romEntry.getInt("PokedexAreaDataDungeonIndex"); + int dungeonSpecialPreNationalDataIndex = romEntry.getInt("PokedexAreaDataDungeonSpecialPreNationalIndex"); + int dungeonSpecialPostNationalDataIndex = romEntry.getInt("PokedexAreaDataDungeonSpecialPostNationalIndex"); + int overworldDataIndex = romEntry.getInt("PokedexAreaDataOverworldIndex"); + int overworldSpecialPreNationalDataIndex = romEntry.getInt("PokedexAreaDataOverworldSpecialPreNationalIndex"); + int overworldSpecialPostNationalDataIndex = romEntry.getInt("PokedexAreaDataOverworldSpecialPostNationalIndex"); + for (int pk = 1; pk <= Gen4Constants.pokemonCount; pk++) { + for (int time = 0; time < 3; time++) { + pokedexAreaData.files.set(dungeonDataIndex + pk + time * Gen4Constants.pokedexAreaDataSize, + makePokedexAreaDataFile(dungeonAreaData[pk][time])); + pokedexAreaData.files.set(overworldDataIndex + pk + time * Gen4Constants.pokedexAreaDataSize, + makePokedexAreaDataFile(overworldAreaData[pk][time])); + } + pokedexAreaData.files.set(dungeonSpecialPreNationalDataIndex + pk, + makePokedexAreaDataFile(dungeonSpecialPreNationalData[pk])); + pokedexAreaData.files.set(dungeonSpecialPostNationalDataIndex + pk, + makePokedexAreaDataFile(dungeonSpecialPostNationalData[pk])); + pokedexAreaData.files.set(overworldSpecialPreNationalDataIndex + pk, + makePokedexAreaDataFile(overworldSpecialPreNationalData[pk])); + pokedexAreaData.files.set(overworldSpecialPostNationalDataIndex + pk, + makePokedexAreaDataFile(overworldSpecialPostNationalData[pk])); + } + writeNARC(pokedexAreaDataFile, pokedexAreaData); + } + + private void updatePokedexAreaDataHGSS(List encounters) throws IOException { + String encountersFile = romEntry.getFile("WildPokemon"); + NARCArchive encounterData = readNARC(encountersFile); + + // Initialize empty area data + Set[][] dungeonAreaData = new Set[Gen4Constants.pokemonCount + 1][3]; + Set[][] overworldAreaData = new Set[Gen4Constants.pokemonCount + 1][3]; + Set[] dungeonSpecialData = new Set[Gen4Constants.pokemonCount + 1]; + Set[] overworldSpecialData = new Set[Gen4Constants.pokemonCount + 1]; + + for (int pk = 1; pk <= Gen4Constants.pokemonCount; pk++) { + for (int time = 0; time < 3; time++) { + dungeonAreaData[pk][time] = new TreeSet<>(); + overworldAreaData[pk][time] = new TreeSet<>(); + } + dungeonSpecialData[pk] = new TreeSet<>(); + overworldSpecialData[pk] = new TreeSet<>(); + } + + for (int c = 0; c < encounterData.files.size(); c++) { + Set[][] target; + Set[] specialTarget; + int index; + if (Gen4Constants.hgssOverworldDexMaps[c] != -1) { + target = overworldAreaData; + specialTarget = overworldSpecialData; + index = Gen4Constants.hgssOverworldDexMaps[c]; + } else if (Gen4Constants.hgssDungeonDexMaps[c] != -1) { + target = dungeonAreaData; + specialTarget = dungeonSpecialData; + index = Gen4Constants.hgssDungeonDexMaps[c]; + } else { + continue; + } + + byte[] b = encounterData.files.get(c); + int[] amounts = new int[]{0, 5, 2, 5, 5, 5}; + int[] rates = new int[6]; + rates[0] = b[0] & 0xFF; + rates[1] = b[1] & 0xFF; + rates[2] = b[2] & 0xFF; + rates[3] = b[3] & 0xFF; + rates[4] = b[4] & 0xFF; + rates[5] = b[5] & 0xFF; + // Up to 20 now (12 for levels) + if (rates[0] != 0) { + for (int time = 0; time < 3; time++) { + Pokemon[] pokes = readPokemonHGSS(b, 20 + time * 24, 12); + for (Pokemon pk : pokes) { + target[pk.number][time].add(index); + } + } + } + + // Hoenn/Sinnoh Radio + EncounterSet radio = readOptionalEncountersHGSS(b, 92, 4); + for (Encounter enc : radio.encounters) { + specialTarget[enc.pokemon.number].add(index); + } + + // Up to 100 now... 2*2*2 for radio pokemon + // Handle surf, rock smash, and old rod + int offset = 100; + for (int i = 1; i < 4; i++) { + List encountersHere = readSeaEncountersHGSS(b, offset, amounts[i]); + offset += 4 * amounts[i]; + if (rates[i] != 0) { + // Valid area. + for (Encounter enc : encountersHere) { + target[enc.pokemon.number][0].add(index); + target[enc.pokemon.number][1].add(index); + target[enc.pokemon.number][2].add(index); + } + } + } + + // Handle good and super rod, because they can get an encounter slot replaced by the night fishing replacement + Pokemon nightFishingReplacement = pokes[readWord(b, 192)]; + if (rates[4] != 0) { + List goodRodEncounters = + readTimeBasedRodEncountersHGSS(b, offset, nightFishingReplacement, Gen4Constants.hgssGoodRodReplacementIndex); + for (Encounter enc : goodRodEncounters.get(0).encounters) { + target[enc.pokemon.number][0].add(index); + target[enc.pokemon.number][1].add(index); + } + for (Encounter enc : goodRodEncounters.get(1).encounters) { + target[enc.pokemon.number][2].add(index); + } + } + if (rates[5] != 0) { + List superRodEncounters = + readTimeBasedRodEncountersHGSS(b, offset + 20, nightFishingReplacement, Gen4Constants.hgssSuperRodReplacementIndex); + for (Encounter enc : superRodEncounters.get(0).encounters) { + target[enc.pokemon.number][0].add(index); + target[enc.pokemon.number][1].add(index); + } + for (Encounter enc : superRodEncounters.get(1).encounters) { + target[enc.pokemon.number][2].add(index); + } + } + } + + // Handle headbutt encounters too (only doing it like this because reading the encounters from the ROM is really annoying) + EncounterSet firstHeadbuttEncounter = encounters.stream().filter(es -> es.displayName.contains("Route 1 Headbutt")).findFirst().orElse(null); + int startingHeadbuttOffset = encounters.indexOf(firstHeadbuttEncounter); + if (startingHeadbuttOffset != -1) { + for (int i = 0; i < Gen4Constants.hgssHeadbuttOverworldDexMaps.length; i++) { + EncounterSet es = encounters.get(startingHeadbuttOffset + i); + for (Encounter enc : es.encounters) { + if (Gen4Constants.hgssHeadbuttOverworldDexMaps[i] != -1) { + overworldSpecialData[enc.pokemon.number].add(Gen4Constants.hgssHeadbuttOverworldDexMaps[i]); + } else if (Gen4Constants.hgssHeadbuttDungeonDexMaps[i] != -1) { + dungeonSpecialData[enc.pokemon.number].add(Gen4Constants.hgssHeadbuttDungeonDexMaps[i]); + } + } + } + } + + // Write new area data to its file + // Area data format credit to Ganix + String pokedexAreaDataFile = romEntry.getFile("PokedexAreaData"); + NARCArchive pokedexAreaData = readNARC(pokedexAreaDataFile); + int dungeonDataIndex = romEntry.getInt("PokedexAreaDataDungeonIndex"); + int overworldDataIndex = romEntry.getInt("PokedexAreaDataOverworldIndex"); + int dungeonSpecialIndex = romEntry.getInt("PokedexAreaDataDungeonSpecialIndex"); + int overworldSpecialDataIndex = romEntry.getInt("PokedexAreaDataOverworldSpecialIndex"); + for (int pk = 1; pk <= Gen4Constants.pokemonCount; pk++) { + for (int time = 0; time < 3; time++) { + pokedexAreaData.files.set(dungeonDataIndex + pk + time * Gen4Constants.pokedexAreaDataSize, + makePokedexAreaDataFile(dungeonAreaData[pk][time])); + pokedexAreaData.files.set(overworldDataIndex + pk + time * Gen4Constants.pokedexAreaDataSize, + makePokedexAreaDataFile(overworldAreaData[pk][time])); + } + pokedexAreaData.files.set(dungeonSpecialIndex + pk, makePokedexAreaDataFile(dungeonSpecialData[pk])); + pokedexAreaData.files.set(overworldSpecialDataIndex + pk, makePokedexAreaDataFile(overworldSpecialData[pk])); + } + writeNARC(pokedexAreaDataFile, pokedexAreaData); + } + + private byte[] makePokedexAreaDataFile(Set data) { + byte[] output = new byte[data.size() * 4 + 4]; + int idx = 0; + for (Integer obj : data) { + int areaIndex = obj; + this.writeLong(output, idx, areaIndex); + idx += 4; + } + return output; + } + + @Override + public List getTrainers() { + List allTrainers = new ArrayList<>(); + try { + NARCArchive trainers = this.readNARC(romEntry.getFile("TrainerData")); + NARCArchive trpokes = this.readNARC(romEntry.getFile("TrainerPokemon")); + List tclasses = this.getTrainerClassNames(); + List tnames = this.getTrainerNames(); + int trainernum = trainers.files.size(); + for (int i = 1; i < trainernum; i++) { + // Trainer entries are 20 bytes + // Team flags; 1 byte; 0x01 = custom moves, 0x02 = held item + // Class; 1 byte + // 1 byte not used + // Number of pokemon in team; 1 byte + // Items; 2 bytes each, 4 item slots + // AI Flags; 2 byte + // 2 bytes not used + // Battle Mode; 1 byte; 0 means single, 1 means double. + // 3 bytes not used + byte[] trainer = trainers.files.get(i); + byte[] trpoke = trpokes.files.get(i); + Trainer tr = new Trainer(); + tr.poketype = trainer[0] & 0xFF; + tr.trainerclass = trainer[1] & 0xFF; + tr.index = i; + int numPokes = trainer[3] & 0xFF; + int pokeOffs = 0; + tr.fullDisplayName = tclasses.get(tr.trainerclass) + " " + tnames.get(i - 1); + for (int poke = 0; poke < numPokes; poke++) { + // Structure is + // IV SB LV LV SP SP FRM FRM + // (HI HI) + // (M1 M1 M2 M2 M3 M3 M4 M4) + // where SB = 0 0 Ab Ab 0 0 G G + // IV is a "difficulty" level between 0 and 255 to represent 0 to 31 IVs. + // These IVs affect all attributes. For the vanilla games, the + // vast majority of trainers have 0 IVs; Elite Four members will + // have 30 IVs. + // Ab Ab = ability number, 0 for first ability, 2 for second [HGSS only] + // G G affect the gender somehow. 0 appears to mean "most common + // gender for the species". + int difficulty = trpoke[pokeOffs] & 0xFF; + int level = trpoke[pokeOffs + 2] & 0xFF; + int species = (trpoke[pokeOffs + 4] & 0xFF) + ((trpoke[pokeOffs + 5] & 0x01) << 8); + int formnum = (trpoke[pokeOffs + 5] >> 2); + TrainerPokemon tpk = new TrainerPokemon(); + tpk.level = level; + tpk.pokemon = pokes[species]; + tpk.IVs = (difficulty * 31) / 255; + int abilitySlot = (trpoke[pokeOffs + 1] >>> 4) & 0xF; + if (abilitySlot == 0) { + // All Gen 4 games represent the first ability as ability 0. + abilitySlot = 1; + } + tpk.abilitySlot = abilitySlot; + tpk.forme = formnum; + tpk.formeSuffix = Gen4Constants.getFormeSuffixByBaseForme(species,formnum); + pokeOffs += 6; + if (tr.pokemonHaveItems()) { + tpk.heldItem = readWord(trpoke, pokeOffs); + pokeOffs += 2; + } + if (tr.pokemonHaveCustomMoves()) { + for (int move = 0; move < 4; move++) { + tpk.moves[move] = readWord(trpoke, pokeOffs + (move*2)); + } + pokeOffs += 8; + } + // Plat/HGSS have another random pokeOffs +=2 here. + if (romEntry.romType != Gen4Constants.Type_DP) { + pokeOffs += 2; + } + tr.pokemon.add(tpk); + } + allTrainers.add(tr); + } + if (romEntry.romType == Gen4Constants.Type_DP) { + Gen4Constants.tagTrainersDP(allTrainers); + Gen4Constants.setMultiBattleStatusDP(allTrainers); + } else if (romEntry.romType == Gen4Constants.Type_Plat) { + Gen4Constants.tagTrainersPt(allTrainers); + Gen4Constants.setMultiBattleStatusPt(allTrainers); + } else { + Gen4Constants.tagTrainersHGSS(allTrainers); + Gen4Constants.setMultiBattleStatusHGSS(allTrainers); + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + return allTrainers; + } + + @Override + public List getMainPlaythroughTrainers() { + return new ArrayList<>(); // Not implemented + } + + @Override + public List getEliteFourTrainers(boolean isChallengeMode) { + return Arrays.stream(romEntry.arrayEntries.get("EliteFourIndices")).boxed().collect(Collectors.toList()); + } + + @Override + public List getEvolutionItems() { + return Gen4Constants.evolutionItems; + } + + @Override + public void setTrainers(List trainerData, boolean doubleBattleMode) { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + fixAbilitySlotValuesForHGSS(trainerData); + } + Iterator allTrainers = trainerData.iterator(); + try { + NARCArchive trainers = this.readNARC(romEntry.getFile("TrainerData")); + NARCArchive trpokes = new NARCArchive(); + + // Get current movesets in case we need to reset them for certain + // trainer mons. + Map> movesets = this.getMovesLearnt(); + + // empty entry + trpokes.files.add(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }); + int trainernum = trainers.files.size(); + for (int i = 1; i < trainernum; i++) { + byte[] trainer = trainers.files.get(i); + Trainer tr = allTrainers.next(); + // preserve original poketype + trainer[0] = (byte) tr.poketype; + int numPokes = tr.pokemon.size(); + trainer[3] = (byte) numPokes; + + if (doubleBattleMode) { + if (!tr.skipImportant()) { + // If we set this flag for partner trainers (e.g., Cheryl), then the double wild battles + // will turn into trainer battles with glitchy trainers. + boolean excludedPartnerTrainer = romEntry.romType != Gen4Constants.Type_HGSS && + Gen4Constants.partnerTrainerIndices.contains(tr.index); + if (trainer[16] == 0 && !excludedPartnerTrainer) { + trainer[16] |= 3; + } + } + } + + int bytesNeeded = 6 * numPokes; + if (romEntry.romType != Gen4Constants.Type_DP) { + bytesNeeded += 2 * numPokes; + } + if (tr.pokemonHaveCustomMoves()) { + bytesNeeded += 8 * numPokes; // 2 bytes * 4 moves + } + if (tr.pokemonHaveItems()) { + bytesNeeded += 2 * numPokes; + } + byte[] trpoke = new byte[bytesNeeded]; + int pokeOffs = 0; + Iterator tpokes = tr.pokemon.iterator(); + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon tp = tpokes.next(); + int ability = tp.abilitySlot << 4; + if (tp.abilitySlot == 1) { + // All Gen 4 games represent the first ability as ability 0. + ability = 0; + } + // Add 1 to offset integer division truncation + int difficulty = Math.min(255, 1 + (tp.IVs * 255) / 31); + writeWord(trpoke, pokeOffs, difficulty | ability << 8); + writeWord(trpoke, pokeOffs + 2, tp.level); + writeWord(trpoke, pokeOffs + 4, tp.pokemon.number); + trpoke[pokeOffs + 5] |= (tp.forme << 2); + pokeOffs += 6; + if (tr.pokemonHaveItems()) { + writeWord(trpoke, pokeOffs, tp.heldItem); + pokeOffs += 2; + } + if (tr.pokemonHaveCustomMoves()) { + if (tp.resetMoves) { + int[] pokeMoves = RomFunctions.getMovesAtLevel(getAltFormeOfPokemon(tp.pokemon, tp.forme).number, movesets, tp.level); + for (int m = 0; m < 4; m++) { + writeWord(trpoke, pokeOffs + m * 2, pokeMoves[m]); + } + } else { + writeWord(trpoke, pokeOffs, tp.moves[0]); + writeWord(trpoke, pokeOffs + 2, tp.moves[1]); + writeWord(trpoke, pokeOffs + 4, tp.moves[2]); + writeWord(trpoke, pokeOffs + 6, tp.moves[3]); + } + pokeOffs += 8; + } + // Plat/HGSS have another random pokeOffs +=2 here. + if (romEntry.romType != Gen4Constants.Type_DP) { + pokeOffs += 2; + } + } + trpokes.files.add(trpoke); + } + this.writeNARC(romEntry.getFile("TrainerData"), trainers); + this.writeNARC(romEntry.getFile("TrainerPokemon"), trpokes); + + // In Gen 4, the game prioritizes showing the special double battle intro over almost any + // other kind of intro. Since the trainer music is tied to the intro, this results in the + // vast majority of "special" trainers losing their intro and music in double battle mode. + // To fix this, the below code patches the executable to skip the case for the special + // double battle intro (by changing a beq to an unconditional branch); this slightly breaks + // battles that are double battles in the original game, but the trade-off is worth it. + + // Then, also patch various subroutines that control the "Trainer Eye" event and text boxes + // related to this in order to make double battles work on all trainers + if (doubleBattleMode) { + String doubleBattleFixPrefix = Gen4Constants.getDoubleBattleFixPrefix(romEntry.romType); + int offset = find(arm9, doubleBattleFixPrefix); + if (offset > 0) { + offset += doubleBattleFixPrefix.length() / 2; // because it was a prefix + arm9[offset] = (byte) 0xE0; + } else { + throw new RandomizationException("Double Battle Mode not supported for this game"); + } + + String doubleBattleFlagReturnPrefix = romEntry.getString("DoubleBattleFlagReturnPrefix"); + String doubleBattleWalkingPrefix1 = romEntry.getString("DoubleBattleWalkingPrefix1"); + String doubleBattleWalkingPrefix2 = romEntry.getString("DoubleBattleWalkingPrefix2"); + String doubleBattleTextBoxPrefix = romEntry.getString("DoubleBattleTextBoxPrefix"); + + // After getting the double battle flag, return immediately instead of converting it to a 1 for + // non-zero values/0 for zero + offset = find(arm9, doubleBattleFlagReturnPrefix); + if (offset > 0) { + offset += doubleBattleFlagReturnPrefix.length() / 2; // because it was a prefix + writeWord(arm9, offset, 0xBD08); + } else { + throw new RandomizationException("Double Battle Mode not supported for this game"); + } + + // Instead of doing "double trainer walk" for nonzero values, do it only for value == 2 + offset = find(arm9, doubleBattleWalkingPrefix1); + if (offset > 0) { + offset += doubleBattleWalkingPrefix1.length() / 2; // because it was a prefix + arm9[offset] = (byte) 0x2; // cmp r0, #0x2 + arm9[offset+3] = (byte) 0xD0; // beq DOUBLE_TRAINER_WALK + } else { + throw new RandomizationException("Double Battle Mode not supported for this game"); + } + + // Instead of checking if the value was exactly 1 after checking that it was nonzero, check that it's + // 2 again lol + offset = find(arm9, doubleBattleWalkingPrefix2); + if (offset > 0) { + offset += doubleBattleWalkingPrefix2.length() / 2; // because it was a prefix + arm9[offset] = (byte) 0x2; + } else { + throw new RandomizationException("Double Battle Mode not supported for this game"); + } + + // Once again, compare a value to 2 instead of just checking that it's nonzero + offset = find(arm9, doubleBattleTextBoxPrefix); + if (offset > 0) { + offset += doubleBattleTextBoxPrefix.length() / 2; // because it was a prefix + writeWord(arm9, offset, 0x46C0); + writeWord(arm9, offset+2, 0x2802); + arm9[offset+5] = (byte) 0xD0; + } else { + throw new RandomizationException("Double Battle Mode not supported for this game"); + } + + // This NARC has some data that controls how text boxes are handled at the end of a trainer battle. + // Changing this byte from 4 -> 0 makes it check if the "double battle" flag is exactly 2 instead of + // checking "flag & 2", which makes the single trainer double battles use the single battle + // handling (since we set their flag to 3 instead of 2) + NARCArchive battleSkillSubSeq = readNARC(romEntry.getFile("BattleSkillSubSeq")); + byte[] trainerEndFile = battleSkillSubSeq.files.get(romEntry.getInt("TrainerEndFileNumber")); + trainerEndFile[romEntry.getInt("TrainerEndTextBoxOffset")] = 0; + writeNARC(romEntry.getFile("BattleSkillSubSeq"), battleSkillSubSeq); + + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + } + + // Note: This method is here to avoid bloating AbstractRomHandler with special-case logic. + // It only works here because nothing in AbstractRomHandler cares about the abilitySlot at + // the moment; if that changes, then this should be moved there instead. + private void fixAbilitySlotValuesForHGSS(List trainers) { + for (Trainer tr : trainers) { + if (tr.pokemon.size() > 0) { + TrainerPokemon lastPokemon = tr.pokemon.get(tr.pokemon.size() - 1); + int lastAbilitySlot = lastPokemon.abilitySlot; + for (int i = 0; i < tr.pokemon.size(); i++) { + // HGSS has a nasty bug where if a single Pokemon with an abilitySlot of 2 + // appears on the trainer's team, then all Pokemon that appear after it in + // the trpoke data will *also* use their second ability in-game, regardless + // of what their abilitySlot is set to. This can mess with the rival's + // starter carrying forward their ability, and can also cause sensible items + // to behave incorrectly. To fix this, we just make sure everything on a + // Trainer's team uses the same abilitySlot. The choice to copy the last + // Pokemon's abilitySlot is arbitrary, but allows us to avoid any special- + // casing involving the rival's starter, since it always appears last. + tr.pokemon.get(i).abilitySlot = lastAbilitySlot; + } + } + } + } + + @Override + public List bannedForWildEncounters() { + // Ban Unown in DPPt because you can't get certain letters outside of Solaceon Ruins. + // Ban Unown in HGSS because they don't show up unless you complete a puzzle in the Ruins of Alph. + return new ArrayList<>(Collections.singletonList(pokes[Species.unown])); + } + + @Override + public List getBannedFormesForTrainerPokemon() { + List banned = new ArrayList<>(); + if (romEntry.romType != Gen4Constants.Type_DP) { + Pokemon giratinaOrigin = this.getAltFormeOfPokemon(pokes[Species.giratina], 1); + if (giratinaOrigin != null) { + // Ban Giratina-O for trainers in Gen 4, since he just instantly transforms + // back to Altered Forme if he's not holding the Griseous Orb. + banned.add(giratinaOrigin); + } + } + return banned; + } + + @Override + public Map> getMovesLearnt() { + Map> movesets = new TreeMap<>(); + try { + NARCArchive movesLearnt = this.readNARC(romEntry.getFile("PokemonMovesets")); + int formeCount = Gen4Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= Gen4Constants.pokemonCount + formeCount; i++) { + Pokemon pkmn = pokes[i]; + byte[] rom; + if (i > Gen4Constants.pokemonCount) { + rom = movesLearnt.files.get(i + Gen4Constants.formeOffset); + } else { + rom = movesLearnt.files.get(i); + } + int moveDataLoc = 0; + List learnt = new ArrayList<>(); + while ((rom[moveDataLoc] & 0xFF) != 0xFF || (rom[moveDataLoc + 1] & 0xFF) != 0xFF) { + int move = (rom[moveDataLoc] & 0xFF); + int level = (rom[moveDataLoc + 1] & 0xFE) >> 1; + if ((rom[moveDataLoc + 1] & 0x01) == 0x01) { + move += 256; + } + MoveLearnt ml = new MoveLearnt(); + ml.level = level; + ml.move = move; + learnt.add(ml); + moveDataLoc += 2; + } + movesets.put(pkmn.number, learnt); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return movesets; + } + + @Override + public void setMovesLearnt(Map> movesets) { + // int[] extraLearnSets = new int[] { 7, 13, 13 }; + // Build up a new NARC + NARCArchive movesLearnt = new NARCArchive(); + // The blank moveset + byte[] blankSet = new byte[] { (byte) 0xFF, (byte) 0xFF, 0, 0 }; + movesLearnt.files.add(blankSet); + int formeCount = Gen4Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= Gen4Constants.pokemonCount + formeCount; i++) { + if (i == Gen4Constants.pokemonCount + 1) { + for (int j = 0; j < Gen4Constants.formeOffset; j++) { + movesLearnt.files.add(blankSet); + } + } + Pokemon pkmn = pokes[i]; + List learnt = movesets.get(pkmn.number); + int sizeNeeded = learnt.size() * 2 + 2; + if ((sizeNeeded % 4) != 0) { + sizeNeeded += 2; + } + byte[] moveset = new byte[sizeNeeded]; + int j = 0; + for (; j < learnt.size(); j++) { + MoveLearnt ml = learnt.get(j); + moveset[j * 2] = (byte) (ml.move & 0xFF); + int levelPart = (ml.level << 1) & 0xFE; + if (ml.move > 255) { + levelPart++; + } + moveset[j * 2 + 1] = (byte) levelPart; + } + moveset[j * 2] = (byte) 0xFF; + moveset[j * 2 + 1] = (byte) 0xFF; + movesLearnt.files.add(moveset); + } + //for (int j = 0; j < extraLearnSets[romEntry.romType]; j++) { + // movesLearnt.files.add(blankSet); + //} + // Save + try { + this.writeNARC(romEntry.getFile("PokemonMovesets"), movesLearnt); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + @Override + public Map> getEggMoves() { + Map> eggMoves = new TreeMap<>(); + try { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + NARCArchive eggMoveNARC = this.readNARC(romEntry.getFile("EggMoves")); + byte[] eggMoveData = eggMoveNARC.files.get(0); + eggMoves = readEggMoves(eggMoveData, 0); + } else { + byte[] fieldOvl = readOverlay(romEntry.getInt("FieldOvlNumber")); + int offset = find(fieldOvl, Gen4Constants.dpptEggMoveTablePrefix); + if (offset > 0) { + offset += Gen4Constants.dpptEggMoveTablePrefix.length() / 2; // because it was a prefix + eggMoves = readEggMoves(fieldOvl, offset); + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + return eggMoves; + } + + @Override + public void setEggMoves(Map> eggMoves) { + try { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + NARCArchive eggMoveNARC = this.readNARC(romEntry.getFile("EggMoves")); + byte[] eggMoveData = eggMoveNARC.files.get(0); + writeEggMoves(eggMoves, eggMoveData, 0); + eggMoveNARC.files.set(0, eggMoveData); + this.writeNARC(romEntry.getFile("EggMoves"), eggMoveNARC); + } else { + byte[] fieldOvl = readOverlay(romEntry.getInt("FieldOvlNumber")); + int offset = find(fieldOvl, Gen4Constants.dpptEggMoveTablePrefix); + if (offset > 0) { + offset += Gen4Constants.dpptEggMoveTablePrefix.length() / 2; // because it was a prefix + writeEggMoves(eggMoves, fieldOvl, offset); + this.writeOverlay(romEntry.getInt("FieldOvlNumber"), fieldOvl); + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private Map> readEggMoves(byte[] data, int startingOffset) { + Map> eggMoves = new TreeMap<>(); + int currentOffset = startingOffset; + int currentSpecies = 0; + List currentMoves = new ArrayList<>(); + int val = FileFunctions.read2ByteInt(data, currentOffset); + + // Egg move data is stored exactly like in Gen 3, so check egg_moves.h in the + // Gen 3 decomps for more info on how this algorithm works. + while (val != 0xFFFF) { + if (val > 20000) { + int species = val - 20000; + if (currentMoves.size() > 0) { + eggMoves.put(currentSpecies, currentMoves); + } + currentSpecies = species; + currentMoves = new ArrayList<>(); + } else { + currentMoves.add(val); + } + currentOffset += 2; + val = FileFunctions.read2ByteInt(data, currentOffset); + } + + // Need to make sure the last entry gets recorded too + if (currentMoves.size() > 0) { + eggMoves.put(currentSpecies, currentMoves); + } + + return eggMoves; + } + + private void writeEggMoves(Map> eggMoves, byte[] data, int startingOffset) { + int currentOffset = startingOffset; + for (int species : eggMoves.keySet()) { + FileFunctions.write2ByteInt(data, currentOffset, species + 20000); + currentOffset += 2; + for (int move : eggMoves.get(species)) { + FileFunctions.write2ByteInt(data, currentOffset, move); + currentOffset += 2; + } + } + } + + private static class ScriptEntry { + private int scriptFile; + private int scriptOffset; + + public ScriptEntry(int scriptFile, int scriptOffset) { + this.scriptFile = scriptFile; + this.scriptOffset = scriptOffset; + } + } + + private static class TextEntry { + private int textIndex; + private int stringNumber; + + public TextEntry(int textIndex, int stringNumber) { + this.textIndex = textIndex; + this.stringNumber = stringNumber; + } + } + + private static class StaticPokemon { + protected ScriptEntry[] speciesEntries; + protected ScriptEntry[] formeEntries; + protected ScriptEntry[] levelEntries; + + public StaticPokemon() { + this.speciesEntries = new ScriptEntry[0]; + this.formeEntries = new ScriptEntry[0]; + this.levelEntries = new ScriptEntry[0]; + } + + public Pokemon getPokemon(Gen4RomHandler parent, NARCArchive scriptNARC) { + return parent.pokes[parent.readWord(scriptNARC.files.get(speciesEntries[0].scriptFile), speciesEntries[0].scriptOffset)]; + } + + public void setPokemon(Gen4RomHandler parent, NARCArchive scriptNARC, Pokemon pkmn) { + int value = pkmn.number; + for (int i = 0; i < speciesEntries.length; i++) { + byte[] file = scriptNARC.files.get(speciesEntries[i].scriptFile); + parent.writeWord(file, speciesEntries[i].scriptOffset, value); + } + } + + public int getForme(NARCArchive scriptNARC) { + if (formeEntries.length == 0) { + return 0; + } + byte[] file = scriptNARC.files.get(formeEntries[0].scriptFile); + return file[formeEntries[0].scriptOffset]; + } + + public void setForme(NARCArchive scriptNARC, int forme) { + for (int i = 0; i < formeEntries.length; i++) { + byte[] file = scriptNARC.files.get(formeEntries[i].scriptFile); + file[formeEntries[i].scriptOffset] = (byte) forme; + } + } + + public int getLevelCount() { + return levelEntries.length; + } + + public int getLevel(NARCArchive scriptNARC, int i) { + if (levelEntries.length <= i) { + return 1; + } + byte[] file = scriptNARC.files.get(levelEntries[i].scriptFile); + return file[levelEntries[i].scriptOffset]; + } + + public void setLevel(NARCArchive scriptNARC, int level, int i) { + if (levelEntries.length > i) { // Might not have a level entry e.g., it's an egg + byte[] file = scriptNARC.files.get(levelEntries[i].scriptFile); + file[levelEntries[i].scriptOffset] = (byte) level; + } + } + } + + private static class StaticPokemonGameCorner extends StaticPokemon { + private TextEntry[] textEntries; + + public StaticPokemonGameCorner() { + super(); + this.textEntries = new TextEntry[0]; + } + + @Override + public void setPokemon(Gen4RomHandler parent, NARCArchive scriptNARC, Pokemon pkmn) { + super.setPokemon(parent, scriptNARC, pkmn); + for (TextEntry textEntry : textEntries) { + List strings = parent.getStrings(textEntry.textIndex); + String originalString = strings.get(textEntry.stringNumber); + // For JP, the first thing after the name is "\x0001". For non-JP, it's "\v0203" + int postNameIndex = originalString.indexOf("\\"); + String newString = pkmn.name.toUpperCase() + originalString.substring(postNameIndex); + strings.set(textEntry.stringNumber, newString); + parent.setStrings(textEntry.textIndex, strings); + } + } + } + + private static class RoamingPokemon { + private int[] speciesCodeOffsets; + private int[] levelCodeOffsets; + private ScriptEntry[] speciesScriptOffsets; + private ScriptEntry[] genderOffsets; + + public RoamingPokemon() { + this.speciesCodeOffsets = new int[0]; + this.levelCodeOffsets = new int[0]; + this.speciesScriptOffsets = new ScriptEntry[0]; + this.genderOffsets = new ScriptEntry[0]; + } + + public Pokemon getPokemon(Gen4RomHandler parent) { + int species = parent.readWord(parent.arm9, speciesCodeOffsets[0]); + return parent.pokes[species]; + } + + public void setPokemon(Gen4RomHandler parent, NARCArchive scriptNARC, Pokemon pkmn) { + int value = pkmn.number; + for (int speciesCodeOffset : speciesCodeOffsets) { + parent.writeWord(parent.arm9, speciesCodeOffset, value); + } + for (ScriptEntry speciesScriptOffset : speciesScriptOffsets) { + byte[] file = scriptNARC.files.get(speciesScriptOffset.scriptFile); + parent.writeWord(file, speciesScriptOffset.scriptOffset, value); + } + int gender = 0; // male (works for genderless Pokemon too) + if (pkmn.genderRatio == 0xFE) { + gender = 1; // female + } + for (ScriptEntry genderOffset : genderOffsets) { + byte[] file = scriptNARC.files.get(genderOffset.scriptFile); + parent.writeWord(file, genderOffset.scriptOffset, gender); + } + } + + public int getLevel(Gen4RomHandler parent) { + if (levelCodeOffsets.length == 0) { + return 1; + } + return parent.arm9[levelCodeOffsets[0]]; + } + + public void setLevel(Gen4RomHandler parent, int level) { + for (int levelCodeOffset : levelCodeOffsets) { + parent.arm9[levelCodeOffset] = (byte) level; + } + } + } + + @Override + public List getStaticPokemon() { + List sp = new ArrayList<>(); + if (!romEntry.staticPokemonSupport) { + return sp; + } + try { + int[] staticEggOffsets = new int[0]; + if (romEntry.arrayEntries.containsKey("StaticEggPokemonOffsets")) { + staticEggOffsets = romEntry.arrayEntries.get("StaticEggPokemonOffsets"); + } + NARCArchive scriptNARC = scriptNarc; + for (int i = 0; i < romEntry.staticPokemon.size(); i++) { + int currentOffset = i; + StaticPokemon statP = romEntry.staticPokemon.get(i); + StaticEncounter se = new StaticEncounter(); + Pokemon newPK = statP.getPokemon(this, scriptNARC); + newPK = getAltFormeOfPokemon(newPK, statP.getForme(scriptNARC)); + se.pkmn = newPK; + se.level = statP.getLevel(scriptNARC, 0); + se.isEgg = Arrays.stream(staticEggOffsets).anyMatch(x-> x == currentOffset); + for (int levelEntry = 1; levelEntry < statP.getLevelCount(); levelEntry++) { + StaticEncounter linkedStatic = new StaticEncounter(); + linkedStatic.pkmn = newPK; + linkedStatic.level = statP.getLevel(scriptNARC, levelEntry); + se.linkedEncounters.add(linkedStatic); + } + sp.add(se); + } + if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { + NARCArchive tradeNARC = this.readNARC(romEntry.getFile("InGameTrades")); + int[] trades = romEntry.arrayEntries.get("StaticPokemonTrades"); + int[] scripts = romEntry.arrayEntries.get("StaticPokemonTradeScripts"); + int[] scriptOffsets = romEntry.arrayEntries.get("StaticPokemonTradeLevelOffsets"); + for (int i = 0; i < trades.length; i++) { + int tradeNum = trades[i]; + byte[] scriptFile = scriptNARC.files.get(scripts[i]); + int level = scriptFile[scriptOffsets[i]]; + StaticEncounter se = new StaticEncounter(pokes[readLong(tradeNARC.files.get(tradeNum), 0)]); + se.level = level; + sp.add(se); + } + } + if (romEntry.getInt("MysteryEggOffset") > 0) { + byte[] ovOverlay = readOverlay(romEntry.getInt("FieldOvlNumber")); + StaticEncounter se = new StaticEncounter(pokes[ovOverlay[romEntry.getInt("MysteryEggOffset")] & 0xFF]); + se.isEgg = true; + sp.add(se); + } + if (romEntry.getInt("FossilTableOffset") > 0) { + byte[] ftData = arm9; + int baseOffset = romEntry.getInt("FossilTableOffset"); + int fossilLevelScriptNum = romEntry.getInt("FossilLevelScriptNumber"); + byte[] fossilLevelScript = scriptNARC.files.get(fossilLevelScriptNum); + int level = fossilLevelScript[romEntry.getInt("FossilLevelOffset")]; + if (romEntry.romType == Gen4Constants.Type_HGSS) { + ftData = readOverlay(romEntry.getInt("FossilTableOvlNumber")); + } + // read the 7 Fossil Pokemon + for (int f = 0; f < Gen4Constants.fossilCount; f++) { + StaticEncounter se = new StaticEncounter(pokes[readWord(ftData, baseOffset + 2 + f * 4)]); + se.level = level; + sp.add(se); + } + } + + if (roamerRandomizationEnabled) { + getRoamers(sp); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return sp; + } + + @Override + public boolean setStaticPokemon(List staticPokemon) { + if (!romEntry.staticPokemonSupport) { + return false; + } + int sptsize = romEntry.arrayEntries.containsKey("StaticPokemonTrades") ? romEntry.arrayEntries + .get("StaticPokemonTrades").length : 0; + int meggsize = romEntry.getInt("MysteryEggOffset") > 0 ? 1 : 0; + int fossilsize = romEntry.getInt("FossilTableOffset") > 0 ? 7 : 0; + if (staticPokemon.size() != romEntry.staticPokemon.size() + sptsize + meggsize + fossilsize + romEntry.roamingPokemon.size()) { + return false; + } + try { + Iterator statics = staticPokemon.iterator(); + NARCArchive scriptNARC = scriptNarc; + for (StaticPokemon statP : romEntry.staticPokemon) { + StaticEncounter se = statics.next(); + statP.setPokemon(this, scriptNARC, se.pkmn); + statP.setForme(scriptNARC, se.pkmn.formeNumber); + statP.setLevel(scriptNARC, se.level, 0); + for (int i = 0; i < se.linkedEncounters.size(); i++) { + StaticEncounter linkedStatic = se.linkedEncounters.get(i); + statP.setLevel(scriptNARC, linkedStatic.level, i + 1); + } + } + if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { + NARCArchive tradeNARC = this.readNARC(romEntry.getFile("InGameTrades")); + int[] trades = romEntry.arrayEntries.get("StaticPokemonTrades"); + int[] scripts = romEntry.arrayEntries.get("StaticPokemonTradeScripts"); + int[] scriptOffsets = romEntry.arrayEntries.get("StaticPokemonTradeLevelOffsets"); + for (int i = 0; i < trades.length; i++) { + int tradeNum = trades[i]; + StaticEncounter se = statics.next(); + Pokemon thisTrade = se.pkmn; + List possibleAbilities = new ArrayList<>(); + possibleAbilities.add(thisTrade.ability1); + if (thisTrade.ability2 > 0) { + possibleAbilities.add(thisTrade.ability2); + } + if (thisTrade.ability3 > 0) { + possibleAbilities.add(thisTrade.ability3); + } + + // Write species and ability + writeLong(tradeNARC.files.get(tradeNum), 0, thisTrade.number); + writeLong(tradeNARC.files.get(tradeNum), 0x1C, + possibleAbilities.get(this.random.nextInt(possibleAbilities.size()))); + + // Write level to script file + byte[] scriptFile = scriptNARC.files.get(scripts[i]); + scriptFile[scriptOffsets[i]] = (byte) se.level; + + // If it's Kenya, write new species name to text file + if (i == 1) { + Map replacements = new TreeMap<>(); + replacements.put(pokes[Species.spearow].name.toUpperCase(), se.pkmn.name); + replaceAllStringsInEntry(romEntry.getInt("KenyaTextOffset"), replacements); + } + } + writeNARC(romEntry.getFile("InGameTrades"), tradeNARC); + } + if (romEntry.getInt("MysteryEggOffset") > 0) { + // Same overlay as MT moves + // Truncate the pokemon# to 1byte, unless it's 0 + int pokenum = statics.next().pkmn.number; + if (pokenum > 255) { + pokenum = this.random.nextInt(255) + 1; + } + byte[] ovOverlay = readOverlay(romEntry.getInt("FieldOvlNumber")); + ovOverlay[romEntry.getInt("MysteryEggOffset")] = (byte) pokenum; + writeOverlay(romEntry.getInt("FieldOvlNumber"), ovOverlay); + } + if (romEntry.getInt("FossilTableOffset") > 0) { + int baseOffset = romEntry.getInt("FossilTableOffset"); + int fossilLevelScriptNum = romEntry.getInt("FossilLevelScriptNumber"); + byte[] fossilLevelScript = scriptNARC.files.get(fossilLevelScriptNum); + if (romEntry.romType == Gen4Constants.Type_HGSS) { + byte[] ftData = readOverlay(romEntry.getInt("FossilTableOvlNumber")); + for (int f = 0; f < Gen4Constants.fossilCount; f++) { + StaticEncounter se = statics.next(); + int pokenum = se.pkmn.number; + writeWord(ftData, baseOffset + 2 + f * 4, pokenum); + fossilLevelScript[romEntry.getInt("FossilLevelOffset")] = (byte) se.level; + } + writeOverlay(romEntry.getInt("FossilTableOvlNumber"), ftData); + } else { + // write to arm9 + for (int f = 0; f < Gen4Constants.fossilCount; f++) { + StaticEncounter se = statics.next(); + int pokenum = se.pkmn.number; + writeWord(arm9, baseOffset + 2 + f * 4, pokenum); + fossilLevelScript[romEntry.getInt("FossilLevelOffset")] = (byte) se.level; + } + } + } + if (roamerRandomizationEnabled) { + setRoamers(statics); + } + if (romEntry.romType == Gen4Constants.Type_Plat) { + patchDistortionWorldGroundCheck(); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return true; + } + + private void getRoamers(List statics) { + if (romEntry.romType == Gen4Constants.Type_DP) { + int offset = romEntry.getInt("RoamingPokemonFunctionStartOffset"); + if (readWord(arm9, offset + 44) != 0) { + // In the original code, the code at this offset would be performing a shift to put + // Cresselia's constant in r7. After applying the patch, this is now a nop, since + // we just pc-relative load it instead. So if a nop isn't here, apply the patch. + applyDiamondPearlRoamerPatch(); + } + } else if (romEntry.romType == Gen4Constants.Type_Plat || romEntry.romType == Gen4Constants.Type_HGSS) { + int firstSpeciesOffset = romEntry.roamingPokemon.get(0).speciesCodeOffsets[0]; + if (arm9.length < firstSpeciesOffset || readWord(arm9, firstSpeciesOffset) == 0) { + // Either the arm9 hasn't been extended, or the patch hasn't been written + int extendBy = romEntry.getInt("Arm9ExtensionSize"); + arm9 = extendARM9(arm9, extendBy, romEntry.getString("TCMCopyingPrefix"), Gen4Constants.arm9Offset); + genericIPSPatch(arm9, "NewRoamerSubroutineTweak"); + } + } + for (int i = 0; i < romEntry.roamingPokemon.size(); i++) { + RoamingPokemon roamer = romEntry.roamingPokemon.get(i); + StaticEncounter se = new StaticEncounter(); + se.pkmn = roamer.getPokemon(this); + se.level = roamer.getLevel(this); + statics.add(se); + } + } + + private void setRoamers(Iterator statics) { + for (int i = 0; i < romEntry.roamingPokemon.size(); i++) { + RoamingPokemon roamer = romEntry.roamingPokemon.get(i); + StaticEncounter roamerEncounter = statics.next(); + roamer.setPokemon(this, scriptNarc, roamerEncounter.pkmn); + roamer.setLevel(this, roamerEncounter.level); + } + } + + private void applyDiamondPearlRoamerPatch() { + int offset = romEntry.getInt("RoamingPokemonFunctionStartOffset"); + + // The original code had an entry for Darkrai; its species ID is pc-relative loaded. Since this + // entry is clearly unused, just replace Darkrai's species ID constant with Cresselia's, since + // in the original code, her ID is computed as 0x7A << 0x2 + FileFunctions.writeFullInt(arm9, offset + 244, Species.cresselia); + + // Now write a pc-relative load to our new constant over where Cresselia's ID is normally mov'd + // into r7 and shifted. + arm9[offset + 42] = 0x32; + arm9[offset + 43] = 0x4F; + arm9[offset + 44] = 0x00; + arm9[offset + 45] = 0x00; + } + + private void patchDistortionWorldGroundCheck() throws IOException { + byte[] fieldOverlay = readOverlay(romEntry.getInt("FieldOvlNumber")); + int offset = find(fieldOverlay, Gen4Constants.distortionWorldGroundCheckPrefix); + if (offset > 0) { + offset += Gen4Constants.distortionWorldGroundCheckPrefix.length() / 2; // because it was a prefix + + // We're now looking at a jump table in the field overlay that determines which intro graphic the game + // should display when encountering a Pokemon that does *not* have a special intro. The Giratina fight + // in the Distortion World uses ground type 23, and that particular ground type never initializes the + // variable that determines which graphic to use. As a result, if Giratina is replaced with a Pokemon + // that lacks a special intro, the game will use an uninitialized value for the intro graphic and crash. + // The below code simply patches the jump table entry for ground type 23 to take the same branch that + // regular grass encounters take, ensuring the intro graphic variable is initialized. + fieldOverlay[offset + (2 * 23)] = 0x30; + writeOverlay(romEntry.getInt("FieldOvlNumber"), fieldOverlay); + } + } + + @Override + public List getTMMoves() { + String tmDataPrefix; + if (romEntry.romType == Gen4Constants.Type_DP || romEntry.romType == Gen4Constants.Type_Plat) { + tmDataPrefix = Gen4Constants.dpptTMDataPrefix; + } else { + tmDataPrefix = Gen4Constants.hgssTMDataPrefix; + } + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += tmDataPrefix.length() / 2; // because it was a prefix + List tms = new ArrayList<>(); + for (int i = 0; i < Gen4Constants.tmCount; i++) { + tms.add(readWord(arm9, offset + i * 2)); + } + return tms; + } else { + return null; + } + } + + @Override + public List getHMMoves() { + String tmDataPrefix; + if (romEntry.romType == Gen4Constants.Type_DP || romEntry.romType == Gen4Constants.Type_Plat) { + tmDataPrefix = Gen4Constants.dpptTMDataPrefix; + } else { + tmDataPrefix = Gen4Constants.hgssTMDataPrefix; + } + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += tmDataPrefix.length() / 2; // because it was a prefix + offset += Gen4Constants.tmCount * 2; // TM data + List hms = new ArrayList<>(); + for (int i = 0; i < Gen4Constants.hmCount; i++) { + hms.add(readWord(arm9, offset + i * 2)); + } + return hms; + } else { + return null; + } + } + + @Override + public void setTMMoves(List moveIndexes) { + List oldMoveIndexes = this.getTMMoves(); + String tmDataPrefix; + if (romEntry.romType == Gen4Constants.Type_DP || romEntry.romType == Gen4Constants.Type_Plat) { + tmDataPrefix = Gen4Constants.dpptTMDataPrefix; + } else { + tmDataPrefix = Gen4Constants.hgssTMDataPrefix; + } + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += tmDataPrefix.length() / 2; // because it was a prefix + for (int i = 0; i < Gen4Constants.tmCount; i++) { + writeWord(arm9, offset + i * 2, moveIndexes.get(i)); + } + + // Update TM item descriptions + List itemDescriptions = getStrings(romEntry.getInt("ItemDescriptionsTextOffset")); + List moveDescriptions = getStrings(romEntry.getInt("MoveDescriptionsTextOffset")); + int textCharsPerLine = Gen4Constants.getTextCharsPerLine(romEntry.romType); + // TM01 is item 328 and so on + for (int i = 0; i < Gen4Constants.tmCount; i++) { + // Rewrite 5-line move descs into 3-line item descs + itemDescriptions.set(i + Gen4Constants.tmItemOffset, RomFunctions.rewriteDescriptionForNewLineSize( + moveDescriptions.get(moveIndexes.get(i)), "\\n", textCharsPerLine, ssd)); + } + // Save the new item descriptions + setStrings(romEntry.getInt("ItemDescriptionsTextOffset"), itemDescriptions); + // Palettes update + String baseOfPalettes = Gen4Constants.pthgssItemPalettesPrefix; + if (romEntry.romType == Gen4Constants.Type_DP) { + baseOfPalettes = Gen4Constants.dpItemPalettesPrefix; + } + int offsPals = find(arm9, baseOfPalettes); + if (offsPals > 0) { + // Write pals + for (int i = 0; i < Gen4Constants.tmCount; i++) { + Move m = this.moves[moveIndexes.get(i)]; + int pal = this.typeTMPaletteNumber(m.type); + writeWord(arm9, offsPals + i * 8 + 2, pal); + } + } + // if we can't update the palettes, it's not a big deal... + + // Update TM Text + for (int i = 0; i < Gen4Constants.tmCount; i++) { + int oldMoveIndex = oldMoveIndexes.get(i); + int newMoveIndex = moveIndexes.get(i); + int tmNumber = i + 1; + + if (romEntry.tmTexts.containsKey(tmNumber)) { + List textEntries = romEntry.tmTexts.get(tmNumber); + Set textFiles = new HashSet<>(); + for (TextEntry textEntry : textEntries) { + textFiles.add(textEntry.textIndex); + } + String oldMoveName = moves[oldMoveIndex].name; + String newMoveName = moves[newMoveIndex].name; + if (romEntry.romType == Gen4Constants.Type_HGSS && oldMoveIndex == Moves.roar) { + // It's somewhat dumb to even be bothering with this, but it's too silly not to do + oldMoveName = oldMoveName.toUpperCase(); + newMoveName = newMoveName.toUpperCase(); + } + Map replacements = new TreeMap<>(); + replacements.put(oldMoveName, newMoveName); + for (int textFile : textFiles) { + replaceAllStringsInEntry(textFile, replacements); + } + } + + if (romEntry.tmTextsGameCorner.containsKey(tmNumber)) { + TextEntry textEntry = romEntry.tmTextsGameCorner.get(tmNumber); + setBottomScreenTMText(textEntry.textIndex, textEntry.stringNumber, newMoveIndex); + } + + if (romEntry.tmScriptOffsetsFrontier.containsKey(tmNumber)) { + int scriptFile = romEntry.getInt("FrontierScriptNumber"); + byte[] frontierScript = scriptNarc.files.get(scriptFile); + int scriptOffset = romEntry.tmScriptOffsetsFrontier.get(tmNumber); + writeWord(frontierScript, scriptOffset, newMoveIndex); + scriptNarc.files.set(scriptFile, frontierScript); + } + + if (romEntry.tmTextsFrontier.containsKey(tmNumber)) { + int textOffset = romEntry.getInt("MiscUITextOffset"); + int stringNumber = romEntry.tmTextsFrontier.get(tmNumber); + setBottomScreenTMText(textOffset, stringNumber, newMoveIndex); + } + } + } + } + + private void setBottomScreenTMText(int textOffset, int stringNumber, int newMoveIndex) { + List strings = getStrings(textOffset); + String originalString = strings.get(stringNumber); + + // The first thing after the name is "\n". + int postNameIndex = originalString.indexOf("\\"); + String originalName = originalString.substring(0, postNameIndex); + + // Some languages (like English) write the name in ALL CAPS, others don't. + // Check if the original is ALL CAPS and then match it for consistency. + boolean isAllCaps = originalName.equals(originalName.toUpperCase()); + String newName = moves[newMoveIndex].name; + if (isAllCaps) { + newName = newName.toUpperCase(); + } + String newString = newName + originalString.substring(postNameIndex); + strings.set(stringNumber, newString); + setStrings(textOffset, strings); + } + + private static RomFunctions.StringSizeDeterminer ssd = new RomFunctions.StringLengthSD(); + + @Override + public int getTMCount() { + return Gen4Constants.tmCount; + } + + @Override + public int getHMCount() { + return Gen4Constants.hmCount; + } + + @Override + public Map getTMHMCompatibility() { + Map compat = new TreeMap<>(); + int formeCount = Gen4Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= Gen4Constants.pokemonCount + formeCount; i++) { + byte[] data; + if (i > Gen4Constants.pokemonCount) { + data = pokeNarc.files.get(i + Gen4Constants.formeOffset); + } else { + data = pokeNarc.files.get(i); + } + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen4Constants.tmCount + Gen4Constants.hmCount + 1]; + for (int j = 0; j < 13; j++) { + readByteIntoFlags(data, flags, j * 8 + 1, Gen4Constants.bsTMHMCompatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map compatData) { + for (Map.Entry compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + byte[] data = pokeNarc.files.get(pkmn.number); + for (int j = 0; j < 13; j++) { + data[Gen4Constants.bsTMHMCompatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public boolean hasMoveTutors() { + return romEntry.romType != Gen4Constants.Type_DP; + } + + @Override + public List getMoveTutorMoves() { + if (!hasMoveTutors()) { + return new ArrayList<>(); + } + int baseOffset = romEntry.getInt("MoveTutorMovesOffset"); + int amount = romEntry.getInt("MoveTutorCount"); + int bytesPer = romEntry.getInt("MoveTutorBytesCount"); + List mtMoves = new ArrayList<>(); + try { + byte[] mtFile = readOverlay(romEntry.getInt("FieldOvlNumber")); + for (int i = 0; i < amount; i++) { + mtMoves.add(readWord(mtFile, baseOffset + i * bytesPer)); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return mtMoves; + } + + @Override + public void setMoveTutorMoves(List moves) { + if (!hasMoveTutors()) { + return; + } + int baseOffset = romEntry.getInt("MoveTutorMovesOffset"); + int amount = romEntry.getInt("MoveTutorCount"); + int bytesPer = romEntry.getInt("MoveTutorBytesCount"); + if (moves.size() != amount) { + return; + } + try { + byte[] mtFile = readOverlay(romEntry.getInt("FieldOvlNumber")); + for (int i = 0; i < amount; i++) { + writeWord(mtFile, baseOffset + i * bytesPer, moves.get(i)); + } + writeOverlay(romEntry.getInt("FieldOvlNumber"), mtFile); + + // In HGSS, Headbutt is the last tutor move, but the tutor teaches it + // to you via a hardcoded script rather than looking at this data + if (romEntry.romType == Gen4Constants.Type_HGSS) { + setHGSSHeadbuttTutor(moves.get(moves.size() - 1)); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void setHGSSHeadbuttTutor(int headbuttReplacement) { + byte[] ilexForestScripts = scriptNarc.files.get(Gen4Constants.ilexForestScriptFile); + for (int offset : Gen4Constants.headbuttTutorScriptOffsets) { + writeWord(ilexForestScripts, offset, headbuttReplacement); + } + + String replacementName = moves[headbuttReplacement].name; + Map replacements = new TreeMap<>(); + replacements.put(moves[Moves.headbutt].name, replacementName); + replaceAllStringsInEntry(Gen4Constants.ilexForestStringsFile, replacements); + } + + @Override + public Map getMoveTutorCompatibility() { + if (!hasMoveTutors()) { + return new TreeMap<>(); + } + Map compat = new TreeMap<>(); + int amount = romEntry.getInt("MoveTutorCount"); + int baseOffset = romEntry.getInt("MoveTutorCompatOffset"); + int bytesPer = romEntry.getInt("MoveTutorCompatBytesCount"); + try { + byte[] mtcFile; + if (romEntry.romType == Gen4Constants.Type_HGSS) { + mtcFile = readFile(romEntry.getFile("MoveTutorCompat")); + } else { + mtcFile = readOverlay(romEntry.getInt("MoveTutorCompatOvlNumber")); + } + int formeCount = Gen4Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= Gen4Constants.pokemonCount + formeCount; i++) { + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[amount + 1]; + for (int j = 0; j < bytesPer; j++) { + if (i > Gen4Constants.pokemonCount) { + readByteIntoFlags(mtcFile, flags, j * 8 + 1, baseOffset + (i - 1) * bytesPer + j); + } else { + readByteIntoFlags(mtcFile, flags, j * 8 + 1, baseOffset + (i - 1) * bytesPer + j); + } + } + compat.put(pkmn, flags); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return compat; + } + + @Override + public void setMoveTutorCompatibility(Map compatData) { + if (!hasMoveTutors()) { + return; + } + int amount = romEntry.getInt("MoveTutorCount"); + int baseOffset = romEntry.getInt("MoveTutorCompatOffset"); + int bytesPer = romEntry.getInt("MoveTutorCompatBytesCount"); + try { + byte[] mtcFile; + if (romEntry.romType == Gen4Constants.Type_HGSS) { + mtcFile = readFile(romEntry.getFile("MoveTutorCompat")); + } else { + mtcFile = readOverlay(romEntry.getInt("MoveTutorCompatOvlNumber")); + } + for (Map.Entry compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + for (int j = 0; j < bytesPer; j++) { + int offsHere = baseOffset + (pkmn.number - 1) * bytesPer + j; + if (j * 8 + 8 <= amount) { + // entirely new byte + mtcFile[offsHere] = getByteFromFlags(flags, j * 8 + 1); + } else if (j * 8 < amount) { + // need some of the original byte + int newByte = getByteFromFlags(flags, j * 8 + 1) & 0xFF; + int oldByteParts = (mtcFile[offsHere] >>> (8 - amount + j * 8)) << (8 - amount + j * 8); + mtcFile[offsHere] = (byte) (newByte | oldByteParts); + } + // else do nothing to the byte + } + } + if (romEntry.romType == Gen4Constants.Type_HGSS) { + writeFile(romEntry.getFile("MoveTutorCompat"), mtcFile); + } else { + writeOverlay(romEntry.getInt("MoveTutorCompatOvlNumber"), mtcFile); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private int find(byte[] data, String hexString) { + if (hexString.length() % 2 != 0) { + return -3; // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + List found = RomFunctions.search(data, searchFor); + if (found.size() == 0) { + return -1; // not found + } else if (found.size() > 1) { + return -2; // not unique + } else { + return found.get(0); + } + } + + private boolean lastStringsCompressed = false; + + private List getStrings(int index) { + PokeTextData pt = new PokeTextData(msgNarc.files.get(index)); + pt.decrypt(); + lastStringsCompressed = pt.compressFlag; + return new ArrayList<>(pt.strlist); + } + + private void setStrings(int index, List newStrings) { + setStrings(index, newStrings, false); + } + + private void setStrings(int index, List newStrings, boolean compressed) { + byte[] rawUnencrypted = TextToPoke.MakeFile(newStrings, compressed); + + // make new encrypted name set + PokeTextData encrypt = new PokeTextData(rawUnencrypted); + encrypt.SetKey(0xD00E); + encrypt.encrypt(); + + // rewrite + msgNarc.files.set(index, encrypt.get()); + } + + @Override + public String getROMName() { + return "Pokemon " + romEntry.name; + } + + @Override + public String getROMCode() { + return romEntry.romCode; + } + + @Override + public String getSupportLevel() { + return romEntry.staticPokemonSupport ? "Complete" : "No Static Pokemon"; + } + + @Override + public boolean hasTimeBasedEncounters() { + // dppt technically do but we ignore them completely + return romEntry.romType == Gen4Constants.Type_HGSS; + } + + @Override + public boolean hasWildAltFormes() { + return false; + } + + @Override + public boolean canChangeStaticPokemon() { + return romEntry.staticPokemonSupport; + } + + @Override + public boolean hasStaticAltFormes() { + return false; + } + + @Override + public boolean hasMainGameLegendaries() { + return true; + } + + @Override + public List getMainGameLegendaries() { + return Arrays.stream(romEntry.arrayEntries.get("MainGameLegendaries")).boxed().collect(Collectors.toList()); + } + + @Override + public List getSpecialMusicStatics() { + return Arrays.stream(romEntry.arrayEntries.get("SpecialMusicStatics")).boxed().collect(Collectors.toList()); + } + + @Override + public List getTotemPokemon() { + return new ArrayList<>(); + } + + @Override + public void setTotemPokemon(List totemPokemon) { + + } + + @Override + public boolean hasStarterAltFormes() { + return false; + } + + @Override + public int starterCount() { + return 3; + } + + @Override + public Map getUpdatedPokemonStats(int generation) { + return GlobalConstants.getStatChanges(generation); + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + // Read NARC + try { + NARCArchive evoNARC = readNARC(romEntry.getFile("PokemonEvolutions")); + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + Pokemon pk = pokes[i]; + byte[] evoEntry = evoNARC.files.get(i); + for (int evo = 0; evo < 7; evo++) { + int method = readWord(evoEntry, evo * 6); + int species = readWord(evoEntry, evo * 6 + 4); + if (method >= 1 && method <= Gen4Constants.evolutionMethodCount && species >= 1) { + EvolutionType et = EvolutionType.fromIndex(4, method); + int extraInfo = readWord(evoEntry, evo * 6 + 2); + Evolution evol = new Evolution(pokes[i], pokes[species], true, et, extraInfo); + if (!pk.evolutionsFrom.contains(evol)) { + pk.evolutionsFrom.add(evol); + pokes[species].evolutionsTo.add(evol); + } + } + } + // Split evos shouldn't carry stats unless the evo is Nincada's + // In that case, we should have Ninjask carry stats + if (pk.evolutionsFrom.size() > 1) { + for (Evolution e : pk.evolutionsFrom) { + if (e.type != EvolutionType.LEVEL_CREATE_EXTRA) { + e.carryStats = false; + } + } + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void writeEvolutions() { + try { + NARCArchive evoNARC = readNARC(romEntry.getFile("PokemonEvolutions")); + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + byte[] evoEntry = evoNARC.files.get(i); + Pokemon pk = pokes[i]; + if (pk.number == Species.nincada) { + writeShedinjaEvolution(); + } + int evosWritten = 0; + for (Evolution evo : pk.evolutionsFrom) { + writeWord(evoEntry, evosWritten * 6, evo.type.toIndex(4)); + writeWord(evoEntry, evosWritten * 6 + 2, evo.extraInfo); + writeWord(evoEntry, evosWritten * 6 + 4, evo.to.number); + evosWritten++; + if (evosWritten == 7) { + break; + } + } + while (evosWritten < 7) { + writeWord(evoEntry, evosWritten * 6, 0); + writeWord(evoEntry, evosWritten * 6 + 2, 0); + writeWord(evoEntry, evosWritten * 6 + 4, 0); + evosWritten++; + } + } + writeNARC(romEntry.getFile("PokemonEvolutions"), evoNARC); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void writeShedinjaEvolution() { + Pokemon nincada = pokes[Species.nincada]; + + // When the "Limit Pokemon" setting is enabled and Gen 3 is disabled, or when + // "Random Every Level" evolutions are selected, we end up clearing out Nincada's + // vanilla evolutions. In that case, there's no point in even worrying about + // Shedinja, so just return. + if (nincada.evolutionsFrom.size() < 2) { + return; + } + Pokemon extraEvolution = nincada.evolutionsFrom.get(1).to; + + // In all the Gen 4 games, the game is hardcoded to check for + // the LEVEL_IS_EXTRA evolution method; if it the Pokemon has it, + // then a harcoded Shedinja is generated after every evolution + // by using the following instructions: + // mov r0, #0x49 + // lsl r0, r0, #2 + // The below code tweaks this instruction to load the species ID of Nincada's + // new extra evolution into r0 using an 8-bit addition. Since Gen 4 has fewer + // than 510 species in it, this will always succeed. + int offset = find(arm9, Gen4Constants.shedinjaSpeciesLocator); + if (offset > 0) { + int lowByte, highByte; + if (extraEvolution.number < 256) { + lowByte = extraEvolution.number; + highByte = 0; + } else { + lowByte = 255; + highByte = extraEvolution.number - 255; + } + + // mov r0, lowByte + // add r0, r0, highByte + arm9[offset] = (byte) lowByte; + arm9[offset + 1] = 0x20; + arm9[offset + 2] = (byte) highByte; + arm9[offset + 3] = 0x30; + } + } + + @Override + public void removeImpossibleEvolutions(Settings settings) { + boolean changeMoveEvos = !(settings.getMovesetsMod() == Settings.MovesetsMod.UNCHANGED); + + Map> movesets = this.getMovesLearnt(); + Set extraEvolutions = new HashSet<>(); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + extraEvolutions.clear(); + for (Evolution evo : pkmn.evolutionsFrom) { + // new 160 other impossible evolutions: + if (romEntry.romType == Gen4Constants.Type_HGSS) { + // beauty milotic + if (evo.type == EvolutionType.LEVEL_HIGH_BEAUTY) { + // Replace w/ level 35 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 35; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // mt.coronet (magnezone/probopass) + if (evo.type == EvolutionType.LEVEL_ELECTRIFIED_AREA) { + // Replace w/ level 40 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 40; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // moss rock (leafeon) + if (evo.type == EvolutionType.LEVEL_MOSS_ROCK) { + // Replace w/ leaf stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.leafStone; + addEvoUpdateStone(impossibleEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } + // icy rock (glaceon) + if (evo.type == EvolutionType.LEVEL_ICY_ROCK) { + // Replace w/ dawn stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.dawnStone; + addEvoUpdateStone(impossibleEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } + } + if (changeMoveEvos && evo.type == EvolutionType.LEVEL_WITH_MOVE) { + // read move + int move = evo.extraInfo; + int levelLearntAt = 1; + for (MoveLearnt ml : movesets.get(evo.from.number)) { + if (ml.move == move) { + levelLearntAt = ml.level; + break; + } + } + if (levelLearntAt == 1) { + // override for piloswine + levelLearntAt = 45; + } + // change to pure level evo + evo.type = EvolutionType.LEVEL; + evo.extraInfo = levelLearntAt; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // Pure Trade + if (evo.type == EvolutionType.TRADE) { + // Replace w/ level 37 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // Trade w/ Item + if (evo.type == EvolutionType.TRADE_ITEM) { + // Get the current item & evolution + int item = evo.extraInfo; + if (evo.from.number == Species.slowpoke) { + // Slowpoke is awkward - he already has a level evo + // So we can't do Level up w/ Held Item for him + // Put Water Stone instead + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.waterStone; + addEvoUpdateStone(impossibleEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + addEvoUpdateHeldItem(impossibleEvolutionUpdates, evo, itemNames.get(item)); + // Replace, for this entry, w/ + // Level up w/ Held Item at Day + evo.type = EvolutionType.LEVEL_ITEM_DAY; + // now add an extra evo for + // Level up w/ Held Item at Night + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_NIGHT, item); + extraEvolutions.add(extraEntry); + } + } + } + pkmn.evolutionsFrom.addAll(extraEvolutions); + for (Evolution ev : extraEvolutions) { + ev.to.evolutionsTo.add(ev); + } + } + } + + } + + @Override + public void makeEvolutionsEasier(Settings settings) { + boolean wildsRandomized = !settings.getWildPokemonMod().equals(Settings.WildPokemonMod.UNCHANGED); + + // Reduce the amount of happiness required to evolve. + int offset = find(arm9, Gen4Constants.friendshipValueForEvoLocator); + if (offset > 0) { + // Amount of required happiness for HAPPINESS evolutions. + if (arm9[offset] == (byte)220) { + arm9[offset] = (byte)160; + } + // Amount of required happiness for HAPPINESS_DAY evolutions. + if (arm9[offset + 22] == (byte)220) { + arm9[offset + 22] = (byte)160; + } + // Amount of required happiness for HAPPINESS_NIGHT evolutions. + if (arm9[offset + 44] == (byte)220) { + arm9[offset + 44] = (byte)160; + } + } + + if (wildsRandomized) { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + for (Evolution evo : pkmn.evolutionsFrom) { + if (evo.type == EvolutionType.LEVEL_WITH_OTHER) { + // Replace w/ level 35 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 35; + addEvoUpdateCondensed(easierEvolutionUpdates, evo, false); + } + } + } + } + } + } + + @Override + public void removeTimeBasedEvolutions() { + Set extraEvolutions = new HashSet<>(); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + extraEvolutions.clear(); + for (Evolution evo : pkmn.evolutionsFrom) { + if (evo.type == EvolutionType.HAPPINESS_DAY) { + if (evo.from.number == Species.eevee) { + // We can't set Eevee to evolve into Espeon with happiness at night because that's how + // Umbreon works in the original game. Instead, make Eevee: == sun stone => Espeon + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.sunStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + // Add an extra evo for Happiness at Night + addEvoUpdateHappiness(timeBasedEvolutionUpdates, evo); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.HAPPINESS_NIGHT, 0); + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.HAPPINESS_NIGHT) { + if (evo.from.number == Species.eevee) { + // We can't set Eevee to evolve into Umbreon with happiness at day because that's how + // Espeon works in the original game. Instead, make Eevee: == moon stone => Umbreon + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.moonStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + // Add an extra evo for Happiness at Day + addEvoUpdateHappiness(timeBasedEvolutionUpdates, evo); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.HAPPINESS_DAY, 0); + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.LEVEL_ITEM_DAY) { + int item = evo.extraInfo; + // Make sure we don't already have an evo for the same item at night (e.g., when using Change Impossible Evos) + if (evo.from.evolutionsFrom.stream().noneMatch(e -> e.type == EvolutionType.LEVEL_ITEM_NIGHT && e.extraInfo == item)) { + // Add an extra evo for Level w/ Item During Night + addEvoUpdateHeldItem(timeBasedEvolutionUpdates, evo, itemNames.get(item)); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_NIGHT, item); + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.LEVEL_ITEM_NIGHT) { + int item = evo.extraInfo; + // Make sure we don't already have an evo for the same item at day (e.g., when using Change Impossible Evos) + if (evo.from.evolutionsFrom.stream().noneMatch(e -> e.type == EvolutionType.LEVEL_ITEM_DAY && e.extraInfo == item)) { + // Add an extra evo for Level w/ Item During Day + addEvoUpdateHeldItem(timeBasedEvolutionUpdates, evo, itemNames.get(item)); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_DAY, item); + extraEvolutions.add(extraEntry); + } + } + } + pkmn.evolutionsFrom.addAll(extraEvolutions); + for (Evolution ev : extraEvolutions) { + ev.to.evolutionsTo.add(ev); + } + } + } + + } + + @Override + public boolean hasShopRandomization() { + return true; + } + + @Override + public Map getShopItems() { + List shopNames = Gen4Constants.getShopNames(romEntry.romType); + List mainGameShops = Arrays.stream(romEntry.arrayEntries.get("MainGameShops")).boxed().collect(Collectors.toList()); + List skipShops = Arrays.stream(romEntry.arrayEntries.get("SkipShops")).boxed().collect(Collectors.toList()); + int shopCount = romEntry.getInt("ShopCount"); + Map shopItemsMap = new TreeMap<>(); + String shopDataPrefix = romEntry.getString("ShopDataPrefix"); + int offset = find(arm9,shopDataPrefix); + offset += shopDataPrefix.length() / 2; + + for (int i = 0; i < shopCount; i++) { + if (!skipShops.contains(i)) { + List items = new ArrayList<>(); + int val = (FileFunctions.read2ByteInt(arm9, offset)); + while ((val & 0xFFFF) != 0xFFFF) { + if (val != 0) { + items.add(val); + } + offset += 2; + val = (FileFunctions.read2ByteInt(arm9, offset)); + } + offset += 2; + Shop shop = new Shop(); + shop.items = items; + shop.name = shopNames.get(i); + shop.isMainGame = mainGameShops.contains(i); + shopItemsMap.put(i, shop); + } else { + while ((FileFunctions.read2ByteInt(arm9, offset) & 0xFFFF) != 0xFFFF) { + offset += 2; + } + offset += 2; + } + } + return shopItemsMap; + } + + @Override + public void setShopItems(Map shopItems) { + int shopCount = romEntry.getInt("ShopCount"); + String shopDataPrefix = romEntry.getString("ShopDataPrefix"); + int offset = find(arm9,shopDataPrefix); + offset += shopDataPrefix.length() / 2; + + for (int i = 0; i < shopCount; i++) { + Shop thisShop = shopItems.get(i); + if (thisShop == null || thisShop.items == null) { + while ((FileFunctions.read2ByteInt(arm9, offset) & 0xFFFF) != 0xFFFF) { + offset += 2; + } + offset += 2; + continue; + } + Iterator iterItems = thisShop.items.iterator(); + int val = (FileFunctions.read2ByteInt(arm9, offset)); + while ((val & 0xFFFF) != 0xFFFF) { + if (val != 0) { + FileFunctions.write2ByteInt(arm9,offset,iterItems.next()); + } + offset += 2; + val = (FileFunctions.read2ByteInt(arm9, offset)); + } + offset += 2; + } + } + + @Override + public void setShopPrices() { + try { + // In Diamond and Pearl, item IDs 112 through 134 are unused. In Platinum and HGSS, item ID 112 is used for + // the Griseous Orb. So we need to skip through the unused IDs at different points depending on the game. + int startOfUnusedIDs = romEntry.romType == Gen4Constants.Type_DP ? 112 : 113; + NARCArchive itemPriceNarc = this.readNARC(romEntry.getFile("ItemData")); + int itemID = 1; + for (int i = 1; i < itemPriceNarc.files.size(); i++) { + writeWord(itemPriceNarc.files.get(i),0,Gen4Constants.balancedItemPrices.get(itemID) * 10); + itemID++; + if (itemID == startOfUnusedIDs) { + itemID = 135; + } + } + writeNARC(romEntry.getFile("ItemData"),itemPriceNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getPickupItems() { + List pickupItems = new ArrayList<>(); + try { + byte[] battleOverlay = readOverlay(romEntry.getInt("BattleOvlNumber")); + if (pickupItemsTableOffset == 0) { + int offset = find(battleOverlay, Gen4Constants.pickupTableLocator); + if (offset > 0) { + pickupItemsTableOffset = offset; + } + } + + // If we haven't found the pickup table for this ROM already, find it. + if (rarePickupItemsTableOffset == 0) { + int offset = find(battleOverlay, Gen4Constants.rarePickupTableLocator); + if (offset > 0) { + rarePickupItemsTableOffset = offset; + } + } + + // Assuming we've found the pickup table, extract the items out of it. + if (pickupItemsTableOffset > 0 && rarePickupItemsTableOffset > 0) { + for (int i = 0; i < Gen4Constants.numberOfCommonPickupItems; i++) { + int itemOffset = pickupItemsTableOffset + (2 * i); + int item = FileFunctions.read2ByteInt(battleOverlay, itemOffset); + PickupItem pickupItem = new PickupItem(item); + pickupItems.add(pickupItem); + } + for (int i = 0; i < Gen4Constants.numberOfRarePickupItems; i++) { + int itemOffset = rarePickupItemsTableOffset + (2 * i); + int item = FileFunctions.read2ByteInt(battleOverlay, itemOffset); + PickupItem pickupItem = new PickupItem(item); + pickupItems.add(pickupItem); + } + } + + // Assuming we got the items from the last step, fill out the probabilities. + if (pickupItems.size() > 0) { + for (int levelRange = 0; levelRange < 10; levelRange++) { + int startingCommonItemOffset = levelRange; + int startingRareItemOffset = 18 + levelRange; + pickupItems.get(startingCommonItemOffset).probabilities[levelRange] = 30; + for (int i = 1; i < 7; i++) { + pickupItems.get(startingCommonItemOffset + i).probabilities[levelRange] = 10; + } + pickupItems.get(startingCommonItemOffset + 7).probabilities[levelRange] = 4; + pickupItems.get(startingCommonItemOffset + 8).probabilities[levelRange] = 4; + pickupItems.get(startingRareItemOffset).probabilities[levelRange] = 1; + pickupItems.get(startingRareItemOffset + 1).probabilities[levelRange] = 1; + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return pickupItems; + } + + @Override + public void setPickupItems(List pickupItems) { + try { + if (pickupItemsTableOffset > 0 && rarePickupItemsTableOffset > 0) { + byte[] battleOverlay = readOverlay(romEntry.getInt("BattleOvlNumber")); + Iterator itemIterator = pickupItems.iterator(); + for (int i = 0; i < Gen4Constants.numberOfCommonPickupItems; i++) { + int itemOffset = pickupItemsTableOffset + (2 * i); + int item = itemIterator.next().item; + FileFunctions.write2ByteInt(battleOverlay, itemOffset, item); + } + for (int i = 0; i < Gen4Constants.numberOfRarePickupItems; i++) { + int itemOffset = rarePickupItemsTableOffset + (2 * i); + int item = itemIterator.next().item; + FileFunctions.write2ByteInt(battleOverlay, itemOffset, item); + } + writeOverlay(romEntry.getInt("BattleOvlNumber"), battleOverlay); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public boolean canChangeTrainerText() { + return true; + } + + @Override + public List getTrainerNames() { + List tnames = new ArrayList<>(getStrings(romEntry.getInt("TrainerNamesTextOffset"))); + tnames.remove(0); // blank one + for (int i = 0; i < tnames.size(); i++) { + if (tnames.get(i).contains("\\and")) { + tnames.set(i, tnames.get(i).replace("\\and", "&")); + } + } + return tnames; + } + + @Override + public int maxTrainerNameLength() { + return 10;// based off the english ROMs fixed + } + + @Override + public void setTrainerNames(List trainerNames) { + List oldTNames = getStrings(romEntry.getInt("TrainerNamesTextOffset")); + List newTNames = new ArrayList<>(trainerNames); + for (int i = 0; i < newTNames.size(); i++) { + if (newTNames.get(i).contains("&")) { + newTNames.set(i, newTNames.get(i).replace("&", "\\and")); + } + } + newTNames.add(0, oldTNames.get(0)); // the 0-entry, preserve it + + // rewrite, only compressed if they were compressed before + setStrings(romEntry.getInt("TrainerNamesTextOffset"), newTNames, lastStringsCompressed); + + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.MAX_LENGTH; + } + + @Override + public List getTCNameLengthsByTrainer() { + // not needed + return new ArrayList<>(); + } + + @Override + public List getTrainerClassNames() { + return getStrings(romEntry.getInt("TrainerClassesTextOffset")); + } + + @Override + public void setTrainerClassNames(List trainerClassNames) { + setStrings(romEntry.getInt("TrainerClassesTextOffset"), trainerClassNames); + } + + @Override + public int maxTrainerClassNameLength() { + return 12;// based off the english ROMs + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return false; + } + + @Override + public List getDoublesTrainerClasses() { + int[] doublesClasses = romEntry.arrayEntries.get("DoublesTrainerClasses"); + List doubles = new ArrayList<>(); + for (int tClass : doublesClasses) { + doubles.add(tClass); + } + return doubles; + } + + @Override + public String getDefaultExtension() { + return "nds"; + } + + @Override + public int abilitiesPerPokemon() { + return 2; + } + + @Override + public int highestAbilityIndex() { + return Gen4Constants.highestAbilityIndex; + } + + @Override + public int internalStringLength(String string) { + return string.length(); + } + + @Override + public void randomizeIntroPokemon() { + try { + if (romEntry.romType == Gen4Constants.Type_DP || romEntry.romType == Gen4Constants.Type_Plat) { + Pokemon introPokemon = randomPokemon(); + while (introPokemon.genderRatio == 0xFE) { + // This is a female-only Pokemon. Gen 4 has an annoying quirk where female-only Pokemon *need* + // to pass a special parameter into the function that loads Pokemon sprites; the game will + // softlock on native hardware otherwise. The way the compiler has optimized the intro Pokemon + // code makes it very hard to modify, so passing in this special parameter is difficult. Rather + // than attempt to patch this code, just reroll until it isn't female-only. + introPokemon = randomPokemon(); + } + byte[] introOverlay = readOverlay(romEntry.getInt("IntroOvlNumber")); + for (String prefix : Gen4Constants.dpptIntroPrefixes) { + int offset = find(introOverlay, prefix); + if (offset > 0) { + offset += prefix.length() / 2; // because it was a prefix + writeWord(introOverlay, offset, introPokemon.number); + } + } + writeOverlay(romEntry.getInt("IntroOvlNumber"), introOverlay); + } else if (romEntry.romType == Gen4Constants.Type_HGSS) { + // Modify the sprite used for Ethan/Lyra's Marill + int marillReplacement = this.random.nextInt(548) + 297; + while (Gen4Constants.hgssBannedOverworldPokemon.contains(marillReplacement)) { + marillReplacement = this.random.nextInt(548) + 297; + } + + byte[] fieldOverlay = readOverlay(romEntry.getInt("FieldOvlNumber")); + String prefix = Gen4Constants.lyraEthanMarillSpritePrefix; + int offset = find(fieldOverlay, prefix); + if (offset > 0) { + offset += prefix.length() / 2; // because it was a prefix + writeWord(fieldOverlay, offset, marillReplacement); + if (Gen4Constants.hgssBigOverworldPokemon.contains(marillReplacement)) { + // Write the constant to indicate it's big (0x208 | (20 << 10)) + writeWord(fieldOverlay, offset + 2, 0x5208); + } else { + // Write the constant to indicate it's normal-sized (0x227 | (19 << 10)) + writeWord(fieldOverlay, offset + 2, 0x4E27); + } + } + writeOverlay(romEntry.getInt("FieldOvlNumber"), fieldOverlay); + + // Now modify the Marill's cry in every script it appears in to ensure consistency + int marillReplacementId = Gen4Constants.convertOverworldSpriteToSpecies(marillReplacement); + for (ScriptEntry entry : romEntry.marillCryScriptEntries) { + byte[] script = scriptNarc.files.get(entry.scriptFile); + writeWord(script, entry.scriptOffset, marillReplacementId); + scriptNarc.files.set(entry.scriptFile, script); + } + + // Modify the text too for additional consistency + int[] textOffsets = romEntry.arrayEntries.get("MarillTextFiles"); + String originalSpeciesString = pokes[Species.marill].name.toUpperCase(); + String newSpeciesString = pokes[marillReplacementId].name; + Map replacements = new TreeMap<>(); + replacements.put(originalSpeciesString, newSpeciesString); + for (int i = 0; i < textOffsets.length; i++) { + replaceAllStringsInEntry(textOffsets[i], replacements); + } + + // Lastly, modify the catching tutorial to use the new Pokemon if we're capable of doing so + if (romEntry.tweakFiles.containsKey("NewCatchingTutorialSubroutineTweak")) { + String catchingTutorialMonTablePrefix = romEntry.getString("CatchingTutorialMonTablePrefix"); + offset = find(arm9, catchingTutorialMonTablePrefix); + if (offset > 0) { + offset += catchingTutorialMonTablePrefix.length() / 2; // because it was a prefix + + // As part of our catching tutorial patch, the player Pokemon's ID is just pc-relative + // loaded, and offset is now pointing to it. + writeWord(arm9, offset, marillReplacementId); + } + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public ItemList getAllowedItems() { + return allowedItems; + } + + @Override + public ItemList getNonBadItems() { + return nonBadItems; + } + + @Override + public List getUniqueNoSellItems() { + return new ArrayList<>(); + } + + @Override + public List getRegularShopItems() { + return Gen4Constants.regularShopItems; + } + + @Override + public List getOPShopItems() { + return Gen4Constants.opShopItems; + } + + @Override + public String[] getItemNames() { + return itemNames.toArray(new String[0]); + } + + @Override + public String abilityName(int number) { + return abilityNames.get(number); + } + + @Override + public Map> getAbilityVariations() { + return Gen4Constants.abilityVariations; + } + + @Override + public List getUselessAbilities() { + return new ArrayList<>(Gen4Constants.uselessAbilities); + } + + @Override + public int getAbilityForTrainerPokemon(TrainerPokemon tp) { + // In Gen 4, alt formes for Trainer Pokemon use the base forme's ability + Pokemon pkmn = tp.pokemon; + while (pkmn.baseForme != null) { + pkmn = pkmn.baseForme; + } + + if (romEntry.romType == Gen4Constants.Type_DP || romEntry.romType == Gen4Constants.Type_Plat) { + // In DPPt, Trainer Pokemon *always* use the first Ability, no matter what + return pkmn.ability1; + } else { + // In HGSS, Trainer Pokemon can specify which ability they want to use. + return tp.abilitySlot == 2 ? pkmn.ability2 : pkmn.ability1; + } + } + + @Override + public boolean hasMegaEvolutions() { + return false; + } + + private List getFieldItems() { + List fieldItems = new ArrayList<>(); + // normal items + int scriptFile = romEntry.getInt("ItemBallsScriptOffset"); + byte[] itemScripts = scriptNarc.files.get(scriptFile); + int offset = 0; + int skipTableOffset = 0; + int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); + int setVar = romEntry.romType == Gen4Constants.Type_HGSS ? Gen4Constants.hgssSetVarScript + : Gen4Constants.dpptSetVarScript; + while (true) { + int part1 = readWord(itemScripts, offset); + if (part1 == Gen4Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(itemScripts, offset); + offset += 4; + if (skipTableOffset < skipTable.length && (skipTable[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(itemScripts, offsetInFile); + int variable = readWord(itemScripts, offsetInFile + 2); + if (command == setVar && variable == Gen4Constants.itemScriptVariable) { + int item = readWord(itemScripts, offsetInFile + 4); + fieldItems.add(item); + } + + } + + // hidden items + int hiTableOffset = romEntry.getInt("HiddenItemTableOffset"); + int hiTableLimit = romEntry.getInt("HiddenItemCount"); + for (int i = 0; i < hiTableLimit; i++) { + int item = readWord(arm9, hiTableOffset + i * 8); + fieldItems.add(item); + } + + return fieldItems; + } + + private void setFieldItems(List fieldItems) { + Iterator iterItems = fieldItems.iterator(); + + // normal items + int scriptFile = romEntry.getInt("ItemBallsScriptOffset"); + byte[] itemScripts = scriptNarc.files.get(scriptFile); + int offset = 0; + int skipTableOffset = 0; + int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); + int setVar = romEntry.romType == Gen4Constants.Type_HGSS ? Gen4Constants.hgssSetVarScript + : Gen4Constants.dpptSetVarScript; + while (true) { + int part1 = readWord(itemScripts, offset); + if (part1 == Gen4Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(itemScripts, offset); + offset += 4; + if (skipTableOffset < skipTable.length && (skipTable[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(itemScripts, offsetInFile); + int variable = readWord(itemScripts, offsetInFile + 2); + if (command == setVar && variable == Gen4Constants.itemScriptVariable) { + int item = iterItems.next(); + writeWord(itemScripts, offsetInFile + 4, item); + } + } + + // hidden items + int hiTableOffset = romEntry.getInt("HiddenItemTableOffset"); + int hiTableLimit = romEntry.getInt("HiddenItemCount"); + for (int i = 0; i < hiTableLimit; i++) { + int item = iterItems.next(); + writeWord(arm9, hiTableOffset + i * 8, item); + } + } + + @Override + public List getRequiredFieldTMs() { + if (romEntry.romType == Gen4Constants.Type_DP) { + return Gen4Constants.dpRequiredFieldTMs; + } else if (romEntry.romType == Gen4Constants.Type_Plat) { + // same as DP just we have to keep the weather TMs + return Gen4Constants.ptRequiredFieldTMs; + } + return new ArrayList<>(); + } + + @Override + public List getCurrentFieldTMs() { + List fieldItems = this.getFieldItems(); + List fieldTMs = new ArrayList<>(); + + for (int item : fieldItems) { + if (Gen4Constants.allowedItems.isTM(item)) { + fieldTMs.add(item - Gen4Constants.tmItemOffset + 1); + } + } + + return fieldTMs; + } + + @Override + public void setFieldTMs(List fieldTMs) { + List fieldItems = this.getFieldItems(); + int fiLength = fieldItems.size(); + Iterator iterTMs = fieldTMs.iterator(); + + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (Gen4Constants.allowedItems.isTM(oldItem)) { + int newItem = iterTMs.next() + Gen4Constants.tmItemOffset - 1; + fieldItems.set(i, newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List getRegularFieldItems() { + List fieldItems = this.getFieldItems(); + List fieldRegItems = new ArrayList<>(); + + for (int item : fieldItems) { + if (Gen4Constants.allowedItems.isAllowed(item) && !(Gen4Constants.allowedItems.isTM(item))) { + fieldRegItems.add(item); + } + } + + return fieldRegItems; + } + + @Override + public void setRegularFieldItems(List items) { + List fieldItems = this.getFieldItems(); + int fiLength = fieldItems.size(); + Iterator iterNewItems = items.iterator(); + + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (!(Gen4Constants.allowedItems.isTM(oldItem)) && Gen4Constants.allowedItems.isAllowed(oldItem)) { + int newItem = iterNewItems.next(); + fieldItems.set(i, newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List getIngameTrades() { + List trades = new ArrayList<>(); + try { + NARCArchive tradeNARC = this.readNARC(romEntry.getFile("InGameTrades")); + int[] spTrades = new int[0]; + if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { + spTrades = romEntry.arrayEntries.get("StaticPokemonTrades"); + } + List tradeStrings = getStrings(romEntry.getInt("IngameTradesTextOffset")); + int tradeCount = tradeNARC.files.size(); + for (int i = 0; i < tradeCount; i++) { + boolean isSP = false; + for (int spTrade : spTrades) { + if (spTrade == i) { + isSP = true; + break; + } + } + if (isSP) { + continue; + } + byte[] tfile = tradeNARC.files.get(i); + IngameTrade trade = new IngameTrade(); + trade.nickname = tradeStrings.get(i); + trade.givenPokemon = pokes[readLong(tfile, 0)]; + trade.ivs = new int[6]; + for (int iv = 0; iv < 6; iv++) { + trade.ivs[iv] = readLong(tfile, 4 + iv * 4); + } + trade.otId = readWord(tfile, 0x20); + trade.otName = tradeStrings.get(i + tradeCount); + trade.item = readLong(tfile, 0x3C); + trade.requestedPokemon = pokes[readLong(tfile, 0x4C)]; + trades.add(trade); + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + return trades; + } + + @Override + public void setIngameTrades(List trades) { + int tradeOffset = 0; + List oldTrades = this.getIngameTrades(); + try { + NARCArchive tradeNARC = this.readNARC(romEntry.getFile("InGameTrades")); + int[] spTrades = new int[0]; + if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { + spTrades = romEntry.arrayEntries.get("StaticPokemonTrades"); + } + List tradeStrings = getStrings(romEntry.getInt("IngameTradesTextOffset")); + int tradeCount = tradeNARC.files.size(); + for (int i = 0; i < tradeCount; i++) { + boolean isSP = false; + for (int spTrade : spTrades) { + if (spTrade == i) { + isSP = true; + break; + } + } + if (isSP) { + continue; + } + byte[] tfile = tradeNARC.files.get(i); + IngameTrade trade = trades.get(tradeOffset++); + tradeStrings.set(i, trade.nickname); + tradeStrings.set(i + tradeCount, trade.otName); + writeLong(tfile, 0, trade.givenPokemon.number); + for (int iv = 0; iv < 6; iv++) { + writeLong(tfile, 4 + iv * 4, trade.ivs[iv]); + } + writeWord(tfile, 0x20, trade.otId); + writeLong(tfile, 0x3C, trade.item); + writeLong(tfile, 0x4C, trade.requestedPokemon.number); + if (tfile.length > 0x50) { + writeLong(tfile, 0x50, 0); // disable gender + } + } + this.writeNARC(romEntry.getFile("InGameTrades"), tradeNARC); + this.setStrings(romEntry.getInt("IngameTradesTextOffset"), tradeStrings); + // update what the people say when they talk to you + if (romEntry.arrayEntries.containsKey("IngameTradePersonTextOffsets")) { + int[] textOffsets = romEntry.arrayEntries.get("IngameTradePersonTextOffsets"); + for (int trade = 0; trade < textOffsets.length; trade++) { + if (textOffsets[trade] > 0) { + if (trade >= oldTrades.size() || trade >= trades.size()) { + break; + } + IngameTrade oldTrade = oldTrades.get(trade); + IngameTrade newTrade = trades.get(trade); + Map replacements = new TreeMap<>(); + replacements.put(oldTrade.givenPokemon.name.toUpperCase(), newTrade.givenPokemon.name); + if (oldTrade.requestedPokemon != newTrade.requestedPokemon) { + replacements.put(oldTrade.requestedPokemon.name.toUpperCase(), newTrade.requestedPokemon.name); + } + replaceAllStringsInEntry(textOffsets[trade], replacements); + // hgss override for one set of strings that appears 2x + if (romEntry.romType == Gen4Constants.Type_HGSS && trade == 6) { + replaceAllStringsInEntry(textOffsets[trade] + 1, replacements); + } + } + } + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + } + + private void replaceAllStringsInEntry(int entry, Map replacements) { + // This function currently only replaces move and Pokemon names, and we don't want them + // split across multiple lines if there is a space. + replacements.replaceAll((key, oldValue) -> oldValue.replace(' ', '_')); + int lineLength = Gen4Constants.getTextCharsPerLine(romEntry.romType); + List strings = this.getStrings(entry); + for (int strNum = 0; strNum < strings.size(); strNum++) { + String oldString = strings.get(strNum); + boolean needsReplacement = false; + for (Map.Entry replacement : replacements.entrySet()) { + if (oldString.contains(replacement.getKey())) { + needsReplacement = true; + break; + } + } + if (needsReplacement) { + String newString = RomFunctions.formatTextWithReplacements(oldString, replacements, "\\n", "\\l", "\\p", + lineLength, ssd); + newString = newString.replace('_', ' '); + strings.set(strNum, newString); + } + } + this.setStrings(entry, strings); + } + + @Override + public boolean hasDVs() { + return false; + } + + @Override + public int generationOfPokemon() { + return 4; + } + + @Override + public void removeEvosForPokemonPool() { + // slightly more complicated than gen2/3 + // we have to update a "baby table" too + List pokemonIncluded = this.mainPokemonList; + Set keepEvos = new HashSet<>(); + for (Pokemon pk : pokes) { + if (pk != null) { + keepEvos.clear(); + for (Evolution evol : pk.evolutionsFrom) { + if (pokemonIncluded.contains(evol.from) && pokemonIncluded.contains(evol.to)) { + keepEvos.add(evol); + } else { + evol.to.evolutionsTo.remove(evol); + } + } + pk.evolutionsFrom.retainAll(keepEvos); + } + } + + try { + byte[] babyPokes = readFile(romEntry.getFile("BabyPokemon")); + // baby pokemon + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + Pokemon baby = pokes[i]; + while (baby.evolutionsTo.size() > 0) { + // Grab the first "to evolution" even if there are multiple + baby = baby.evolutionsTo.get(0).from; + } + writeWord(babyPokes, i * 2, baby.number); + } + // finish up + writeFile(romEntry.getFile("BabyPokemon"), babyPokes); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public boolean supportsFourStartingMoves() { + return true; + } + + @Override + public List getFieldMoves() { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + return Gen4Constants.hgssFieldMoves; + } else { + return Gen4Constants.dpptFieldMoves; + } + } + + @Override + public List getEarlyRequiredHMMoves() { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + return Gen4Constants.hgssEarlyRequiredHMMoves; + } else { + return Gen4Constants.dpptEarlyRequiredHMMoves; + } + } + + @Override + public int miscTweaksAvailable() { + int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); + available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); + available |= MiscTweak.UPDATE_TYPE_EFFECTIVENESS.getValue(); + if (romEntry.tweakFiles.get("FastestTextTweak") != null) { + available |= MiscTweak.FASTEST_TEXT.getValue(); + } + available |= MiscTweak.BAN_LUCKY_EGG.getValue(); + if (romEntry.tweakFiles.get("NationalDexAtStartTweak") != null) { + available |= MiscTweak.NATIONAL_DEX_AT_START.getValue(); + } + available |= MiscTweak.RUN_WITHOUT_RUNNING_SHOES.getValue(); + available |= MiscTweak.FASTER_HP_AND_EXP_BARS.getValue(); + if (romEntry.tweakFiles.get("FastDistortionWorldTweak") != null) { + available |= MiscTweak.FAST_DISTORTION_WORLD.getValue(); + } + if (romEntry.romType == Gen4Constants.Type_Plat || romEntry.romType == Gen4Constants.Type_HGSS) { + available |= MiscTweak.UPDATE_ROTOM_FORME_TYPING.getValue(); + } + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { + applyCamelCaseNames(); + } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { + randomizeCatchingTutorial(); + } else if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestText(); + } else if (tweak == MiscTweak.BAN_LUCKY_EGG) { + allowedItems.banSingles(Items.luckyEgg); + nonBadItems.banSingles(Items.luckyEgg); + } else if (tweak == MiscTweak.NATIONAL_DEX_AT_START) { + patchForNationalDex(); + } else if (tweak == MiscTweak.RUN_WITHOUT_RUNNING_SHOES) { + applyRunWithoutRunningShoesPatch(); + } else if (tweak == MiscTweak.FASTER_HP_AND_EXP_BARS) { + patchFasterBars(); + } else if (tweak == MiscTweak.UPDATE_TYPE_EFFECTIVENESS) { + updateTypeEffectiveness(); + } else if (tweak == MiscTweak.FAST_DISTORTION_WORLD) { + applyFastDistortionWorld(); + } else if (tweak == MiscTweak.UPDATE_ROTOM_FORME_TYPING) { + updateRotomFormeTyping(); + } + } + + @Override + public boolean isEffectivenessUpdated() { + return effectivenessUpdated; + } + + private void randomizeCatchingTutorial() { + int opponentOffset = romEntry.getInt("CatchingTutorialOpponentMonOffset"); + + if (romEntry.tweakFiles.containsKey("NewCatchingTutorialSubroutineTweak")) { + String catchingTutorialMonTablePrefix = romEntry.getString("CatchingTutorialMonTablePrefix"); + int offset = find(arm9, catchingTutorialMonTablePrefix); + if (offset > 0) { + offset += catchingTutorialMonTablePrefix.length() / 2; // because it was a prefix + + // The player's mon is randomized as part of randomizing Lyra/Ethan's Pokemon (see + // randomizeIntroPokemon), so we just care about the enemy mon. As part of our catching + // tutorial patch, the player and enemy species IDs are pc-relative loaded, with the + // enemy ID occurring right after the player ID (which is what offset is pointing to). + Pokemon opponent = randomPokemonLimited(Integer.MAX_VALUE, false); + writeWord(arm9, offset + 4, opponent.number); + } + } else if (romEntry.romType == Gen4Constants.Type_HGSS) { + // For non-US HGSS, just handle it in the old-school way. Can randomize both Pokemon, but both limited to 1-255 + // Make sure to raise the level of Lyra/Ethan's Pokemon to 10 to prevent softlocks + int playerOffset = romEntry.getInt("CatchingTutorialPlayerMonOffset"); + int levelOffset = romEntry.getInt("CatchingTutorialPlayerLevelOffset"); + Pokemon opponent = randomPokemonLimited(255, false); + Pokemon player = randomPokemonLimited(255, false); + if (opponent != null && player != null) { + arm9[opponentOffset] = (byte) opponent.number; + arm9[playerOffset] = (byte) player.number; + arm9[levelOffset] = 10; + } + } else { + // DPPt only supports randomizing the opponent, but enough space for any mon + Pokemon opponent = randomPokemonLimited(Integer.MAX_VALUE, false); + + if (opponent != null) { + writeLong(arm9, opponentOffset, opponent.number); + } + } + + } + + private void applyFastestText() { + genericIPSPatch(arm9, "FastestTextTweak"); + } + + private void patchForNationalDex() { + byte[] pokedexScript = scriptNarc.files.get(romEntry.getInt("NationalDexScriptOffset")); + + if (romEntry.romType == Gen4Constants.Type_HGSS) { + // Our patcher breaks if the output file is larger than the input file. For HGSS, we want + // to expand the script by four bytes to add an instruction to enable the national dex. Thus, + // the IPS patch was created with us adding four 0x00 bytes to the end of the script in mind. + byte[] expandedPokedexScript = new byte[pokedexScript.length + 4]; + System.arraycopy(pokedexScript, 0, expandedPokedexScript, 0, pokedexScript.length); + pokedexScript = expandedPokedexScript; + } + genericIPSPatch(pokedexScript, "NationalDexAtStartTweak"); + scriptNarc.files.set(romEntry.getInt("NationalDexScriptOffset"), pokedexScript); + } + + private void applyRunWithoutRunningShoesPatch() { + String prefix = Gen4Constants.getRunWithoutRunningShoesPrefix(romEntry.romType); + int offset = find(arm9, prefix); + if (offset != 0) { + // The prefix starts 0xE bytes from what we want to patch because what comes + // between is region and revision dependent. To start running, the game checks: + // 1. That you're holding the B button + // 2. That the FLAG_SYS_B_DASH flag is set (aka, you've acquired Running Shoes) + // For #2, if the flag is unset, it jumps to a different part of the + // code to make you walk instead. This simply nops out this jump so the + // game stops caring about the FLAG_SYS_B_DASH flag entirely. + writeWord(arm9,offset + 0xE, 0); + } + } + + private void patchFasterBars() { + // To understand what this code is patching, take a look at the CalcNewBarValue + // and MoveBattleBar functions in this file from the Emerald decompilation: + // https://github.com/pret/pokeemerald/blob/master/src/battle_interface.c + // The code in Gen 4 is almost identical outside of one single constant; the + // reason the bars scroll slower is because Gen 4 runs at 30 FPS instead of 60. + try { + byte[] battleOverlay = readOverlay(romEntry.getInt("BattleOvlNumber")); + int offset = find(battleOverlay, Gen4Constants.hpBarSpeedPrefix); + if (offset > 0) { + offset += Gen4Constants.hpBarSpeedPrefix.length() / 2; // because it was a prefix + // For the HP bar, the original game passes 1 for the toAdd parameter of CalcNewBarValue. + // We want to pass 2 instead, so we simply change the mov instruction at offset. + battleOverlay[offset] = 0x02; + } + + offset = find(battleOverlay, Gen4Constants.expBarSpeedPrefix); + if (offset > 0) { + offset += Gen4Constants.expBarSpeedPrefix.length() / 2; // because it was a prefix + // For the EXP bar, the original game passes expFraction for the toAdd parameter. The + // game calculates expFraction by doing a division, and to do *that*, it has to load + // receivedValue into r0 so it can call the division function with it as the first + // parameter. It gets the value from r6 like so: + // add r0, r6, #0 + // Since we ultimately want toAdd (and thus expFraction) to be doubled, we can double + // receivedValue when it gets loaded into r0 by tweaking the add to be: + // add r0, r6, r6 + battleOverlay[offset] = (byte) 0xB0; + battleOverlay[offset + 1] = 0x19; + } + + offset = find(battleOverlay, Gen4Constants.bothBarsSpeedPrefix); + if (offset > 0) { + offset += Gen4Constants.bothBarsSpeedPrefix.length() / 2; // because it was a prefix + // For both HP and EXP bars, a different set of logic is used when the maxValue has + // fewer pixels than the whole bar; this logic ignores the toAdd parameter entirely and + // calculates its *own* toAdd by doing maxValue << 8 / scale. If we instead do + // maxValue << 9, the new toAdd becomes doubled as well. + battleOverlay[offset] = 0x40; + } + + writeOverlay(romEntry.getInt("BattleOvlNumber"), battleOverlay); + + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void updateTypeEffectiveness() { + try { + byte[] battleOverlay = readOverlay(romEntry.getInt("BattleOvlNumber")); + int typeEffectivenessTableOffset = find(battleOverlay, Gen4Constants.typeEffectivenessTableLocator); + if (typeEffectivenessTableOffset > 0) { + List typeEffectivenessTable = readTypeEffectivenessTable(battleOverlay, typeEffectivenessTableOffset); + log("--Updating Type Effectiveness--"); + for (TypeRelationship relationship : typeEffectivenessTable) { + // Change Ghost 0.5x against Steel to Ghost 1x to Steel + if (relationship.attacker == Type.GHOST && relationship.defender == Type.STEEL) { + relationship.effectiveness = Effectiveness.NEUTRAL; + log("Replaced: Ghost not very effective vs Steel => Ghost neutral vs Steel"); + } + + // Change Dark 0.5x against Steel to Dark 1x to Steel + else if (relationship.attacker == Type.DARK && relationship.defender == Type.STEEL) { + relationship.effectiveness = Effectiveness.NEUTRAL; + log("Replaced: Dark not very effective vs Steel => Dark neutral vs Steel"); + } + } + logBlankLine(); + writeTypeEffectivenessTable(typeEffectivenessTable, battleOverlay, typeEffectivenessTableOffset); + writeOverlay(romEntry.getInt("BattleOvlNumber"), battleOverlay); + effectivenessUpdated = true; + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private List readTypeEffectivenessTable(byte[] battleOverlay, int typeEffectivenessTableOffset) { + List typeEffectivenessTable = new ArrayList<>(); + int currentOffset = typeEffectivenessTableOffset; + int attackingType = battleOverlay[currentOffset]; + // 0xFE marks the end of the table *not* affected by Foresight, while 0xFF marks + // the actual end of the table. Since we don't care about Ghost immunities at all, + // just stop once we reach the Foresight section. + while (attackingType != (byte) 0xFE) { + int defendingType = battleOverlay[currentOffset + 1]; + int effectivenessInternal = battleOverlay[currentOffset + 2]; + Type attacking = Gen4Constants.typeTable[attackingType]; + Type defending = Gen4Constants.typeTable[defendingType]; + Effectiveness effectiveness = null; + switch (effectivenessInternal) { + case 20: + effectiveness = Effectiveness.DOUBLE; + break; + case 10: + effectiveness = Effectiveness.NEUTRAL; + break; + case 5: + effectiveness = Effectiveness.HALF; + break; + case 0: + effectiveness = Effectiveness.ZERO; + break; + } + if (effectiveness != null) { + TypeRelationship relationship = new TypeRelationship(attacking, defending, effectiveness); + typeEffectivenessTable.add(relationship); + } + currentOffset += 3; + attackingType = battleOverlay[currentOffset]; + } + return typeEffectivenessTable; + } + + private void writeTypeEffectivenessTable(List typeEffectivenessTable, byte[] battleOverlay, + int typeEffectivenessTableOffset) { + int currentOffset = typeEffectivenessTableOffset; + for (TypeRelationship relationship : typeEffectivenessTable) { + battleOverlay[currentOffset] = Gen4Constants.typeToByte(relationship.attacker); + battleOverlay[currentOffset + 1] = Gen4Constants.typeToByte(relationship.defender); + byte effectivenessInternal = 0; + switch (relationship.effectiveness) { + case DOUBLE: + effectivenessInternal = 20; + break; + case NEUTRAL: + effectivenessInternal = 10; + break; + case HALF: + effectivenessInternal = 5; + break; + case ZERO: + effectivenessInternal = 0; + break; + } + battleOverlay[currentOffset + 2] = effectivenessInternal; + currentOffset += 3; + } + } + + private void applyFastDistortionWorld() { + byte[] spearPillarPortalScript = scriptNarc.files.get(Gen4Constants.ptSpearPillarPortalScriptFile); + byte[] expandedSpearPillarPortalScript = new byte[spearPillarPortalScript.length + 12]; + System.arraycopy(spearPillarPortalScript, 0, expandedSpearPillarPortalScript, 0, spearPillarPortalScript.length); + spearPillarPortalScript = expandedSpearPillarPortalScript; + genericIPSPatch(spearPillarPortalScript, "FastDistortionWorldTweak"); + scriptNarc.files.set(Gen4Constants.ptSpearPillarPortalScriptFile, spearPillarPortalScript); + } + + private void updateRotomFormeTyping() { + pokes[Species.Gen4Formes.rotomH].secondaryType = Type.FIRE; + pokes[Species.Gen4Formes.rotomW].secondaryType = Type.WATER; + pokes[Species.Gen4Formes.rotomFr].secondaryType = Type.ICE; + pokes[Species.Gen4Formes.rotomFa].secondaryType = Type.FLYING; + pokes[Species.Gen4Formes.rotomM].secondaryType = Type.GRASS; + } + + @Override + public void enableGuaranteedPokemonCatching() { + try { + byte[] battleOverlay = readOverlay(romEntry.getInt("BattleOvlNumber")); + int offset = find(battleOverlay, Gen4Constants.perfectOddsBranchLocator); + if (offset > 0) { + // In Cmd_handleballthrow (name taken from pokeemerald decomp), the middle of the function checks + // if the odds of catching a Pokemon is greater than 254; if it is, then the Pokemon is automatically + // caught. In ASM, this is represented by: + // cmp r1, #0xFF + // bcc oddsLessThanOrEqualTo254 + // The below code just nops these two instructions so that we *always* act like our odds are 255, + // and Pokemon are automatically caught no matter what. + battleOverlay[offset] = 0x00; + battleOverlay[offset + 1] = 0x00; + battleOverlay[offset + 2] = 0x00; + battleOverlay[offset + 3] = 0x00; + writeOverlay(romEntry.getInt("BattleOvlNumber"), battleOverlay); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public void applyCorrectStaticMusic(Map specialMusicStaticChanges) { + List replaced = new ArrayList<>(); + String newIndexToMusicPrefix; + int newIndexToMusicPoolOffset; + + switch(romEntry.romType) { + case Gen4Constants.Type_DP: + case Gen4Constants.Type_Plat: + int extendBy = romEntry.getInt("Arm9ExtensionSize"); + arm9 = extendARM9(arm9, extendBy, romEntry.getString("TCMCopyingPrefix"), Gen4Constants.arm9Offset); + genericIPSPatch(arm9, "NewIndexToMusicTweak"); + + newIndexToMusicPrefix = romEntry.getString("NewIndexToMusicPrefix"); + newIndexToMusicPoolOffset = find(arm9, newIndexToMusicPrefix); + newIndexToMusicPoolOffset += newIndexToMusicPrefix.length() / 2; + + for (int oldStatic: specialMusicStaticChanges.keySet()) { + int i = newIndexToMusicPoolOffset; + int index = readWord(arm9, i); + while (index != oldStatic || replaced.contains(i)) { + i += 4; + index = readWord(arm9, i); + } + writeWord(arm9, i, specialMusicStaticChanges.get(oldStatic)); + replaced.add(i); + } + break; + case Gen4Constants.Type_HGSS: + newIndexToMusicPrefix = romEntry.getString("IndexToMusicPrefix"); + newIndexToMusicPoolOffset = find(arm9, newIndexToMusicPrefix); + + if (newIndexToMusicPoolOffset > 0) { + newIndexToMusicPoolOffset += newIndexToMusicPrefix.length() / 2; + + for (int oldStatic: specialMusicStaticChanges.keySet()) { + int i = newIndexToMusicPoolOffset; + int indexEtc = readWord(arm9, i); + int index = indexEtc & 0x3FF; + while (index != oldStatic || replaced.contains(i)) { + i += 2; + indexEtc = readWord(arm9, i); + index = indexEtc & 0x3FF; + } + int newIndexEtc = specialMusicStaticChanges.get(oldStatic) | (indexEtc & 0xFC00); + writeWord(arm9, i, newIndexEtc); + replaced.add(i); + } + } + break; + } + } + + @Override + public boolean hasStaticMusicFix() { + return romEntry.tweakFiles.get("NewIndexToMusicTweak") != null || romEntry.romType == Gen4Constants.Type_HGSS; + } + + private boolean genericIPSPatch(byte[] data, String ctName) { + String patchName = romEntry.tweakFiles.get(ctName); + if (patchName == null) { + return false; + } + + try { + FileFunctions.applyPatch(data, patchName); + return true; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private Pokemon randomPokemonLimited(int maxValue, boolean blockNonMales) { + checkPokemonRestrictions(); + List validPokemon = new ArrayList<>(); + for (Pokemon pk : this.mainPokemonList) { + if (pk.number <= maxValue && (!blockNonMales || pk.genderRatio <= 0xFD)) { + validPokemon.add(pk); + } + } + if (validPokemon.size() == 0) { + return null; + } else { + return validPokemon.get(random.nextInt(validPokemon.size())); + } + } + + private void computeCRC32sForRom() throws IOException { + this.actualOverlayCRC32s = new HashMap<>(); + this.actualFileCRC32s = new HashMap<>(); + this.actualArm9CRC32 = FileFunctions.getCRC32(arm9); + for (int overlayNumber : romEntry.overlayExpectedCRC32s.keySet()) { + byte[] overlay = readOverlay(overlayNumber); + long crc32 = FileFunctions.getCRC32(overlay); + this.actualOverlayCRC32s.put(overlayNumber, crc32); + } + for (String fileKey : romEntry.files.keySet()) { + byte[] file = readFile(romEntry.getFile(fileKey)); + long crc32 = FileFunctions.getCRC32(file); + this.actualFileCRC32s.put(fileKey, crc32); + } + } + + @Override + public boolean isRomValid() { + if (romEntry.arm9ExpectedCRC32 != actualArm9CRC32) { + System.out.println(actualArm9CRC32); + return false; + } + + for (int overlayNumber : romEntry.overlayExpectedCRC32s.keySet()) { + long expectedCRC32 = romEntry.overlayExpectedCRC32s.get(overlayNumber); + long actualCRC32 = actualOverlayCRC32s.get(overlayNumber); + if (expectedCRC32 != actualCRC32) { + return false; + } + } + + for (String fileKey : romEntry.files.keySet()) { + long expectedCRC32 = romEntry.files.get(fileKey).expectedCRC32; + long actualCRC32 = actualFileCRC32s.get(fileKey); + if (expectedCRC32 != actualCRC32) { + return false; + } + } + + return true; + } + + @Override + public BufferedImage getMascotImage() { + try { + Pokemon pk = randomPokemon(); + NARCArchive pokespritesNARC = this.readNARC(romEntry.getFile("PokemonGraphics")); + int spriteIndex = pk.number * 6 + 2 + random.nextInt(2); + int palIndex = pk.number * 6 + 4; + if (random.nextInt(10) == 0) { + // shiny + palIndex++; + } + + // read sprite + byte[] rawSprite = pokespritesNARC.files.get(spriteIndex); + if (rawSprite.length == 0) { + // Must use other gender form + rawSprite = pokespritesNARC.files.get(spriteIndex ^ 1); + } + int[] spriteData = new int[3200]; + for (int i = 0; i < 3200; i++) { + spriteData[i] = readWord(rawSprite, i * 2 + 48); + } + + // Decrypt sprite (why does EVERYTHING use the RNG formula geez) + if (romEntry.romType != Gen4Constants.Type_DP) { + int key = spriteData[0]; + for (int i = 0; i < 3200; i++) { + spriteData[i] ^= (key & 0xFFFF); + key = key * 0x41C64E6D + 0x6073; + } + } else { + // D/P sprites are encrypted *backwards*. Wut. + int key = spriteData[3199]; + for (int i = 3199; i >= 0; i--) { + spriteData[i] ^= (key & 0xFFFF); + key = key * 0x41C64E6D + 0x6073; + } + } + + byte[] rawPalette = pokespritesNARC.files.get(palIndex); + + int[] palette = new int[16]; + for (int i = 1; i < 16; i++) { + palette[i] = GFXFunctions.conv16BitColorToARGB(readWord(rawPalette, 40 + i * 2)); + } + + // Deliberately chop off the right half of the image while still + // correctly indexing the array. + BufferedImage bim = new BufferedImage(80, 80, BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < 80; y++) { + for (int x = 0; x < 80; x++) { + int value = ((spriteData[y * 40 + x / 4]) >> (x % 4) * 4) & 0x0F; + bim.setRGB(x, y, palette[value]); + } + } + return bim; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getAllConsumableHeldItems() { + return Gen4Constants.consumableHeldItems; + } + + @Override + public List getAllHeldItems() { + return Gen4Constants.allHeldItems; + } + + @Override + public List getSensibleHeldItemsFor(TrainerPokemon tp, boolean consumableOnly, List moves, int[] pokeMoves) { + List items = new ArrayList<>(); + items.addAll(Gen4Constants.generalPurposeConsumableItems); + int frequencyBoostCount = 6; // Make some very good items more common, but not too common + if (!consumableOnly) { + frequencyBoostCount = 8; // bigger to account for larger item pool. + items.addAll(Gen4Constants.generalPurposeItems); + } + for (int moveIdx : pokeMoves) { + Move move = moves.get(moveIdx); + if (move == null) { + continue; + } + if (move.category == MoveCategory.PHYSICAL) { + items.add(Items.liechiBerry); + if (!consumableOnly) { + items.addAll(Gen4Constants.typeBoostingItems.get(move.type)); + items.add(Items.choiceBand); + items.add(Items.muscleBand); + } + } + if (move.category == MoveCategory.SPECIAL) { + items.add(Items.petayaBerry); + if (!consumableOnly) { + items.addAll(Gen4Constants.typeBoostingItems.get(move.type)); + items.add(Items.wiseGlasses); + items.add(Items.choiceSpecs); + } + } + if (!consumableOnly && Gen4Constants.moveBoostingItems.containsKey(moveIdx)) { + items.addAll(Gen4Constants.moveBoostingItems.get(moveIdx)); + } + } + Map byType = Effectiveness.against(tp.pokemon.primaryType, tp.pokemon.secondaryType, 4, effectivenessUpdated); + for(Map.Entry entry : byType.entrySet()) { + Integer berry = Gen4Constants.weaknessReducingBerries.get(entry.getKey()); + if (entry.getValue() == Effectiveness.DOUBLE) { + items.add(berry); + } else if (entry.getValue() == Effectiveness.QUADRUPLE) { + for (int i = 0; i < frequencyBoostCount; i++) { + items.add(berry); + } + } + } + if (byType.get(Type.NORMAL) == Effectiveness.NEUTRAL) { + items.add(Items.chilanBerry); + } + + int ability = this.getAbilityForTrainerPokemon(tp); + if (ability == Abilities.levitate) { + items.removeAll(Arrays.asList(Items.shucaBerry)); + } + + if (!consumableOnly) { + if (Gen4Constants.abilityBoostingItems.containsKey(ability)) { + items.addAll(Gen4Constants.abilityBoostingItems.get(ability)); + } + if (tp.pokemon.primaryType == Type.POISON || tp.pokemon.secondaryType == Type.POISON) { + items.add(Items.blackSludge); + } + List speciesItems = Gen4Constants.speciesBoostingItems.get(tp.pokemon.number); + if (speciesItems != null) { + for (int i = 0; i < frequencyBoostCount; i++) { + items.addAll(speciesItems); + } + } + } + return items; + } +} diff --git a/src/com/pkrandom/romhandlers/Gen5RomHandler.java b/src/com/pkrandom/romhandlers/Gen5RomHandler.java new file mode 100755 index 0000000..02010e7 --- /dev/null +++ b/src/com/pkrandom/romhandlers/Gen5RomHandler.java @@ -0,0 +1,4343 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- Gen5RomHandler.java - randomizer handler for B/W/B2/W2. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import com.pkrandom.*; +import com.pkrandom.constants.*; +import com.pkrandom.exceptions.RandomizationException; +import com.pkrandom.pokemon.*; +import pptxt.PPTxtHandler; + +import com.pkrandom.exceptions.RandomizerIOException; +import com.pkrandom.newnds.NARCArchive; +import compressors.DSDecmp; + +public class Gen5RomHandler extends AbstractDSRomHandler { + + public static class Factory extends RomHandler.Factory { + + @Override + public Gen5RomHandler create(Random random, PrintStream logStream) { + return new Gen5RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + return detectNDSRomInner(getROMCodeFromFile(filename), getVersionFromFile(filename)); + } + } + + public Gen5RomHandler(Random random) { + super(random, null); + } + + public Gen5RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + private static class OffsetWithinEntry { + private int entry; + private int offset; + } + + private static class RomFileEntry { + public String path; + public long expectedCRC32; + } + + private static class RomEntry { + private String name; + private String romCode; + private byte version; + private int romType; + private long arm9ExpectedCRC32; + private boolean staticPokemonSupport = false, copyStaticPokemon = false, copyRoamingPokemon = false, + copyTradeScripts = false, isBlack = false; + private Map strings = new HashMap<>(); + private Map numbers = new HashMap<>(); + private Map tweakFiles = new HashMap<>(); + private Map arrayEntries = new HashMap<>(); + private Map offsetArrayEntries = new HashMap<>(); + private Map files = new HashMap<>(); + private Map overlayExpectedCRC32s = new HashMap<>(); + private List staticPokemon = new ArrayList<>(); + private List staticPokemonFakeBall = new ArrayList<>(); + private List roamingPokemon = new ArrayList<>(); + private List tradeScripts = new ArrayList<>(); + + + private int getInt(String key) { + if (!numbers.containsKey(key)) { + numbers.put(key, 0); + } + return numbers.get(key); + } + + private String getString(String key) { + if (!strings.containsKey(key)) { + strings.put(key, ""); + } + return strings.get(key); + } + + private String getFile(String key) { + if (!files.containsKey(key)) { + files.put(key, new RomFileEntry()); + } + return files.get(key).path; + } + } + + private static List roms; + + static { + loadROMInfo(); + } + + private static void loadROMInfo() { + roms = new ArrayList<>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen5_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + if (r[0].equals("Game")) { + current.romCode = r[1]; + } else if (r[0].equals("Version")) { + current.version = Byte.parseByte(r[1]); + } else if (r[0].equals("Type")) { + if (r[1].equalsIgnoreCase("BW2")) { + current.romType = Gen5Constants.Type_BW2; + } else { + current.romType = Gen5Constants.Type_BW; + } + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.romCode)) { + // copy from here + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.numbers.putAll(otherEntry.numbers); + current.strings.putAll(otherEntry.strings); + current.offsetArrayEntries.putAll(otherEntry.offsetArrayEntries); + current.files.putAll(otherEntry.files); + if (current.copyStaticPokemon) { + current.staticPokemon.addAll(otherEntry.staticPokemon); + current.staticPokemonFakeBall.addAll(otherEntry.staticPokemonFakeBall); + current.staticPokemonSupport = true; + } else { + current.staticPokemonSupport = false; + } + if (current.copyTradeScripts) { + current.tradeScripts.addAll(otherEntry.tradeScripts); + } + if (current.copyRoamingPokemon) { + current.roamingPokemon.addAll(otherEntry.roamingPokemon); + } + } + } + } else if (r[0].startsWith("File<")) { + String key = r[0].split("<")[1].split(">")[0]; + String[] values = r[1].substring(1, r[1].length() - 1).split(","); + RomFileEntry entry = new RomFileEntry(); + entry.path = values[0].trim(); + entry.expectedCRC32 = parseRILong("0x" + values[1].trim()); + current.files.put(key, entry); + } else if (r[0].equals("Arm9CRC32")) { + current.arm9ExpectedCRC32 = parseRILong("0x" + r[1]); + } else if (r[0].startsWith("OverlayCRC32<")) { + String keyString = r[0].split("<")[1].split(">")[0]; + int key = parseRIInt(keyString); + long value = parseRILong("0x" + r[1]); + current.overlayExpectedCRC32s.put(key, value); + } else if (r[0].equals("StaticPokemon{}")) { + current.staticPokemon.add(parseStaticPokemon(r[1])); + } else if (r[0].equals("StaticPokemonFakeBall{}")) { + current.staticPokemonFakeBall.add(parseStaticPokemon(r[1])); + } else if (r[0].equals("RoamingPokemon{}")) { + current.roamingPokemon.add(parseRoamingPokemon(r[1])); + } else if (r[0].equals("TradeScript[]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + int[] reqOffs = new int[offsets.length]; + int[] givOffs = new int[offsets.length]; + int file = 0; + int c = 0; + for (String off : offsets) { + String[] parts = off.split(":"); + file = parseRIInt(parts[0]); + reqOffs[c] = parseRIInt(parts[1]); + givOffs[c++] = parseRIInt(parts[2]); + } + TradeScript ts = new TradeScript(); + ts.fileNum = file; + ts.requestedOffsets = reqOffs; + ts.givenOffsets = givOffs; + current.tradeScripts.add(ts); + } else if (r[0].equals("StaticPokemonSupport")) { + int spsupport = parseRIInt(r[1]); + current.staticPokemonSupport = (spsupport > 0); + } else if (r[0].equals("CopyStaticPokemon")) { + int csp = parseRIInt(r[1]); + current.copyStaticPokemon = (csp > 0); + } else if (r[0].equals("CopyRoamingPokemon")) { + int crp = parseRIInt(r[1]); + current.copyRoamingPokemon = (crp > 0); + } else if (r[0].equals("CopyTradeScripts")) { + int cts = parseRIInt(r[1]); + current.copyTradeScripts = (cts > 0); + } else if (r[0].startsWith("StarterOffsets")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + OffsetWithinEntry[] offs = new OffsetWithinEntry[offsets.length]; + int c = 0; + for (String off : offsets) { + String[] parts = off.split(":"); + OffsetWithinEntry owe = new OffsetWithinEntry(); + owe.entry = parseRIInt(parts[0]); + owe.offset = parseRIInt(parts[1]); + offs[c++] = owe; + } + current.offsetArrayEntries.put(r[0], offs); + } else if (r[0].endsWith("Tweak")) { + current.tweakFiles.put(r[0], r[1]); + } else if (r[0].equals("IsBlack")) { + int isBlack = parseRIInt(r[1]); + current.isBlack = (isBlack > 0); + } else { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.arrayEntries.put(r[0], offs); + } + } else if (r[0].endsWith("Offset") || r[0].endsWith("Count") || r[0].endsWith("Number") + || r[0].endsWith("Size") || r[0].endsWith("Index")) { + int offs = parseRIInt(r[1]); + current.numbers.put(r[0], offs); + } else { + current.strings.put(r[0], r[1]); + } + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + System.err.println("File not found!"); + } + } + + private static int parseRIInt(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Integer.parseInt(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + private static long parseRILong(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Long.parseLong(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + private static StaticPokemon parseStaticPokemon(String staticPokemonString) { + StaticPokemon sp = new StaticPokemon(); + String pattern = "[A-z]+=\\[([0-9]+:0x[0-9a-fA-F]+,?\\s?)+]"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(staticPokemonString); + while (m.find()) { + String[] segments = m.group().split("="); + String[] offsets = segments[1].substring(1, segments[1].length() - 1).split(","); + FileEntry[] entries = new FileEntry[offsets.length]; + for (int i = 0; i < entries.length; i++) { + String[] parts = offsets[i].split(":"); + entries[i] = new FileEntry(parseRIInt(parts[0]), parseRIInt(parts[1])); + } + switch (segments[0]) { + case "Species": + sp.speciesEntries = entries; + break; + case "Level": + sp.levelEntries = entries; + break; + case "Forme": + sp.formeEntries = entries; + break; + } + } + return sp; + } + + private static RoamingPokemon parseRoamingPokemon(String roamingPokemonString) { + RoamingPokemon rp = new RoamingPokemon(); + String pattern = "[A-z]+=\\[(0x[0-9a-fA-F]+,?\\s?)+]|[A-z]+=\\[([0-9]+:0x[0-9a-fA-F]+,?\\s?)+]"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(roamingPokemonString); + while (m.find()) { + String[] segments = m.group().split("="); + String[] offsets = segments[1].substring(1, segments[1].length() - 1).split(","); + switch (segments[0]) { + case "Species": + int[] speciesOverlayOffsets = new int[offsets.length]; + for (int i = 0; i < speciesOverlayOffsets.length; i++) { + speciesOverlayOffsets[i] = parseRIInt(offsets[i]); + } + rp.speciesOverlayOffsets = speciesOverlayOffsets; + break; + case "Level": + int[] levelOverlayOffsets = new int[offsets.length]; + for (int i = 0; i < levelOverlayOffsets.length; i++) { + levelOverlayOffsets[i] = parseRIInt(offsets[i]); + } + rp.levelOverlayOffsets = levelOverlayOffsets; + break; + case "Script": + FileEntry[] entries = new FileEntry[offsets.length]; + for (int i = 0; i < entries.length; i++) { + String[] parts = offsets[i].split(":"); + entries[i] = new FileEntry(parseRIInt(parts[0]), parseRIInt(parts[1])); + } + rp.speciesScriptOffsets = entries; + break; + } + } + return rp; + } + + // This ROM + private Pokemon[] pokes; + private Map formeMappings = new TreeMap<>(); + private List pokemonList; + private List pokemonListInclFormes; + private Move[] moves; + private RomEntry romEntry; + private byte[] arm9; + private List abilityNames; + private List itemNames; + private List shopNames; + private boolean loadedWildMapNames; + private Map wildMapNames; + private ItemList allowedItems, nonBadItems; + private List regularShopItems; + private List opShopItems; + private int hiddenHollowCount = 0; + private boolean hiddenHollowCounted = false; + private List originalDoubleTrainers = new ArrayList<>(); + private boolean effectivenessUpdated; + private int pickupItemsTableOffset; + private long actualArm9CRC32; + private Map actualOverlayCRC32s; + private Map actualFileCRC32s; + + private NARCArchive pokeNarc, moveNarc, stringsNarc, storyTextNarc, scriptNarc, shopNarc; + + @Override + protected boolean detectNDSRom(String ndsCode, byte version) { + return detectNDSRomInner(ndsCode, version); + } + + private static boolean detectNDSRomInner(String ndsCode, byte version) { + return entryFor(ndsCode, version) != null; + } + + private static RomEntry entryFor(String ndsCode, byte version) { + if (ndsCode == null) { + return null; + } + + for (RomEntry re : roms) { + if (ndsCode.equals(re.romCode) && re.version == version) { + return re; + } + } + return null; + } + + @Override + protected void loadedROM(String romCode, byte version) { + this.romEntry = entryFor(romCode, version); + try { + arm9 = readARM9(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + try { + stringsNarc = readNARC(romEntry.getFile("TextStrings")); + storyTextNarc = readNARC(romEntry.getFile("TextStory")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + try { + scriptNarc = readNARC(romEntry.getFile("Scripts")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + if (romEntry.romType == Gen5Constants.Type_BW2) { + try { + shopNarc = readNARC(romEntry.getFile("ShopItems")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + loadPokemonStats(); + pokemonListInclFormes = Arrays.asList(pokes); + pokemonList = Arrays.asList(Arrays.copyOfRange(pokes,0,Gen5Constants.pokemonCount + 1)); + loadMoves(); + + abilityNames = getStrings(false, romEntry.getInt("AbilityNamesTextOffset")); + itemNames = getStrings(false, romEntry.getInt("ItemNamesTextOffset")); + if (romEntry.romType == Gen5Constants.Type_BW) { + shopNames = Gen5Constants.bw1ShopNames; + } + else if (romEntry.romType == Gen5Constants.Type_BW2) { + shopNames = Gen5Constants.bw2ShopNames; + } + + loadedWildMapNames = false; + + allowedItems = Gen5Constants.allowedItems.copy(); + nonBadItems = Gen5Constants.getNonBadItems(romEntry.romType).copy(); + regularShopItems = Gen5Constants.regularShopItems; + opShopItems = Gen5Constants.opShopItems; + + try { + computeCRC32sForRom(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + // If there are tweaks for expanding the ARM9, do it here to keep it simple. + boolean shouldExtendARM9 = romEntry.tweakFiles.containsKey("ShedinjaEvolutionTweak") || romEntry.tweakFiles.containsKey("NewIndexToMusicTweak"); + if (shouldExtendARM9) { + int extendBy = romEntry.getInt("Arm9ExtensionSize"); + arm9 = extendARM9(arm9, extendBy, romEntry.getString("TCMCopyingPrefix"), Gen5Constants.arm9Offset); + } + } + + private void loadPokemonStats() { + try { + pokeNarc = this.readNARC(romEntry.getFile("PokemonStats")); + String[] pokeNames = readPokemonNames(); + int formeCount = Gen5Constants.getFormeCount(romEntry.romType); + pokes = new Pokemon[Gen5Constants.pokemonCount + formeCount + 1]; + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i], pokeNarc.files.get(i), formeMappings); + // Name? + pokes[i].name = pokeNames[i]; + } + + int i = Gen5Constants.pokemonCount + 1; + for (int k: formeMappings.keySet()) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i], pokeNarc.files.get(k), formeMappings); + FormeInfo fi = formeMappings.get(k); + pokes[i].name = pokeNames[fi.baseForme]; + pokes[i].baseForme = pokes[fi.baseForme]; + pokes[i].formeNumber = fi.formeNumber; + pokes[i].formeSpriteIndex = fi.formeSpriteOffset + Gen5Constants.pokemonCount + Gen5Constants.getNonPokemonBattleSpriteCount(romEntry.romType); + pokes[i].formeSuffix = Gen5Constants.getFormeSuffix(k,romEntry.romType); + i = i + 1; + } + populateEvolutions(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + private void loadMoves() { + try { + moveNarc = this.readNARC(romEntry.getFile("MoveData")); + moves = new Move[Gen5Constants.moveCount + 1]; + List moveNames = getStrings(false, romEntry.getInt("MoveNamesTextOffset")); + for (int i = 1; i <= Gen5Constants.moveCount; i++) { + byte[] moveData = moveNarc.files.get(i); + moves[i] = new Move(); + moves[i].name = moveNames.get(i); + moves[i].number = i; + moves[i].internalId = i; + moves[i].effectIndex = readWord(moveData, 16); + moves[i].hitratio = (moveData[4] & 0xFF); + moves[i].power = moveData[3] & 0xFF; + moves[i].pp = moveData[5] & 0xFF; + moves[i].type = Gen5Constants.typeTable[moveData[0] & 0xFF]; + moves[i].flinchPercentChance = moveData[15] & 0xFF; + moves[i].target = moveData[20] & 0xFF; + moves[i].category = Gen5Constants.moveCategoryIndices[moveData[2] & 0xFF]; + moves[i].priority = moveData[6]; + + int critStages = moveData[14] & 0xFF; + if (critStages == 6) { + moves[i].criticalChance = CriticalChance.GUARANTEED; + } else if (critStages > 0) { + moves[i].criticalChance = CriticalChance.INCREASED; + } + + int internalStatusType = readWord(moveData, 8); + int flags = FileFunctions.readFullInt(moveData, 32); + moves[i].makesContact = (flags & 0x001) != 0; + moves[i].isChargeMove = (flags & 0x002) != 0; + moves[i].isRechargeMove = (flags & 0x004) != 0; + moves[i].isPunchMove = (flags & 0x080) != 0; + moves[i].isSoundMove = (flags & 0x100) != 0; + moves[i].isTrapMove = (moves[i].effectIndex == Gen5Constants.trappingEffect || internalStatusType == 8); + int qualities = moveData[1]; + int recoilOrAbsorbPercent = moveData[18]; + if (qualities == Gen5Constants.damageAbsorbQuality) { + moves[i].absorbPercent = recoilOrAbsorbPercent; + } else { + moves[i].recoilPercent = -recoilOrAbsorbPercent; + } + + if (i == Moves.swift) { + perfectAccuracy = (int)moves[i].hitratio; + } + + if (GlobalConstants.normalMultihitMoves.contains(i)) { + moves[i].hitCount = 19 / 6.0; + } else if (GlobalConstants.doubleHitMoves.contains(i)) { + moves[i].hitCount = 2; + } else if (i == Moves.tripleKick) { + moves[i].hitCount = 2.71; // this assumes the first hit lands + } + + switch (qualities) { + case Gen5Constants.noDamageStatChangeQuality: + case Gen5Constants.noDamageStatusAndStatChangeQuality: + // All Allies or Self + if (moves[i].target == 6 || moves[i].target == 7) { + moves[i].statChangeMoveType = StatChangeMoveType.NO_DAMAGE_USER; + } else { + moves[i].statChangeMoveType = StatChangeMoveType.NO_DAMAGE_TARGET; + } + break; + case Gen5Constants.damageTargetDebuffQuality: + moves[i].statChangeMoveType = StatChangeMoveType.DAMAGE_TARGET; + break; + case Gen5Constants.damageUserBuffQuality: + moves[i].statChangeMoveType = StatChangeMoveType.DAMAGE_USER; + break; + default: + moves[i].statChangeMoveType = StatChangeMoveType.NONE_OR_UNKNOWN; + break; + } + + for (int statChange = 0; statChange < 3; statChange++) { + moves[i].statChanges[statChange].type = StatChangeType.values()[moveData[21 + statChange]]; + moves[i].statChanges[statChange].stages = moveData[24 + statChange]; + moves[i].statChanges[statChange].percentChance = moveData[27 + statChange]; + } + + // Exclude status types that aren't in the StatusType enum. + if (internalStatusType < 7) { + moves[i].statusType = StatusType.values()[internalStatusType]; + if (moves[i].statusType == StatusType.POISON && (i == Moves.toxic || i == Moves.poisonFang)) { + moves[i].statusType = StatusType.TOXIC_POISON; + } + moves[i].statusPercentChance = moveData[10] & 0xFF; + if (moves[i].number == Moves.chatter) { + moves[i].statusPercentChance = 1.0; + } + switch (qualities) { + case Gen5Constants.noDamageStatusQuality: + case Gen5Constants.noDamageStatusAndStatChangeQuality: + moves[i].statusMoveType = StatusMoveType.NO_DAMAGE; + break; + case Gen5Constants.damageStatusQuality: + moves[i].statusMoveType = StatusMoveType.DAMAGE; + break; + } + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + private void loadBasicPokeStats(Pokemon pkmn, byte[] stats, Map altFormes) { + pkmn.hp = stats[Gen5Constants.bsHPOffset] & 0xFF; + pkmn.attack = stats[Gen5Constants.bsAttackOffset] & 0xFF; + pkmn.defense = stats[Gen5Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = stats[Gen5Constants.bsSpeedOffset] & 0xFF; + pkmn.spatk = stats[Gen5Constants.bsSpAtkOffset] & 0xFF; + pkmn.spdef = stats[Gen5Constants.bsSpDefOffset] & 0xFF; + // Type + pkmn.primaryType = Gen5Constants.typeTable[stats[Gen5Constants.bsPrimaryTypeOffset] & 0xFF]; + pkmn.secondaryType = Gen5Constants.typeTable[stats[Gen5Constants.bsSecondaryTypeOffset] & 0xFF]; + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + pkmn.catchRate = stats[Gen5Constants.bsCatchRateOffset] & 0xFF; + pkmn.growthCurve = ExpCurve.fromByte(stats[Gen5Constants.bsGrowthCurveOffset]); + + pkmn.ability1 = stats[Gen5Constants.bsAbility1Offset] & 0xFF; + pkmn.ability2 = stats[Gen5Constants.bsAbility2Offset] & 0xFF; + pkmn.ability3 = stats[Gen5Constants.bsAbility3Offset] & 0xFF; + + // Held Items? + int item1 = readWord(stats, Gen5Constants.bsCommonHeldItemOffset); + int item2 = readWord(stats, Gen5Constants.bsRareHeldItemOffset); + + if (item1 == item2) { + // guaranteed + pkmn.guaranteedHeldItem = item1; + pkmn.commonHeldItem = 0; + pkmn.rareHeldItem = 0; + pkmn.darkGrassHeldItem = 0; + } else { + pkmn.guaranteedHeldItem = 0; + pkmn.commonHeldItem = item1; + pkmn.rareHeldItem = item2; + pkmn.darkGrassHeldItem = readWord(stats, Gen5Constants.bsDarkGrassHeldItemOffset); + } + + int formeCount = stats[Gen5Constants.bsFormeCountOffset] & 0xFF; + if (formeCount > 1) { + int firstFormeOffset = readWord(stats, Gen5Constants.bsFormeOffset); + if (firstFormeOffset != 0) { + for (int i = 1; i < formeCount; i++) { + altFormes.put(firstFormeOffset + i - 1,new FormeInfo(pkmn.number,i,readWord(stats,Gen5Constants.bsFormeSpriteOffset))); // Assumes that formes are in memory in the same order as their numbers + if (pkmn.number == Species.keldeo) { + pkmn.cosmeticForms = formeCount; + } + } + } else { + if (pkmn.number != Species.cherrim && pkmn.number != Species.arceus && pkmn.number != Species.deerling && pkmn.number != Species.sawsbuck && pkmn.number < Species.genesect) { + // Reason for exclusions: + // Cherrim/Arceus/Genesect: to avoid confusion + // Deerling/Sawsbuck: handled automatically in gen 5 + pkmn.cosmeticForms = formeCount; + } + if (pkmn.number == Species.Gen5Formes.keldeoCosmetic1) { + pkmn.actuallyCosmetic = true; + } + } + } + } + + private String[] readPokemonNames() { + String[] pokeNames = new String[Gen5Constants.pokemonCount + 1]; + List nameList = getStrings(false, romEntry.getInt("PokemonNamesTextOffset")); + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + pokeNames[i] = nameList.get(i); + } + return pokeNames; + } + + @Override + protected void savingROM() { + savePokemonStats(); + saveMoves(); + try { + writeARM9(arm9); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + try { + writeNARC(romEntry.getFile("TextStrings"), stringsNarc); + writeNARC(romEntry.getFile("TextStory"), storyTextNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + try { + writeNARC(romEntry.getFile("Scripts"), scriptNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void saveMoves() { + for (int i = 1; i <= Gen5Constants.moveCount; i++) { + byte[] data = moveNarc.files.get(i); + data[2] = Gen5Constants.moveCategoryToByte(moves[i].category); + data[3] = (byte) moves[i].power; + data[0] = Gen5Constants.typeToByte(moves[i].type); + int hitratio = (int) Math.round(moves[i].hitratio); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 101) { + hitratio = 100; + } + data[4] = (byte) hitratio; + data[5] = (byte) moves[i].pp; + } + + try { + this.writeNARC(romEntry.getFile("MoveData"), moveNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + private void savePokemonStats() { + List nameList = getStrings(false, romEntry.getInt("PokemonNamesTextOffset")); + + int formeCount = Gen5Constants.getFormeCount(romEntry.romType); + int formeOffset = Gen5Constants.getFormeOffset(romEntry.romType); + for (int i = 1; i <= Gen5Constants.pokemonCount + formeCount; i++) { + if (i > Gen5Constants.pokemonCount) { + saveBasicPokeStats(pokes[i], pokeNarc.files.get(i + formeOffset)); + continue; + } + saveBasicPokeStats(pokes[i], pokeNarc.files.get(i)); + nameList.set(i, pokes[i].name); + } + + setStrings(false, romEntry.getInt("PokemonNamesTextOffset"), nameList); + + try { + this.writeNARC(romEntry.getFile("PokemonStats"), pokeNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + writeEvolutions(); + } + + private void saveBasicPokeStats(Pokemon pkmn, byte[] stats) { + stats[Gen5Constants.bsHPOffset] = (byte) pkmn.hp; + stats[Gen5Constants.bsAttackOffset] = (byte) pkmn.attack; + stats[Gen5Constants.bsDefenseOffset] = (byte) pkmn.defense; + stats[Gen5Constants.bsSpeedOffset] = (byte) pkmn.speed; + stats[Gen5Constants.bsSpAtkOffset] = (byte) pkmn.spatk; + stats[Gen5Constants.bsSpDefOffset] = (byte) pkmn.spdef; + stats[Gen5Constants.bsPrimaryTypeOffset] = Gen5Constants.typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + stats[Gen5Constants.bsSecondaryTypeOffset] = stats[Gen5Constants.bsPrimaryTypeOffset]; + } else { + stats[Gen5Constants.bsSecondaryTypeOffset] = Gen5Constants.typeToByte(pkmn.secondaryType); + } + stats[Gen5Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + stats[Gen5Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + + stats[Gen5Constants.bsAbility1Offset] = (byte) pkmn.ability1; + stats[Gen5Constants.bsAbility2Offset] = (byte) pkmn.ability2; + stats[Gen5Constants.bsAbility3Offset] = (byte) pkmn.ability3; + + // Held items + if (pkmn.guaranteedHeldItem > 0) { + writeWord(stats, Gen5Constants.bsCommonHeldItemOffset, pkmn.guaranteedHeldItem); + writeWord(stats, Gen5Constants.bsRareHeldItemOffset, pkmn.guaranteedHeldItem); + writeWord(stats, Gen5Constants.bsDarkGrassHeldItemOffset, 0); + } else { + writeWord(stats, Gen5Constants.bsCommonHeldItemOffset, pkmn.commonHeldItem); + writeWord(stats, Gen5Constants.bsRareHeldItemOffset, pkmn.rareHeldItem); + writeWord(stats, Gen5Constants.bsDarkGrassHeldItemOffset, pkmn.darkGrassHeldItem); + } + } + + @Override + public List getPokemon() { + return pokemonList; + } + + @Override + public List getPokemonInclFormes() { + return pokemonListInclFormes; + } + + @Override + public List getAltFormes() { + int formeCount = Gen5Constants.getFormeCount(romEntry.romType); + return pokemonListInclFormes.subList(Gen5Constants.pokemonCount + 1, Gen5Constants.pokemonCount + formeCount + 1); + } + + @Override + public List getMegaEvolutions() { + return new ArrayList<>(); + } + + @Override + public Pokemon getAltFormeOfPokemon(Pokemon pk, int forme) { + int pokeNum = Gen5Constants.getAbsolutePokeNumByBaseForme(pk.number,forme); + return pokeNum != 0 ? pokes[pokeNum] : pk; + } + + @Override + public List getIrregularFormes() { + return Gen5Constants.getIrregularFormes(romEntry.romType).stream().map(i -> pokes[i]).collect(Collectors.toList()); + } + + @Override + public boolean hasFunctionalFormes() { + return true; + } + + @Override + public List getStarters() { + NARCArchive scriptNARC = scriptNarc; + List starters = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + OffsetWithinEntry[] thisStarter = romEntry.offsetArrayEntries.get("StarterOffsets" + (i + 1)); + starters.add(pokes[readWord(scriptNARC.files.get(thisStarter[0].entry), thisStarter[0].offset)]); + } + return starters; + } + + @Override + public boolean setStarters(List newStarters) { + if (newStarters.size() != 3) { + return false; + } + + // Fix up starter offsets + try { + NARCArchive scriptNARC = scriptNarc; + for (int i = 0; i < 3; i++) { + int starter = newStarters.get(i).number; + OffsetWithinEntry[] thisStarter = romEntry.offsetArrayEntries.get("StarterOffsets" + (i + 1)); + for (OffsetWithinEntry entry : thisStarter) { + writeWord(scriptNARC.files.get(entry.entry), entry.offset, starter); + } + } + // GIVE ME BACK MY PURRLOIN + if (romEntry.romType == Gen5Constants.Type_BW2) { + byte[] newScript = Gen5Constants.bw2NewStarterScript; + byte[] oldFile = scriptNARC.files.get(romEntry.getInt("PokedexGivenFileOffset")); + byte[] newFile = new byte[oldFile.length + newScript.length]; + int offset = find(oldFile, Gen5Constants.bw2StarterScriptMagic); + if (offset > 0) { + System.arraycopy(oldFile, 0, newFile, 0, oldFile.length); + System.arraycopy(newScript, 0, newFile, oldFile.length, newScript.length); + if (romEntry.romCode.charAt(3) == 'J') { + newFile[oldFile.length + 0x6] -= 4; + } + newFile[offset++] = 0x1E; + newFile[offset++] = 0x0; + writeRelativePointer(newFile, offset, oldFile.length); + scriptNARC.files.set(romEntry.getInt("PokedexGivenFileOffset"), newFile); + } + } else { + byte[] newScript = Gen5Constants.bw1NewStarterScript; + + byte[] oldFile = scriptNARC.files.get(romEntry.getInt("PokedexGivenFileOffset")); + byte[] newFile = new byte[oldFile.length + newScript.length]; + int offset = find(oldFile, Gen5Constants.bw1StarterScriptMagic); + if (offset > 0) { + System.arraycopy(oldFile, 0, newFile, 0, oldFile.length); + System.arraycopy(newScript, 0, newFile, oldFile.length, newScript.length); + if (romEntry.romCode.charAt(3) == 'J') { + newFile[oldFile.length + 0x4] -= 4; + newFile[oldFile.length + 0x8] -= 4; + } + newFile[offset++] = 0x04; + newFile[offset++] = 0x0; + writeRelativePointer(newFile, offset, oldFile.length); + scriptNARC.files.set(romEntry.getInt("PokedexGivenFileOffset"), newFile); + } + } + + // Starter sprites + NARCArchive starterNARC = this.readNARC(romEntry.getFile("StarterGraphics")); + NARCArchive pokespritesNARC = this.readNARC(romEntry.getFile("PokemonGraphics")); + replaceStarterFiles(starterNARC, pokespritesNARC, 0, newStarters.get(0).number); + replaceStarterFiles(starterNARC, pokespritesNARC, 1, newStarters.get(1).number); + replaceStarterFiles(starterNARC, pokespritesNARC, 2, newStarters.get(2).number); + writeNARC(romEntry.getFile("StarterGraphics"), starterNARC); + + // Starter cries + byte[] starterCryOverlay = this.readOverlay(romEntry.getInt("StarterCryOvlNumber")); + String starterCryTablePrefix = romEntry.getString("StarterCryTablePrefix"); + int offset = find(starterCryOverlay, starterCryTablePrefix); + if (offset > 0) { + offset += starterCryTablePrefix.length() / 2; // because it was a prefix + for (Pokemon newStarter : newStarters) { + writeWord(starterCryOverlay, offset, newStarter.number); + offset += 2; + } + this.writeOverlay(romEntry.getInt("StarterCryOvlNumber"), starterCryOverlay); + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + // Fix text depending on version + if (romEntry.romType == Gen5Constants.Type_BW) { + List yourHouseStrings = getStrings(true, romEntry.getInt("StarterLocationTextOffset")); + for (int i = 0; i < 3; i++) { + yourHouseStrings.set(Gen5Constants.bw1StarterTextOffset - i, + "\\xF000\\xBD02\\x0000The " + newStarters.get(i).primaryType.camelCase() + + "-type Pok\\x00E9mon\\xFFFE\\xF000\\xBD02\\x0000" + newStarters.get(i).name); + } + // Update what the friends say + yourHouseStrings + .set(Gen5Constants.bw1CherenText1Offset, + "Cheren: Hey, how come you get to pick\\xFFFEout my Pok\\x00E9mon?" + + "\\xF000\\xBE01\\x0000\\xFFFEOh, never mind. I wanted this one\\xFFFEfrom the start, anyway." + + "\\xF000\\xBE01\\x0000"); + yourHouseStrings.set(Gen5Constants.bw1CherenText2Offset, + "It's decided. You'll be my opponent...\\xFFFEin our first Pok\\x00E9mon battle!" + + "\\xF000\\xBE01\\x0000\\xFFFELet's see what you can do, \\xFFFEmy Pok\\x00E9mon!" + + "\\xF000\\xBE01\\x0000"); + + // rewrite + setStrings(true, romEntry.getInt("StarterLocationTextOffset"), yourHouseStrings); + } else { + List starterTownStrings = getStrings(true, romEntry.getInt("StarterLocationTextOffset")); + for (int i = 0; i < 3; i++) { + starterTownStrings.set(Gen5Constants.bw2StarterTextOffset - i, "\\xF000\\xBD02\\x0000The " + + newStarters.get(i).primaryType.camelCase() + + "-type Pok\\x00E9mon\\xFFFE\\xF000\\xBD02\\x0000" + newStarters.get(i).name); + } + // Update what the rival says + starterTownStrings.set(Gen5Constants.bw2RivalTextOffset, + "\\xF000\\x0100\\x0001\\x0001: Let's see how good\\xFFFEa Trainer you are!" + + "\\xF000\\xBE01\\x0000\\xFFFEI'll use my Pok\\x00E9mon" + + "\\xFFFEthat I raised from an Egg!\\xF000\\xBE01\\x0000"); + + // rewrite + setStrings(true, romEntry.getInt("StarterLocationTextOffset"), starterTownStrings); + } + return true; + } + + @Override + public boolean hasStarterAltFormes() { + return false; + } + + @Override + public int starterCount() { + return 3; + } + + @Override + public Map getUpdatedPokemonStats(int generation) { + return GlobalConstants.getStatChanges(generation); + } + + @Override + public boolean supportsStarterHeldItems() { + return false; + } + + @Override + public List getStarterHeldItems() { + // do nothing + return new ArrayList<>(); + } + + @Override + public void setStarterHeldItems(List items) { + // do nothing + } + + private void replaceStarterFiles(NARCArchive starterNARC, NARCArchive pokespritesNARC, int starterIndex, + int pokeNumber) { + starterNARC.files.set(starterIndex * 2, pokespritesNARC.files.get(pokeNumber * 20 + 18)); + // Get the picture... + byte[] compressedPic = pokespritesNARC.files.get(pokeNumber * 20); + // Decompress it with JavaDSDecmp + byte[] uncompressedPic = DSDecmp.Decompress(compressedPic); + starterNARC.files.set(12 + starterIndex, uncompressedPic); + } + + @Override + public List getMoves() { + return Arrays.asList(moves); + } + + @Override + public List getEncounters(boolean useTimeOfDay) { + if (!loadedWildMapNames) { + loadWildMapNames(); + } + try { + NARCArchive encounterNARC = readNARC(romEntry.getFile("WildPokemon")); + List encounters = new ArrayList<>(); + int idx = -1; + for (byte[] entry : encounterNARC.files) { + idx++; + if (entry.length > Gen5Constants.perSeasonEncounterDataLength && useTimeOfDay) { + for (int i = 0; i < 4; i++) { + processEncounterEntry(encounters, entry, i * Gen5Constants.perSeasonEncounterDataLength, idx); + } + } else { + processEncounterEntry(encounters, entry, 0, idx); + } + } + return encounters; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void processEncounterEntry(List encounters, byte[] entry, int startOffset, int idx) { + + if (!wildMapNames.containsKey(idx)) { + wildMapNames.put(idx, "? Unknown ?"); + } + String mapName = wildMapNames.get(idx); + + int[] amounts = Gen5Constants.encountersOfEachType; + + int offset = 8; + for (int i = 0; i < 7; i++) { + int rate = entry[startOffset + i] & 0xFF; + if (rate != 0) { + List encs = readEncounters(entry, startOffset + offset, amounts[i]); + EncounterSet area = new EncounterSet(); + area.rate = rate; + area.encounters = encs; + area.offset = idx; + area.displayName = mapName + " " + Gen5Constants.encounterTypeNames[i]; + encounters.add(area); + } + offset += amounts[i] * 4; + } + + } + + private List readEncounters(byte[] data, int offset, int number) { + List encs = new ArrayList<>(); + for (int i = 0; i < number; i++) { + Encounter enc1 = new Encounter(); + int species = readWord(data, offset + i * 4) & 0x7FF; + int forme = readWord(data, offset + i * 4) >> 11; + Pokemon baseForme = pokes[species]; + if (forme <= baseForme.cosmeticForms || forme == 30 || forme == 31) { + enc1.pokemon = pokes[species]; + } else { + int speciesWithForme = Gen5Constants.getAbsolutePokeNumByBaseForme(species,forme); + if (speciesWithForme == 0) { + enc1.pokemon = pokes[species]; // Failsafe + } else { + enc1.pokemon = pokes[speciesWithForme]; + } + } + enc1.formeNumber = forme; + enc1.level = data[offset + 2 + i * 4] & 0xFF; + enc1.maxLevel = data[offset + 3 + i * 4] & 0xFF; + encs.add(enc1); + } + return encs; + } + + @Override + public void setEncounters(boolean useTimeOfDay, List encountersList) { + try { + NARCArchive encounterNARC = readNARC(romEntry.getFile("WildPokemon")); + Iterator encounters = encountersList.iterator(); + for (byte[] entry : encounterNARC.files) { + writeEncounterEntry(encounters, entry, 0); + if (entry.length > 232) { + if (useTimeOfDay) { + for (int i = 1; i < 4; i++) { + writeEncounterEntry(encounters, entry, i * 232); + } + } else { + // copy for other 3 seasons + System.arraycopy(entry, 0, entry, 232, 232); + System.arraycopy(entry, 0, entry, 464, 232); + System.arraycopy(entry, 0, entry, 696, 232); + } + } + } + + // Save + writeNARC(romEntry.getFile("WildPokemon"), encounterNARC); + + this.updatePokedexAreaData(encounterNARC); + + // Habitat List + if (romEntry.romType == Gen5Constants.Type_BW2) { + // disabled: habitat list changes cause a crash if too many + // entries for now. + + // NARCArchive habitatNARC = readNARC(romEntry.getFile("HabitatList")); + // for (int i = 0; i < habitatNARC.files.size(); i++) { + // byte[] oldEntry = habitatNARC.files.get(i); + // int[] encounterFiles = habitatListEntries[i]; + // Map pokemonHere = new TreeMap(); + // for (int encFile : encounterFiles) { + // byte[] encEntry = encounterNARC.files.get(encFile); + // if (encEntry.length > 232) { + // for (int s = 0; s < 4; s++) { + // addHabitats(encEntry, s * 232, pokemonHere, s); + // } + // } else { + // for (int s = 0; s < 4; s++) { + // addHabitats(encEntry, 0, pokemonHere, s); + // } + // } + // } + // // Make the new file + // byte[] habitatEntry = new byte[10 + pokemonHere.size() * 28]; + // System.arraycopy(oldEntry, 0, habitatEntry, 0, 10); + // habitatEntry[8] = (byte) pokemonHere.size(); + // // 28-byte entries for each pokemon + // int num = -1; + // for (Pokemon pkmn : pokemonHere.keySet()) { + // num++; + // writeWord(habitatEntry, 10 + num * 28, pkmn.number); + // byte[] slots = pokemonHere.get(pkmn); + // System.arraycopy(slots, 0, habitatEntry, 12 + num * 28, + // 12); + // } + // // Save + // habitatNARC.files.set(i, habitatEntry); + // } + // // Save habitat + // this.writeNARC(romEntry.getFile("HabitatList"), + // habitatNARC); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + private void updatePokedexAreaData(NARCArchive encounterNARC) throws IOException { + NARCArchive areaNARC = this.readNARC(romEntry.getFile("PokedexAreaData")); + int areaDataEntryLength = Gen5Constants.getAreaDataEntryLength(romEntry.romType); + int encounterAreaCount = Gen5Constants.getEncounterAreaCount(romEntry.romType); + List newFiles = new ArrayList<>(); + for (int i = 0; i < Gen5Constants.pokemonCount; i++) { + byte[] nf = new byte[areaDataEntryLength]; + newFiles.add(nf); + } + // Get data now + for (int i = 0; i < encounterNARC.files.size(); i++) { + byte[] encEntry = encounterNARC.files.get(i); + if (encEntry.length > Gen5Constants.perSeasonEncounterDataLength) { + for (int season = 0; season < 4; season++) { + updateAreaDataFromEncounterEntry(encEntry, season * Gen5Constants.perSeasonEncounterDataLength, newFiles, season, i); + } + } else { + for (int season = 0; season < 4; season++) { + updateAreaDataFromEncounterEntry(encEntry, 0, newFiles, season, i); + } + } + } + // Now update unobtainables, check for seasonal-dependent entries, & save + for (int i = 0; i < Gen5Constants.pokemonCount; i++) { + byte[] file = newFiles.get(i); + for (int season = 0; season < 4; season++) { + boolean unobtainable = true; + for (int enc = 0; enc < encounterAreaCount; enc++) { + if (file[season * (encounterAreaCount + 1) + enc + 2] != 0) { + unobtainable = false; + break; + } + } + if (unobtainable) { + file[season * (encounterAreaCount + 1) + 1] = 1; + } + } + boolean seasonalDependent = false; + for (int enc = 0; enc < encounterAreaCount; enc++) { + byte springEnc = file[enc + 2]; + byte summerEnc = file[(encounterAreaCount + 1) + enc + 2]; + byte autumnEnc = file[2 * (encounterAreaCount + 1) + enc + 2]; + byte winterEnc = file[3 * (encounterAreaCount + 1) + enc + 2]; + boolean allSeasonsAreTheSame = springEnc == summerEnc && springEnc == autumnEnc && springEnc == winterEnc; + if (!allSeasonsAreTheSame) { + seasonalDependent = true; + break; + } + } + if (!seasonalDependent) { + file[0] = 1; + } + areaNARC.files.set(i, file); + } + // Save + this.writeNARC(romEntry.getFile("PokedexAreaData"), areaNARC); + } + + private void updateAreaDataFromEncounterEntry(byte[] entry, int startOffset, List areaData, int season, int fileNumber) { + int[] amounts = Gen5Constants.encountersOfEachType; + int encounterAreaCount = Gen5Constants.getEncounterAreaCount(romEntry.romType); + int[] wildFileToAreaMap = Gen5Constants.getWildFileToAreaMap(romEntry.romType); + + int offset = 8; + for (int i = 0; i < 7; i++) { + int rate = entry[startOffset + i] & 0xFF; + if (rate != 0) { + for (int e = 0; e < amounts[i]; e++) { + Pokemon pkmn = pokes[((entry[startOffset + offset + e * 4] & 0xFF) + ((entry[startOffset + offset + + 1 + e * 4] & 0x03) << 8))]; + byte[] pokeFile = areaData.get(pkmn.getBaseNumber() - 1); + int areaIndex = wildFileToAreaMap[fileNumber]; + // Route 4? + if (romEntry.romType == Gen5Constants.Type_BW2 && areaIndex == Gen5Constants.bw2Route4AreaIndex) { + if ((fileNumber == Gen5Constants.b2Route4EncounterFile && romEntry.romCode.charAt(2) == 'D') + || (fileNumber == Gen5Constants.w2Route4EncounterFile && romEntry.romCode.charAt(2) == 'E')) { + areaIndex = -1; // wrong version + } + } + // Victory Road? + if (romEntry.romType == Gen5Constants.Type_BW2 && areaIndex == Gen5Constants.bw2VictoryRoadAreaIndex) { + if (romEntry.romCode.charAt(2) == 'D') { + // White 2 + if (fileNumber == Gen5Constants.b2VRExclusiveRoom1 + || fileNumber == Gen5Constants.b2VRExclusiveRoom2) { + areaIndex = -1; // wrong version + } + } else { + // Black 2 + if (fileNumber == Gen5Constants.w2VRExclusiveRoom1 + || fileNumber == Gen5Constants.w2VRExclusiveRoom2) { + areaIndex = -1; // wrong version + } + } + } + // Reversal Mountain? + if (romEntry.romType == Gen5Constants.Type_BW2 && areaIndex == Gen5Constants.bw2ReversalMountainAreaIndex) { + if (romEntry.romCode.charAt(2) == 'D') { + // White 2 + if (fileNumber >= Gen5Constants.b2ReversalMountainStart + && fileNumber <= Gen5Constants.b2ReversalMountainEnd) { + areaIndex = -1; // wrong version + } + } else { + // Black 2 + if (fileNumber >= Gen5Constants.w2ReversalMountainStart + && fileNumber <= Gen5Constants.w2ReversalMountainEnd) { + areaIndex = -1; // wrong version + } + } + } + // Skip stuff that isn't on the map or is wrong version + if (areaIndex != -1) { + pokeFile[season * (encounterAreaCount + 1) + 2 + areaIndex] |= (1 << i); + } + } + } + offset += amounts[i] * 4; + } + } + + @SuppressWarnings("unused") + private void addHabitats(byte[] entry, int startOffset, Map pokemonHere, int season) { + int[] amounts = Gen5Constants.encountersOfEachType; + int[] type = Gen5Constants.habitatClassificationOfEachType; + + int offset = 8; + for (int i = 0; i < 7; i++) { + int rate = entry[startOffset + i] & 0xFF; + if (rate != 0) { + for (int e = 0; e < amounts[i]; e++) { + Pokemon pkmn = pokes[((entry[startOffset + offset + e * 4] & 0xFF) + ((entry[startOffset + offset + + 1 + e * 4] & 0x03) << 8))]; + if (pokemonHere.containsKey(pkmn)) { + pokemonHere.get(pkmn)[type[i] + season * 3] = 1; + } else { + byte[] locs = new byte[12]; + locs[type[i] + season * 3] = 1; + pokemonHere.put(pkmn, locs); + } + } + } + offset += amounts[i] * 4; + } + } + + private void writeEncounterEntry(Iterator encounters, byte[] entry, int startOffset) { + int[] amounts = Gen5Constants.encountersOfEachType; + + int offset = 8; + for (int i = 0; i < 7; i++) { + int rate = entry[startOffset + i] & 0xFF; + if (rate != 0) { + EncounterSet area = encounters.next(); + for (int j = 0; j < amounts[i]; j++) { + Encounter enc = area.encounters.get(j); + int speciesAndFormeData = (enc.formeNumber << 11) + enc.pokemon.getBaseNumber(); + writeWord(entry, startOffset + offset + j * 4, speciesAndFormeData); + entry[startOffset + offset + j * 4 + 2] = (byte) enc.level; + entry[startOffset + offset + j * 4 + 3] = (byte) enc.maxLevel; + } + } + offset += amounts[i] * 4; + } + } + + private void loadWildMapNames() { + try { + wildMapNames = new HashMap<>(); + byte[] mapHeaderData = this.readNARC(romEntry.getFile("MapTableFile")).files.get(0); + int numMapHeaders = mapHeaderData.length / 48; + List allMapNames = getStrings(false, romEntry.getInt("MapNamesTextOffset")); + for (int map = 0; map < numMapHeaders; map++) { + int baseOffset = map * 48; + int mapNameIndex = mapHeaderData[baseOffset + 26] & 0xFF; + String mapName = allMapNames.get(mapNameIndex); + if (romEntry.romType == Gen5Constants.Type_BW2) { + int wildSet = mapHeaderData[baseOffset + 20] & 0xFF; + if (wildSet != 255) { + wildMapNames.put(wildSet, mapName); + } + } else { + int wildSet = readWord(mapHeaderData, baseOffset + 20); + if (wildSet != 65535) { + wildMapNames.put(wildSet, mapName); + } + } + } + loadedWildMapNames = true; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + @Override + public List getTrainers() { + List allTrainers = new ArrayList<>(); + try { + NARCArchive trainers = this.readNARC(romEntry.getFile("TrainerData")); + NARCArchive trpokes = this.readNARC(romEntry.getFile("TrainerPokemon")); + int trainernum = trainers.files.size(); + List tclasses = this.getTrainerClassNames(); + List tnames = this.getTrainerNames(); + for (int i = 1; i < trainernum; i++) { + // Trainer entries are 20 bytes + // Team flags; 1 byte; 0x01 = custom moves, 0x02 = held item + // Class; 1 byte + // Battle Mode; 1 byte; 0=single, 1=double, 2=triple, 3=rotation + // Number of pokemon in team; 1 byte + // Items; 2 bytes each, 4 item slots + // AI Flags; 2 byte + // 2 bytes not used + // Healer; 1 byte; 0x01 means they will heal player's pokes after defeat. + // Victory Money; 1 byte; The money given out after defeat = + // 4 * this value * highest level poke in party + // Victory Item; 2 bytes; The item given out after defeat (e.g. berries) + byte[] trainer = trainers.files.get(i); + byte[] trpoke = trpokes.files.get(i); + Trainer tr = new Trainer(); + tr.poketype = trainer[0] & 0xFF; + tr.index = i; + tr.trainerclass = trainer[1] & 0xFF; + int numPokes = trainer[3] & 0xFF; + int pokeOffs = 0; + tr.fullDisplayName = tclasses.get(tr.trainerclass) + " " + tnames.get(i - 1); + if (trainer[2] == 1) { + originalDoubleTrainers.add(i); + } + for (int poke = 0; poke < numPokes; poke++) { + // Structure is + // IV SB LV LV SP SP FRM FRM + // (HI HI) + // (M1 M1 M2 M2 M3 M3 M4 M4) + // where SB = 0 0 Ab Ab 0 0 Fm Ml + // IV is a "difficulty" level between 0 and 255 to represent 0 to 31 IVs. + // These IVs affect all attributes. For the vanilla games, the + // vast majority of trainers have 0 IVs; Elite Four members will + // have 30 IVs. + // Ab Ab = ability number, 0 for random + // Fm = 1 for forced female + // Ml = 1 for forced male + // There's also a trainer flag to force gender, but + // this allows fixed teams with mixed genders. + + int difficulty = trpoke[pokeOffs] & 0xFF; + int level = readWord(trpoke, pokeOffs + 2); + int species = readWord(trpoke, pokeOffs + 4); + int formnum = readWord(trpoke, pokeOffs + 6); + TrainerPokemon tpk = new TrainerPokemon(); + tpk.level = level; + tpk.pokemon = pokes[species]; + tpk.IVs = (difficulty) * 31 / 255; + int abilityAndFlag = trpoke[pokeOffs + 1]; + tpk.abilitySlot = (abilityAndFlag >>> 4) & 0xF; + tpk.forcedGenderFlag = (abilityAndFlag & 0xF); + tpk.forme = formnum; + tpk.formeSuffix = Gen5Constants.getFormeSuffixByBaseForme(species,formnum); + pokeOffs += 8; + if (tr.pokemonHaveItems()) { + tpk.heldItem = readWord(trpoke, pokeOffs); + pokeOffs += 2; + } + if (tr.pokemonHaveCustomMoves()) { + for (int move = 0; move < 4; move++) { + tpk.moves[move] = readWord(trpoke, pokeOffs + (move*2)); + } + pokeOffs += 8; + } + tr.pokemon.add(tpk); + } + allTrainers.add(tr); + } + if (romEntry.romType == Gen5Constants.Type_BW) { + Gen5Constants.tagTrainersBW(allTrainers); + Gen5Constants.setMultiBattleStatusBW(allTrainers); + } else { + if (!romEntry.getFile("DriftveilPokemon").isEmpty()) { + NARCArchive driftveil = this.readNARC(romEntry.getFile("DriftveilPokemon")); + int currentFile = 1; + for (int trno = 0; trno < 17; trno++) { + Trainer tr = new Trainer(); + tr.poketype = 3; // have held items and custom moves + int nameAndClassIndex = Gen5Constants.bw2DriftveilTrainerOffsets.get(trno); + tr.fullDisplayName = tclasses.get(Gen5Constants.normalTrainerClassLength + nameAndClassIndex) + " " + tnames.get(Gen5Constants.normalTrainerNameLength + nameAndClassIndex); + tr.requiresUniqueHeldItems = true; + int pokemonNum = 6; + if (trno < 2) { + pokemonNum = 3; + } + for (int poke = 0; poke < pokemonNum; poke++) { + byte[] pkmndata = driftveil.files.get(currentFile); + int species = readWord(pkmndata, 0); + TrainerPokemon tpk = new TrainerPokemon(); + tpk.level = 25; + tpk.pokemon = pokes[species]; + tpk.IVs = 31; + tpk.heldItem = readWord(pkmndata, 12); + for (int move = 0; move < 4; move++) { + tpk.moves[move] = readWord(pkmndata, 2 + (move*2)); + } + tr.pokemon.add(tpk); + currentFile++; + } + allTrainers.add(tr); + } + } + boolean isBlack2 = romEntry.romCode.startsWith("IRE"); + Gen5Constants.tagTrainersBW2(allTrainers); + Gen5Constants.setMultiBattleStatusBW2(allTrainers, isBlack2); + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + return allTrainers; + } + + @Override + public List getMainPlaythroughTrainers() { + if (romEntry.romType == Gen5Constants.Type_BW) { // BW1 + return Gen5Constants.bw1MainPlaythroughTrainers; + } + else if (romEntry.romType == Gen5Constants.Type_BW2) { // BW2 + return Gen5Constants.bw2MainPlaythroughTrainers; + } + else { + return Gen5Constants.emptyPlaythroughTrainers; + } + } + + @Override + public List getEliteFourTrainers(boolean isChallengeMode) { + if (isChallengeMode) { + return Arrays.stream(romEntry.arrayEntries.get("ChallengeModeEliteFourIndices")).boxed().collect(Collectors.toList()); + } else { + return Arrays.stream(romEntry.arrayEntries.get("EliteFourIndices")).boxed().collect(Collectors.toList()); + } + } + + + @Override + public List getEvolutionItems() { + return Gen5Constants.evolutionItems; + } + + @Override + public void setTrainers(List trainerData, boolean doubleBattleMode) { + Iterator allTrainers = trainerData.iterator(); + try { + NARCArchive trainers = this.readNARC(romEntry.getFile("TrainerData")); + NARCArchive trpokes = new NARCArchive(); + // Get current movesets in case we need to reset them for certain + // trainer mons. + Map> movesets = this.getMovesLearnt(); + // empty entry + trpokes.files.add(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }); + int trainernum = trainers.files.size(); + for (int i = 1; i < trainernum; i++) { + byte[] trainer = trainers.files.get(i); + Trainer tr = allTrainers.next(); + // preserve original poketype for held item & moves + trainer[0] = (byte) tr.poketype; + int numPokes = tr.pokemon.size(); + trainer[3] = (byte) numPokes; + + if (doubleBattleMode) { + if (!tr.skipImportant()) { + if (trainer[2] == 0) { + trainer[2] = 1; + trainer[12] |= 0x80; // Flag that needs to be set for trainers not to attack their own pokes + } + } + } + + int bytesNeeded = 8 * numPokes; + if (tr.pokemonHaveCustomMoves()) { + bytesNeeded += 8 * numPokes; + } + if (tr.pokemonHaveItems()) { + bytesNeeded += 2 * numPokes; + } + byte[] trpoke = new byte[bytesNeeded]; + int pokeOffs = 0; + Iterator tpokes = tr.pokemon.iterator(); + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon tp = tpokes.next(); + // Add 1 to offset integer division truncation + int difficulty = Math.min(255, 1 + (tp.IVs * 255) / 31); + byte abilityAndFlag = (byte)((tp.abilitySlot << 4) | tp.forcedGenderFlag); + writeWord(trpoke, pokeOffs, difficulty | abilityAndFlag << 8); + writeWord(trpoke, pokeOffs + 2, tp.level); + writeWord(trpoke, pokeOffs + 4, tp.pokemon.number); + writeWord(trpoke, pokeOffs + 6, tp.forme); + // no form info, so no byte 6/7 + pokeOffs += 8; + if (tr.pokemonHaveItems()) { + writeWord(trpoke, pokeOffs, tp.heldItem); + pokeOffs += 2; + } + if (tr.pokemonHaveCustomMoves()) { + if (tp.resetMoves) { + int[] pokeMoves = RomFunctions.getMovesAtLevel(getAltFormeOfPokemon(tp.pokemon, tp.forme).number, movesets, tp.level); + for (int m = 0; m < 4; m++) { + writeWord(trpoke, pokeOffs + m * 2, pokeMoves[m]); + } + } else { + writeWord(trpoke, pokeOffs, tp.moves[0]); + writeWord(trpoke, pokeOffs + 2, tp.moves[1]); + writeWord(trpoke, pokeOffs + 4, tp.moves[2]); + writeWord(trpoke, pokeOffs + 6, tp.moves[3]); + } + pokeOffs += 8; + } + } + trpokes.files.add(trpoke); + } + this.writeNARC(romEntry.getFile("TrainerData"), trainers); + this.writeNARC(romEntry.getFile("TrainerPokemon"), trpokes); + + if (doubleBattleMode) { + + NARCArchive trainerTextBoxes = readNARC(romEntry.getFile("TrainerTextBoxes")); + byte[] data = trainerTextBoxes.files.get(0); + for (int i = 0; i < data.length; i += 4) { + int trainerIndex = readWord(data, i); + if (originalDoubleTrainers.contains(trainerIndex)) { + int textBoxIndex = readWord(data, i+2); + if (textBoxIndex == 3) { + writeWord(data, i+2, 0); + } else if (textBoxIndex == 5) { + writeWord(data, i+2, 2); + } else if (textBoxIndex == 6) { + writeWord(data, i+2, 0x18); + } + } + } + + trainerTextBoxes.files.set(0, data); + writeNARC(romEntry.getFile("TrainerTextBoxes"), trainerTextBoxes); + + + try { + byte[] fieldOverlay = readOverlay(romEntry.getInt("FieldOvlNumber")); + String trainerOverworldTextBoxPrefix = romEntry.getString("TrainerOverworldTextBoxPrefix"); + int offset = find(fieldOverlay, trainerOverworldTextBoxPrefix); + if (offset > 0) { + offset += trainerOverworldTextBoxPrefix.length() / 2; // because it was a prefix + // Overwrite text box values for trainer 1 in a doubles pair to use the same as a single trainer + fieldOverlay[offset-2] = 0; + fieldOverlay[offset] = 2; + fieldOverlay[offset+2] = 0x18; + } else { + throw new RandomizationException("Double Battle Mode not supported for this game"); + } + + String doubleBattleLimitPrefix = romEntry.getString("DoubleBattleLimitPrefix"); + offset = find(fieldOverlay, doubleBattleLimitPrefix); + if (offset > 0) { + offset += trainerOverworldTextBoxPrefix.length() / 2; // because it was a prefix + // No limit for doubles trainers, i.e. they will spot you even if you have a single Pokemon + writeWord(fieldOverlay, offset, 0x46C0); // nop + writeWord(fieldOverlay, offset+2, 0x46C0); // nop + } else { + throw new RandomizationException("Double Battle Mode not supported for this game"); + } + + String doubleBattleGetPointerPrefix = romEntry.getString("DoubleBattleGetPointerPrefix"); + int beqToSingleTrainer = romEntry.getInt("BeqToSingleTrainerNumber"); + offset = find(fieldOverlay, doubleBattleGetPointerPrefix); + if (offset > 0) { + offset += trainerOverworldTextBoxPrefix.length() / 2; // because it was a prefix + // Move some instructions up + writeWord(fieldOverlay, offset + 0x10, readWord(fieldOverlay, offset + 0xE)); + writeWord(fieldOverlay, offset + 0xE, readWord(fieldOverlay, offset + 0xC)); + writeWord(fieldOverlay, offset + 0xC, readWord(fieldOverlay, offset + 0xA)); + // Add a beq and cmp to go to the "single trainer" case if a certain pointer is 0 + writeWord(fieldOverlay, offset + 0xA, beqToSingleTrainer); + writeWord(fieldOverlay, offset + 8, 0x2800); + } else { + throw new RandomizationException("Double Battle Mode not supported for this game"); + } + + writeOverlay(romEntry.getInt("FieldOvlNumber"), fieldOverlay); + } catch (IOException e) { + e.printStackTrace(); + } + + String textBoxChoicePrefix = romEntry.getString("TextBoxChoicePrefix"); + int offset = find(arm9,textBoxChoicePrefix); + + if (offset > 0) { + // Change a branch destination in order to only check the relevant trainer instead of checking + // every trainer in the game (will result in incorrect text boxes when being spotted by doubles + // pairs, but this is better than the game freezing for half a second and getting a blank text box) + offset += textBoxChoicePrefix.length() / 2; + arm9[offset-4] = 2; + } else { + throw new RandomizationException("Double Battle Mode not supported for this game"); + } + + } + + // Deal with PWT + if (romEntry.romType == Gen5Constants.Type_BW2 && !romEntry.getFile("DriftveilPokemon").isEmpty()) { + NARCArchive driftveil = this.readNARC(romEntry.getFile("DriftveilPokemon")); + int currentFile = 1; + for (int trno = 0; trno < 17; trno++) { + Trainer tr = allTrainers.next(); + Iterator tpks = tr.pokemon.iterator(); + int pokemonNum = 6; + if (trno < 2) { + pokemonNum = 3; + } + for (int poke = 0; poke < pokemonNum; poke++) { + byte[] pkmndata = driftveil.files.get(currentFile); + TrainerPokemon tp = tpks.next(); + // pokemon and held item + writeWord(pkmndata, 0, tp.pokemon.number); + writeWord(pkmndata, 12, tp.heldItem); + // handle moves + if (tp.resetMoves) { + int[] pokeMoves = RomFunctions.getMovesAtLevel(tp.pokemon.number, movesets, tp.level); + for (int m = 0; m < 4; m++) { + writeWord(pkmndata, 2 + m * 2, pokeMoves[m]); + } + } else { + writeWord(pkmndata, 2, tp.moves[0]); + writeWord(pkmndata, 4, tp.moves[1]); + writeWord(pkmndata, 6, tp.moves[2]); + writeWord(pkmndata, 8, tp.moves[3]); + } + currentFile++; + } + } + this.writeNARC(romEntry.getFile("DriftveilPokemon"), driftveil); + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + } + + @Override + public Map> getMovesLearnt() { + Map> movesets = new TreeMap<>(); + try { + NARCArchive movesLearnt = this.readNARC(romEntry.getFile("PokemonMovesets")); + int formeCount = Gen5Constants.getFormeCount(romEntry.romType); + int formeOffset = Gen5Constants.getFormeOffset(romEntry.romType); + for (int i = 1; i <= Gen5Constants.pokemonCount + formeCount; i++) { + Pokemon pkmn = pokes[i]; + byte[] movedata; + if (i > Gen5Constants.pokemonCount) { + movedata = movesLearnt.files.get(i + formeOffset); + } else { + movedata = movesLearnt.files.get(i); + } + int moveDataLoc = 0; + List learnt = new ArrayList<>(); + while (readWord(movedata, moveDataLoc) != 0xFFFF || readWord(movedata, moveDataLoc + 2) != 0xFFFF) { + int move = readWord(movedata, moveDataLoc); + int level = readWord(movedata, moveDataLoc + 2); + MoveLearnt ml = new MoveLearnt(); + ml.level = level; + ml.move = move; + learnt.add(ml); + moveDataLoc += 4; + } + movesets.put(pkmn.number, learnt); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return movesets; + } + + @Override + public void setMovesLearnt(Map> movesets) { + try { + NARCArchive movesLearnt = readNARC(romEntry.getFile("PokemonMovesets")); + int formeCount = Gen5Constants.getFormeCount(romEntry.romType); + int formeOffset = Gen5Constants.getFormeOffset(romEntry.romType); + for (int i = 1; i <= Gen5Constants.pokemonCount + formeCount; i++) { + Pokemon pkmn = pokes[i]; + List learnt = movesets.get(pkmn.number); + int sizeNeeded = learnt.size() * 4 + 4; + byte[] moveset = new byte[sizeNeeded]; + int j = 0; + for (; j < learnt.size(); j++) { + MoveLearnt ml = learnt.get(j); + writeWord(moveset, j * 4, ml.move); + writeWord(moveset, j * 4 + 2, ml.level); + } + writeWord(moveset, j * 4, 0xFFFF); + writeWord(moveset, j * 4 + 2, 0xFFFF); + if (i > Gen5Constants.pokemonCount) { + movesLearnt.files.set(i + formeOffset, moveset); + } else { + movesLearnt.files.set(i, moveset); + } + } + // Save + this.writeNARC(romEntry.getFile("PokemonMovesets"), movesLearnt); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + @Override + public Map> getEggMoves() { + Map> eggMoves = new TreeMap<>(); + try { + NARCArchive eggMovesNarc = this.readNARC(romEntry.getFile("EggMoves")); + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + Pokemon pkmn = pokes[i]; + byte[] movedata = eggMovesNarc.files.get(i); + int numberOfEggMoves = readWord(movedata, 0); + List moves = new ArrayList<>(); + for (int j = 0; j < numberOfEggMoves; j++) { + int move = readWord(movedata, 2 + (j * 2)); + moves.add(move); + } + eggMoves.put(pkmn.number, moves); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return eggMoves; + } + + @Override + public void setEggMoves(Map> eggMoves) { + try { + NARCArchive eggMovesNarc = this.readNARC(romEntry.getFile("EggMoves")); + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + Pokemon pkmn = pokes[i]; + byte[] movedata = eggMovesNarc.files.get(i); + List moves = eggMoves.get(pkmn.number); + for (int j = 0; j < moves.size(); j++) { + writeWord(movedata, 2 + (j * 2), moves.get(j)); + } + } + // Save + this.writeNARC(romEntry.getFile("EggMoves"), eggMovesNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private static class FileEntry { + private int file; + private int offset; + + public FileEntry(int file, int offset) { + this.file = file; + this.offset = offset; + } + } + + private static class StaticPokemon { + private FileEntry[] speciesEntries; + private FileEntry[] formeEntries; + private FileEntry[] levelEntries; + + public StaticPokemon() { + this.speciesEntries = new FileEntry[0]; + this.formeEntries = new FileEntry[0]; + this.levelEntries = new FileEntry[0]; + } + + public Pokemon getPokemon(Gen5RomHandler parent, NARCArchive scriptNARC) { + return parent.pokes[parent.readWord(scriptNARC.files.get(speciesEntries[0].file), speciesEntries[0].offset)]; + } + + public void setPokemon(Gen5RomHandler parent, NARCArchive scriptNARC, Pokemon pkmn) { + int value = pkmn.number; + for (int i = 0; i < speciesEntries.length; i++) { + byte[] file = scriptNARC.files.get(speciesEntries[i].file); + parent.writeWord(file, speciesEntries[i].offset, value); + } + } + + public int getForme(NARCArchive scriptNARC) { + if (formeEntries.length == 0) { + return 0; + } + byte[] file = scriptNARC.files.get(formeEntries[0].file); + return file[formeEntries[0].offset]; + } + + public void setForme(NARCArchive scriptNARC, int forme) { + for (int i = 0; i < formeEntries.length; i++) { + byte[] file = scriptNARC.files.get(formeEntries[i].file); + file[formeEntries[i].offset] = (byte) forme; + } + } + + public int getLevelCount() { + return levelEntries.length; + } + + public int getLevel(NARCArchive scriptOrMapNARC, int i) { + if (levelEntries.length <= i) { + return 1; + } + byte[] file = scriptOrMapNARC.files.get(levelEntries[i].file); + return file[levelEntries[i].offset]; + } + + public void setLevel(NARCArchive scriptOrMapNARC, int level, int i) { + if (levelEntries.length > i) { // Might not have a level entry e.g., it's an egg + byte[] file = scriptOrMapNARC.files.get(levelEntries[i].file); + file[levelEntries[i].offset] = (byte) level; + } + } + } + + private static class RoamingPokemon { + private int[] speciesOverlayOffsets; + private int[] levelOverlayOffsets; + private FileEntry[] speciesScriptOffsets; + + public RoamingPokemon() { + this.speciesOverlayOffsets = new int[0]; + this.levelOverlayOffsets = new int[0]; + this.speciesScriptOffsets = new FileEntry[0]; + } + + public Pokemon getPokemon(Gen5RomHandler parent) throws IOException { + byte[] overlay = parent.readOverlay(parent.romEntry.getInt("RoamerOvlNumber")); + int species = parent.readWord(overlay, speciesOverlayOffsets[0]); + return parent.pokes[species]; + } + + public void setPokemon(Gen5RomHandler parent, NARCArchive scriptNARC, Pokemon pkmn) throws IOException { + int value = pkmn.number; + byte[] overlay = parent.readOverlay(parent.romEntry.getInt("RoamerOvlNumber")); + for (int speciesOverlayOffset : speciesOverlayOffsets) { + parent.writeWord(overlay, speciesOverlayOffset, value); + } + parent.writeOverlay(parent.romEntry.getInt("RoamerOvlNumber"), overlay); + for (FileEntry speciesScriptOffset : speciesScriptOffsets) { + byte[] file = scriptNARC.files.get(speciesScriptOffset.file); + parent.writeWord(file, speciesScriptOffset.offset, value); + } + } + + public int getLevel(Gen5RomHandler parent) throws IOException { + if (levelOverlayOffsets.length == 0) { + return 1; + } + byte[] overlay = parent.readOverlay(parent.romEntry.getInt("RoamerOvlNumber")); + return overlay[levelOverlayOffsets[0]]; + } + + public void setLevel(Gen5RomHandler parent, int level) throws IOException { + byte[] overlay = parent.readOverlay(parent.romEntry.getInt("RoamerOvlNumber")); + for (int levelOverlayOffset : levelOverlayOffsets) { + overlay[levelOverlayOffset] = (byte) level; + } + parent.writeOverlay(parent.romEntry.getInt("RoamerOvlNumber"), overlay); + } + } + + private static class TradeScript { + private int fileNum; + private int[] requestedOffsets; + private int[] givenOffsets; + + public void setPokemon(Gen5RomHandler parent, NARCArchive scriptNARC, Pokemon requested, Pokemon given) { + int req = requested.number; + int giv = given.number; + for (int i = 0; i < requestedOffsets.length; i++) { + byte[] file = scriptNARC.files.get(fileNum); + parent.writeWord(file, requestedOffsets[i], req); + parent.writeWord(file, givenOffsets[i], giv); + } + } + } + + @Override + public boolean canChangeStaticPokemon() { + return romEntry.staticPokemonSupport; + } + + @Override + public boolean hasStaticAltFormes() { + return false; + } + + @Override + public boolean hasMainGameLegendaries() { + return true; + } + + @Override + public List getMainGameLegendaries() { + return Arrays.stream(romEntry.arrayEntries.get("MainGameLegendaries")).boxed().collect(Collectors.toList()); + } + + @Override + public List getSpecialMusicStatics() { + return Arrays.stream(romEntry.arrayEntries.get("SpecialMusicStatics")).boxed().collect(Collectors.toList()); + } + + @Override + public void applyCorrectStaticMusic(Map specialMusicStaticChanges) { + + try { + byte[] fieldOverlay = readOverlay(romEntry.getInt("FieldOvlNumber")); + genericIPSPatch(fieldOverlay, "NewIndexToMusicOvlTweak"); + writeOverlay(romEntry.getInt("FieldOvlNumber"), fieldOverlay); + } catch (IOException e) { + e.printStackTrace(); + } + + // Relies on arm9 already being extended, which it *should* have been in loadedROM + genericIPSPatch(arm9, "NewIndexToMusicTweak"); + + String newIndexToMusicPrefix = romEntry.getString("NewIndexToMusicPrefix"); + int newIndexToMusicPoolOffset = find(arm9, newIndexToMusicPrefix); + newIndexToMusicPoolOffset += newIndexToMusicPrefix.length() / 2; + + List replaced = new ArrayList<>(); + int iMax = -1; + + switch(romEntry.romType) { + case Gen5Constants.Type_BW: + for (int oldStatic: specialMusicStaticChanges.keySet()) { + int i = newIndexToMusicPoolOffset; + int index = readWord(arm9, i); + while (index != oldStatic || replaced.contains(i)) { + i += 4; + index = readWord(arm9, i); + } + writeWord(arm9, i, specialMusicStaticChanges.get(oldStatic)); + replaced.add(i); + if (i > iMax) iMax = i; + } + break; + case Gen5Constants.Type_BW2: + for (int oldStatic: specialMusicStaticChanges.keySet()) { + int i = newIndexToMusicPoolOffset; + int index = readWord(arm9, i); + while (index != oldStatic || replaced.contains(i)) { + i += 4; + index = readWord(arm9, i); + } + // Special Kyurem-B/W handling + if (index > Gen5Constants.pokemonCount) { + writeWord(arm9, i - 0xFE, 0); + writeWord(arm9, i - 0xFC, 0); + writeWord(arm9, i - 0xFA, 0); + writeWord(arm9, i - 0xF8, 0x4290); + } + writeWord(arm9, i, specialMusicStaticChanges.get(oldStatic)); + replaced.add(i); + if (i > iMax) iMax = i; + } + break; + } + + List specialMusicStatics = getSpecialMusicStatics(); + + for (int i = newIndexToMusicPoolOffset; i <= iMax; i+= 4) { + if (!replaced.contains(i)) { + int pkID = readWord(arm9, i); + + // If a Pokemon is a "special music static" but the music hasn't been replaced, leave as is + // Otherwise zero it out, because the original static encounter doesn't exist + if (!specialMusicStatics.contains(pkID)) { + writeWord(arm9, i, 0); + } + } + } + + } + + @Override + public boolean hasStaticMusicFix() { + return romEntry.tweakFiles.get("NewIndexToMusicTweak") != null; + } + + @Override + public List getTotemPokemon() { + return new ArrayList<>(); + } + + @Override + public void setTotemPokemon(List totemPokemon) { + + } + + @Override + public List getStaticPokemon() { + List sp = new ArrayList<>(); + if (!romEntry.staticPokemonSupport) { + return sp; + } + int[] staticEggOffsets = new int[0]; + if (romEntry.arrayEntries.containsKey("StaticEggPokemonOffsets")) { + staticEggOffsets = romEntry.arrayEntries.get("StaticEggPokemonOffsets"); + } + + // Regular static encounters + NARCArchive scriptNARC = scriptNarc; + for (int i = 0; i < romEntry.staticPokemon.size(); i++) { + int currentOffset = i; + StaticPokemon statP = romEntry.staticPokemon.get(i); + StaticEncounter se = new StaticEncounter(); + Pokemon newPK = statP.getPokemon(this, scriptNARC); + newPK = getAltFormeOfPokemon(newPK, statP.getForme(scriptNARC)); + se.pkmn = newPK; + se.level = statP.getLevel(scriptNARC, 0); + se.isEgg = Arrays.stream(staticEggOffsets).anyMatch(x-> x == currentOffset); + for (int levelEntry = 1; levelEntry < statP.getLevelCount(); levelEntry++) { + StaticEncounter linkedStatic = new StaticEncounter(); + linkedStatic.pkmn = newPK; + linkedStatic.level = statP.getLevel(scriptNARC, levelEntry); + se.linkedEncounters.add(linkedStatic); + } + sp.add(se); + } + + // Foongus/Amoongus fake ball encounters + try { + NARCArchive mapNARC = readNARC(romEntry.getFile("MapFiles")); + for (int i = 0; i < romEntry.staticPokemonFakeBall.size(); i++) { + StaticPokemon statP = romEntry.staticPokemonFakeBall.get(i); + StaticEncounter se = new StaticEncounter(); + Pokemon newPK = statP.getPokemon(this, scriptNARC); + se.pkmn = newPK; + se.level = statP.getLevel(mapNARC, 0); + for (int levelEntry = 1; levelEntry < statP.getLevelCount(); levelEntry++) { + StaticEncounter linkedStatic = new StaticEncounter(); + linkedStatic.pkmn = newPK; + linkedStatic.level = statP.getLevel(mapNARC, levelEntry); + se.linkedEncounters.add(linkedStatic); + } + sp.add(se); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + // BW2 hidden grotto encounters + if (romEntry.romType == Gen5Constants.Type_BW2) { + List allowedHiddenHollowPokemon = new ArrayList<>(); + allowedHiddenHollowPokemon.addAll(Arrays.asList(Arrays.copyOfRange(pokes,1,494))); + allowedHiddenHollowPokemon.addAll( + Gen5Constants.bw2HiddenHollowUnovaPokemon.stream().map(i -> pokes[i]).collect(Collectors.toList())); + + try { + NARCArchive hhNARC = this.readNARC(romEntry.getFile("HiddenHollows")); + for (byte[] hhEntry : hhNARC.files) { + for (int version = 0; version < 2; version++) { + if (version != romEntry.getInt("HiddenHollowIndex")) continue; + for (int raritySlot = 0; raritySlot < 3; raritySlot++) { + List encountersInGroup = new ArrayList<>(); + for (int group = 0; group < 4; group++) { + StaticEncounter se = new StaticEncounter(); + Pokemon newPK = pokes[readWord(hhEntry, version * 78 + raritySlot * 26 + group * 2)]; + newPK = getAltFormeOfPokemon(newPK, hhEntry[version * 78 + raritySlot * 26 + 20 + group]); + se.pkmn = newPK; + se.level = hhEntry[version * 78 + raritySlot * 26 + 12 + group]; + se.maxLevel = hhEntry[version * 78 + raritySlot * 26 + 8 + group]; + se.isEgg = false; + se.restrictedPool = true; + se.restrictedList = allowedHiddenHollowPokemon; + boolean originalEncounter = true; + for (StaticEncounter encounterInGroup: encountersInGroup) { + if (encounterInGroup.pkmn.equals(se.pkmn)) { + encounterInGroup.linkedEncounters.add(se); + originalEncounter = false; + break; + } + } + if (originalEncounter) { + encountersInGroup.add(se); + sp.add(se); + if (!hiddenHollowCounted) { + hiddenHollowCount++; + } + } + } + } + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + hiddenHollowCounted = true; + + // Roaming encounters + if (romEntry.roamingPokemon.size() > 0) { + try { + int firstSpeciesOffset = romEntry.roamingPokemon.get(0).speciesOverlayOffsets[0]; + byte[] overlay = readOverlay(romEntry.getInt("RoamerOvlNumber")); + if (readWord(overlay, firstSpeciesOffset) > pokes.length) { + // In the original code, this is "mov r0, #0x2", which read as a word is + // 0x2002, much larger than the number of species in the game. + applyBlackWhiteRoamerPatch(); + } + for (int i = 0; i < romEntry.roamingPokemon.size(); i++) { + RoamingPokemon roamer = romEntry.roamingPokemon.get(i); + StaticEncounter se = new StaticEncounter(); + se.pkmn = roamer.getPokemon(this); + se.level = roamer.getLevel(this); + sp.add(se); + } + } catch (Exception e) { + throw new RandomizerIOException(e); + } + } + + return sp; + } + + @Override + public boolean setStaticPokemon(List staticPokemon) { + if (!romEntry.staticPokemonSupport) { + return false; + } + if (staticPokemon.size() != (romEntry.staticPokemon.size() + romEntry.staticPokemonFakeBall.size() + hiddenHollowCount + romEntry.roamingPokemon.size())) { + return false; + } + Iterator statics = staticPokemon.iterator(); + + // Regular static encounters + NARCArchive scriptNARC = scriptNarc; + for (StaticPokemon statP : romEntry.staticPokemon) { + StaticEncounter se = statics.next(); + statP.setPokemon(this, scriptNARC, se.pkmn); + statP.setForme(scriptNARC, se.pkmn.formeNumber); + statP.setLevel(scriptNARC, se.level, 0); + for (int i = 0; i < se.linkedEncounters.size(); i++) { + StaticEncounter linkedStatic = se.linkedEncounters.get(i); + statP.setLevel(scriptNARC, linkedStatic.level, i + 1); + } + } + + // Foongus/Amoongus fake ball encounters + try { + NARCArchive mapNARC = readNARC(romEntry.getFile("MapFiles")); + for (StaticPokemon statP : romEntry.staticPokemonFakeBall) { + StaticEncounter se = statics.next(); + statP.setPokemon(this, scriptNARC, se.pkmn); + statP.setLevel(mapNARC, se.level, 0); + for (int i = 0; i < se.linkedEncounters.size(); i++) { + StaticEncounter linkedStatic = se.linkedEncounters.get(i); + statP.setLevel(mapNARC, linkedStatic.level, i + 1); + } + } + this.writeNARC(romEntry.getFile("MapFiles"), mapNARC); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + // BW2 hidden grotto encounters + if (romEntry.romType == Gen5Constants.Type_BW2) { + try { + NARCArchive hhNARC = this.readNARC(romEntry.getFile("HiddenHollows")); + for (byte[] hhEntry : hhNARC.files) { + for (int version = 0; version < 2; version++) { + if (version != romEntry.getInt("HiddenHollowIndex")) continue; + for (int raritySlot = 0; raritySlot < 3; raritySlot++) { + for (int group = 0; group < 4; group++) { + StaticEncounter se = statics.next(); + writeWord(hhEntry, version * 78 + raritySlot * 26 + group * 2, se.pkmn.number); + int genderRatio = this.random.nextInt(101); + hhEntry[version * 78 + raritySlot * 26 + 16 + group] = (byte) genderRatio; + hhEntry[version * 78 + raritySlot * 26 + 20 + group] = (byte) se.forme; // forme + hhEntry[version * 78 + raritySlot * 26 + 12 + group] = (byte) se.level; + hhEntry[version * 78 + raritySlot * 26 + 8 + group] = (byte) se.maxLevel; + for (int i = 0; i < se.linkedEncounters.size(); i++) { + StaticEncounter linkedStatic = se.linkedEncounters.get(i); + group++; + writeWord(hhEntry, version * 78 + raritySlot * 26 + group * 2, linkedStatic.pkmn.number); + hhEntry[version * 78 + raritySlot * 26 + 16 + group] = (byte) genderRatio; + hhEntry[version * 78 + raritySlot * 26 + 20 + group] = (byte) linkedStatic.forme; // forme + hhEntry[version * 78 + raritySlot * 26 + 12 + group] = (byte) linkedStatic.level; + hhEntry[version * 78 + raritySlot * 26 + 8 + group] = (byte) linkedStatic.maxLevel; + } + } + } + } + } + this.writeNARC(romEntry.getFile("HiddenHollows"), hhNARC); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + // Roaming encounters + try { + for (int i = 0; i < romEntry.roamingPokemon.size(); i++) { + RoamingPokemon roamer = romEntry.roamingPokemon.get(i); + StaticEncounter roamerEncounter = statics.next(); + roamer.setPokemon(this, scriptNarc, roamerEncounter.pkmn); + roamer.setLevel(this, roamerEncounter.level); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + // In Black/White, the game has multiple hardcoded checks for Reshiram/Zekrom's species + // ID in order to properly move it out of a box and into the first slot of the player's + // party. We need to replace these checks with the species ID of whatever occupies + // Reshiram/Zekrom's static encounter for the game to still function properly. + if (romEntry.romType == Gen5Constants.Type_BW) { + int boxLegendaryIndex = romEntry.getInt("BoxLegendaryOffset"); + try { + int boxLegendarySpecies = staticPokemon.get(boxLegendaryIndex).pkmn.number; + fixBoxLegendaryBW1(boxLegendarySpecies); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + return true; + } + + private void fixBoxLegendaryBW1(int boxLegendarySpecies) throws IOException { + byte[] boxLegendaryOverlay = readOverlay(romEntry.getInt("FieldOvlNumber")); + if (romEntry.isBlack) { + // In Black, Reshiram's species ID is always retrieved via a pc-relative + // load to some constant. All we need to is replace these constants with + // the new species ID. + int firstConstantOffset = find(boxLegendaryOverlay, Gen5Constants.blackBoxLegendaryCheckPrefix1); + if (firstConstantOffset > 0) { + firstConstantOffset += Gen5Constants.blackBoxLegendaryCheckPrefix1.length() / 2; // because it was a prefix + FileFunctions.writeFullInt(boxLegendaryOverlay, firstConstantOffset, boxLegendarySpecies); + } + int secondConstantOffset = find(boxLegendaryOverlay, Gen5Constants.blackBoxLegendaryCheckPrefix2); + if (secondConstantOffset > 0) { + secondConstantOffset += Gen5Constants.blackBoxLegendaryCheckPrefix2.length() / 2; // because it was a prefix + FileFunctions.writeFullInt(boxLegendaryOverlay, secondConstantOffset, boxLegendarySpecies); + } + } else { + // In White, Zekrom's species ID is always loaded by loading 161 into a register + // and then shifting left by 2. Thus, we need to be more clever with how we + // modify code in order to set up some pc-relative loads. + int firstFunctionOffset = find(boxLegendaryOverlay, Gen5Constants.whiteBoxLegendaryCheckPrefix1); + if (firstFunctionOffset > 0) { + firstFunctionOffset += Gen5Constants.whiteBoxLegendaryCheckPrefix1.length() / 2; // because it was a prefix + + // First, nop the instruction that loads a pointer to the string + // "scrcmd_pokemon_fld.c" into a register; this has seemingly no + // effect on the game and was probably used strictly for debugging. + boxLegendaryOverlay[firstFunctionOffset + 66] = 0x00; + boxLegendaryOverlay[firstFunctionOffset + 67] = 0x00; + + // In the space that used to hold the address of the "scrcmd_pokemon_fld.c" + // string, we're going to instead store the species ID of the box legendary + // so that we can do a pc-relative load to it. + FileFunctions.writeFullInt(boxLegendaryOverlay, firstFunctionOffset + 320, boxLegendarySpecies); + + // Zekrom's species ID is originally loaded by doing a mov into r1 and then a shift + // on that same register four instructions later. This nops out the first instruction + // and replaces the left shift with a pc-relative load to the constant we stored above. + boxLegendaryOverlay[firstFunctionOffset + 18] = 0x00; + boxLegendaryOverlay[firstFunctionOffset + 19] = 0x00; + boxLegendaryOverlay[firstFunctionOffset + 26] = 0x49; + boxLegendaryOverlay[firstFunctionOffset + 27] = 0x49; + } + + int secondFunctionOffset = find(boxLegendaryOverlay, Gen5Constants.whiteBoxLegendaryCheckPrefix2); + if (secondFunctionOffset > 0) { + secondFunctionOffset += Gen5Constants.whiteBoxLegendaryCheckPrefix2.length() / 2; // because it was a prefix + + // A completely unrelated function below this one decides to pc-relative load 0x00000000 into r4 + // instead of just doing a mov. We can replace it with a simple "mov r4, #0x0", but we have to be + // careful about where we put it. The original code calls a function, performs an "add r6, r0, #0x0", + // then does the load into r4. This means that whether or not the Z bit is set depends on the result + // of the function call. If we naively replace the load with our mov, we'll be forcibly setting the Z + // bit to 1, which will cause the subsequent beq to potentially take us to the wrong place. To get + // around this, we reorder the code so the "mov r4, #0x0" occurs *before* the "add r6, r0, #0x0". + boxLegendaryOverlay[secondFunctionOffset + 502] = 0x00; + boxLegendaryOverlay[secondFunctionOffset + 503] = 0x24; + boxLegendaryOverlay[secondFunctionOffset + 504] = 0x06; + boxLegendaryOverlay[secondFunctionOffset + 505] = 0x1C; + + // Now replace the 0x00000000 constant with the species ID + FileFunctions.writeFullInt(boxLegendaryOverlay, secondFunctionOffset + 556, boxLegendarySpecies); + + // Lastly, replace the mov and lsl that originally puts Zekrom's species ID into r1 + // with a pc-relative of the above constant and a nop. + boxLegendaryOverlay[secondFunctionOffset + 78] = 0x77; + boxLegendaryOverlay[secondFunctionOffset + 79] = 0x49; + boxLegendaryOverlay[secondFunctionOffset + 80] = 0x00; + boxLegendaryOverlay[secondFunctionOffset + 81] = 0x00; + } + } + writeOverlay(romEntry.getInt("FieldOvlNumber"), boxLegendaryOverlay); + } + + private void applyBlackWhiteRoamerPatch() throws IOException { + int offset = romEntry.getInt("GetRoamerFlagOffsetStartOffset"); + byte[] overlay = readOverlay(romEntry.getInt("RoamerOvlNumber")); + + // This function returns 0 for Thundurus, 1 for Tornadus, and 2 for any other species. + // In testing, this 2 case is never used, so we can use the space for it to pc-relative + // load Thundurus's ID. The original code compares to Tornadus and Thundurus then does + // "bne #0xA" to the default case. Change it to "bne #0x4", which will just make this + // case immediately return. + overlay[offset + 10] = 0x00; + + // Now in the space that used to do "mov r0, #0x2" and return, write Thundurus's ID + FileFunctions.writeFullInt(overlay, offset + 20, Species.thundurus); + + // Lastly, instead of computing Thundurus's ID as TornadusID + 1, pc-relative load it + // from what we wrote earlier. + overlay[offset + 6] = 0x03; + overlay[offset + 7] = 0x49; + writeOverlay(romEntry.getInt("RoamerOvlNumber"), overlay); + } + + @Override + public int miscTweaksAvailable() { + int available = 0; + if (romEntry.tweakFiles.get("FastestTextTweak") != null) { + available |= MiscTweak.FASTEST_TEXT.getValue(); + } + available |= MiscTweak.BAN_LUCKY_EGG.getValue(); + available |= MiscTweak.NO_FREE_LUCKY_EGG.getValue(); + available |= MiscTweak.BAN_BIG_MANIAC_ITEMS.getValue(); + available |= MiscTweak.UPDATE_TYPE_EFFECTIVENESS.getValue(); + if (romEntry.romType == Gen5Constants.Type_BW) { + available |= MiscTweak.BALANCE_STATIC_LEVELS.getValue(); + } + if (romEntry.tweakFiles.get("NationalDexAtStartTweak") != null) { + available |= MiscTweak.NATIONAL_DEX_AT_START.getValue(); + } + available |= MiscTweak.RUN_WITHOUT_RUNNING_SHOES.getValue(); + if (romEntry.romType == Gen5Constants.Type_BW2) { + available |= MiscTweak.FORCE_CHALLENGE_MODE.getValue(); + } + available |= MiscTweak.DISABLE_LOW_HP_MUSIC.getValue(); + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestText(); + } else if (tweak == MiscTweak.BAN_LUCKY_EGG) { + allowedItems.banSingles(Items.luckyEgg); + nonBadItems.banSingles(Items.luckyEgg); + } else if (tweak == MiscTweak.NO_FREE_LUCKY_EGG) { + removeFreeLuckyEgg(); + } else if (tweak == MiscTweak.BAN_BIG_MANIAC_ITEMS) { + // BalmMushroom, Big Nugget, Pearl String, Comet Shard + allowedItems.banRange(Items.balmMushroom, 4); + nonBadItems.banRange(Items.balmMushroom, 4); + + // Relics + allowedItems.banRange(Items.relicVase, 4); + nonBadItems.banRange(Items.relicVase, 4); + + // Rare berries + allowedItems.banRange(Items.lansatBerry, 7); + nonBadItems.banRange(Items.lansatBerry, 7); + } else if (tweak == MiscTweak.BALANCE_STATIC_LEVELS) { + byte[] fossilFile = scriptNarc.files.get(Gen5Constants.fossilPokemonFile); + writeWord(fossilFile,Gen5Constants.fossilPokemonLevelOffset,20); + } else if (tweak == MiscTweak.NATIONAL_DEX_AT_START) { + patchForNationalDex(); + } else if (tweak == MiscTweak.RUN_WITHOUT_RUNNING_SHOES) { + applyRunWithoutRunningShoesPatch(); + } else if (tweak == MiscTweak.UPDATE_TYPE_EFFECTIVENESS) { + updateTypeEffectiveness(); + } else if (tweak == MiscTweak.FORCE_CHALLENGE_MODE) { + forceChallengeMode(); + } else if (tweak == MiscTweak.DISABLE_LOW_HP_MUSIC) { + disableLowHpMusic(); + } + } + + @Override + public boolean isEffectivenessUpdated() { + return effectivenessUpdated; + } + + // Removes the free lucky egg you receive from Professor Juniper and replaces it with a gooey mulch. + private void removeFreeLuckyEgg() { + int scriptFileGifts = romEntry.getInt("LuckyEggScriptOffset"); + int setVarGift = Gen5Constants.hiddenItemSetVarCommand; + int mulchIndex = this.random.nextInt(4); + + byte[] itemScripts = scriptNarc.files.get(scriptFileGifts); + int offset = 0; + int lookingForEggs = romEntry.romType == Gen5Constants.Type_BW ? 1 : 2; + while (lookingForEggs > 0) { + int part1 = readWord(itemScripts, offset); + if (part1 == Gen5Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(itemScripts, offset); + offset += 4; + if (offsetInFile > itemScripts.length) { + break; + } + while (true) { + offsetInFile++; + // Gift items are not necessarily word aligned, so need to read one byte at a time + int b = readByte(itemScripts, offsetInFile); + if (b == setVarGift) { + int command = readWord(itemScripts, offsetInFile); + int variable = readWord(itemScripts,offsetInFile + 2); + int item = readWord(itemScripts, offsetInFile + 4); + if (command == setVarGift && variable == Gen5Constants.hiddenItemVarSet && item == Items.luckyEgg) { + + writeWord(itemScripts, offsetInFile + 4, Gen5Constants.mulchIndices[mulchIndex]); + lookingForEggs--; + } + } + if (b == 0x2E) { // Beginning of a new block in the file + break; + } + } + } + } + + private void applyFastestText() { + genericIPSPatch(arm9, "FastestTextTweak"); + } + + private void patchForNationalDex() { + byte[] pokedexScript = scriptNarc.files.get(romEntry.getInt("NationalDexScriptOffset")); + + // Our patcher breaks if the output file is larger than the input file. In our case, we want + // to expand the script by four bytes to add an instruction to enable the national dex. Thus, + // the IPS patch was created with us adding four 0x00 bytes to the end of the script in mind. + byte[] expandedPokedexScript = new byte[pokedexScript.length + 4]; + System.arraycopy(pokedexScript, 0, expandedPokedexScript, 0, pokedexScript.length); + genericIPSPatch(expandedPokedexScript, "NationalDexAtStartTweak"); + scriptNarc.files.set(romEntry.getInt("NationalDexScriptOffset"), expandedPokedexScript); + } + + private void applyRunWithoutRunningShoesPatch() { + try { + // In the overlay that handles field movement, there's a very simple function + // that checks if the player has the Running Shoes by checking if flag 2403 is + // set on the save file. If it isn't, the code branches to a separate code path + // where the function returns 0. The below code simply nops this branch so that + // this function always returns 1, regardless of the status of flag 2403. + byte[] fieldOverlay = readOverlay(romEntry.getInt("FieldOvlNumber")); + String prefix = Gen5Constants.runningShoesPrefix; + int offset = find(fieldOverlay, prefix); + if (offset != 0) { + writeWord(fieldOverlay, offset, 0); + writeOverlay(romEntry.getInt("FieldOvlNumber"), fieldOverlay); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void updateTypeEffectiveness() { + try { + byte[] battleOverlay = readOverlay(romEntry.getInt("BattleOvlNumber")); + int typeEffectivenessTableOffset = find(battleOverlay, Gen5Constants.typeEffectivenessTableLocator); + if (typeEffectivenessTableOffset > 0) { + Effectiveness[][] typeEffectivenessTable = readTypeEffectivenessTable(battleOverlay, typeEffectivenessTableOffset); + log("--Updating Type Effectiveness--"); + int steel = Gen5Constants.typeToByte(Type.STEEL); + int dark = Gen5Constants.typeToByte(Type.DARK); + int ghost = Gen5Constants.typeToByte(Type.GHOST); + typeEffectivenessTable[ghost][steel] = Effectiveness.NEUTRAL; + log("Replaced: Ghost not very effective vs Steel => Ghost neutral vs Steel"); + typeEffectivenessTable[dark][steel] = Effectiveness.NEUTRAL; + log("Replaced: Dark not very effective vs Steel => Dark neutral vs Steel"); + logBlankLine(); + writeTypeEffectivenessTable(typeEffectivenessTable, battleOverlay, typeEffectivenessTableOffset); + writeOverlay(romEntry.getInt("BattleOvlNumber"), battleOverlay); + effectivenessUpdated = true; + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private Effectiveness[][] readTypeEffectivenessTable(byte[] battleOverlay, int typeEffectivenessTableOffset) { + Effectiveness[][] effectivenessTable = new Effectiveness[Type.DARK.ordinal() + 1][Type.DARK.ordinal() + 1]; + for (int attacker = Type.NORMAL.ordinal(); attacker <= Type.DARK.ordinal(); attacker++) { + for (int defender = Type.NORMAL.ordinal(); defender <= Type.DARK.ordinal(); defender++) { + int offset = typeEffectivenessTableOffset + (attacker * (Type.DARK.ordinal() + 1)) + defender; + int effectivenessInternal = battleOverlay[offset]; + Effectiveness effectiveness = null; + switch (effectivenessInternal) { + case 8: + effectiveness = Effectiveness.DOUBLE; + break; + case 4: + effectiveness = Effectiveness.NEUTRAL; + break; + case 2: + effectiveness = Effectiveness.HALF; + break; + case 0: + effectiveness = Effectiveness.ZERO; + break; + } + effectivenessTable[attacker][defender] = effectiveness; + } + } + return effectivenessTable; + } + + private void writeTypeEffectivenessTable(Effectiveness[][] typeEffectivenessTable, byte[] battleOverlay, + int typeEffectivenessTableOffset) { + for (int attacker = Type.NORMAL.ordinal(); attacker <= Type.DARK.ordinal(); attacker++) { + for (int defender = Type.NORMAL.ordinal(); defender <= Type.DARK.ordinal(); defender++) { + Effectiveness effectiveness = typeEffectivenessTable[attacker][defender]; + int offset = typeEffectivenessTableOffset + (attacker * (Type.DARK.ordinal() + 1)) + defender; + byte effectivenessInternal = 0; + switch (effectiveness) { + case DOUBLE: + effectivenessInternal = 8; + break; + case NEUTRAL: + effectivenessInternal = 4; + break; + case HALF: + effectivenessInternal = 2; + break; + case ZERO: + effectivenessInternal = 0; + break; + } + battleOverlay[offset] = effectivenessInternal; + } + } + } + + private void forceChallengeMode() { + int offset = find(arm9, Gen5Constants.forceChallengeModeLocator); + if (offset > 0) { + // offset is now pointing at the start of sub_2010528, which is the function that + // determines which difficulty the player currently has enabled. It returns 0 for + // Easy Mode, 1 for Normal Mode, and 2 for Challenge Mode. Since we're just trying + // to force Challenge Mode, all we need to do is: + // mov r0, #0x2 + // bx lr + arm9[offset] = 0x02; + arm9[offset + 1] = 0x20; + arm9[offset + 2] = 0x70; + arm9[offset + 3] = 0x47; + } + } + + private void disableLowHpMusic() { + try { + byte[] lowHealthMusicOverlay = readOverlay(romEntry.getInt("LowHealthMusicOvlNumber")); + int offset = find(lowHealthMusicOverlay, Gen5Constants.lowHealthMusicLocator); + if (offset > 0) { + // The game calls a function that returns 2 if the Pokemon has low HP. The ASM looks like this: + // bl funcThatReturns2IfThePokemonHasLowHp + // cmp r0, #0x2 + // bne pokemonDoesNotHaveLowHp + // mov r7, #0x1 + // The offset variable is currently pointing at the bne instruction. If we change that bne to an unconditional + // branch, the game will never think the player's Pokemon has low HP (for the purposes of changing the music). + lowHealthMusicOverlay[offset + 1] = (byte)0xE0; + writeOverlay(romEntry.getInt("LowHealthMusicOvlNumber"), lowHealthMusicOverlay); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public void enableGuaranteedPokemonCatching() { + try { + byte[] battleOverlay = readOverlay(romEntry.getInt("BattleOvlNumber")); + int offset = find(battleOverlay, Gen5Constants.perfectOddsBranchLocator); + if (offset > 0) { + // The game checks to see if your odds are greater then or equal to 255 using the following + // code. Note that they compare to 0xFF000 instead of 0xFF; it looks like all catching code + // probabilities are shifted like this? + // mov r0, #0xFF + // lsl r0, r0, #0xC + // cmp r7, r0 + // blt oddsLessThanOrEqualTo254 + // The below code just nops the branch out so it always acts like our odds are 255, and + // Pokemon are automatically caught no matter what. + battleOverlay[offset] = 0x00; + battleOverlay[offset + 1] = 0x00; + writeOverlay(romEntry.getInt("BattleOvlNumber"), battleOverlay); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private boolean genericIPSPatch(byte[] data, String ctName) { + String patchName = romEntry.tweakFiles.get(ctName); + if (patchName == null) { + return false; + } + + try { + FileFunctions.applyPatch(data, patchName); + return true; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getTMMoves() { + String tmDataPrefix = Gen5Constants.tmDataPrefix; + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += Gen5Constants.tmDataPrefix.length() / 2; // because it was + // a prefix + List tms = new ArrayList<>(); + for (int i = 0; i < Gen5Constants.tmBlockOneCount; i++) { + tms.add(readWord(arm9, offset + i * 2)); + } + // Skip past first 92 TMs and 6 HMs + offset += (Gen5Constants.tmBlockOneCount + Gen5Constants.hmCount) * 2; + for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { + tms.add(readWord(arm9, offset + i * 2)); + } + return tms; + } else { + return null; + } + } + + @Override + public List getHMMoves() { + String tmDataPrefix = Gen5Constants.tmDataPrefix; + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += Gen5Constants.tmDataPrefix.length() / 2; // because it was + // a prefix + offset += Gen5Constants.tmBlockOneCount * 2; // TM data + List hms = new ArrayList<>(); + for (int i = 0; i < Gen5Constants.hmCount; i++) { + hms.add(readWord(arm9, offset + i * 2)); + } + return hms; + } else { + return null; + } + } + + @Override + public void setTMMoves(List moveIndexes) { + String tmDataPrefix = Gen5Constants.tmDataPrefix; + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += Gen5Constants.tmDataPrefix.length() / 2; // because it was + // a prefix + for (int i = 0; i < Gen5Constants.tmBlockOneCount; i++) { + writeWord(arm9, offset + i * 2, moveIndexes.get(i)); + } + // Skip past those 92 TMs and 6 HMs + offset += (Gen5Constants.tmBlockOneCount + Gen5Constants.hmCount) * 2; + for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { + writeWord(arm9, offset + i * 2, moveIndexes.get(i + Gen5Constants.tmBlockOneCount)); + } + + // Update TM item descriptions + List itemDescriptions = getStrings(false, romEntry.getInt("ItemDescriptionsTextOffset")); + List moveDescriptions = getStrings(false, romEntry.getInt("MoveDescriptionsTextOffset")); + // TM01 is item 328 and so on + for (int i = 0; i < Gen5Constants.tmBlockOneCount; i++) { + itemDescriptions.set(i + Gen5Constants.tmBlockOneOffset, moveDescriptions.get(moveIndexes.get(i))); + } + // TM93-95 are 618-620 + for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { + itemDescriptions.set(i + Gen5Constants.tmBlockTwoOffset, + moveDescriptions.get(moveIndexes.get(i + Gen5Constants.tmBlockOneCount))); + } + // Save the new item descriptions + setStrings(false, romEntry.getInt("ItemDescriptionsTextOffset"), itemDescriptions); + // Palettes + String baseOfPalettes; + if (romEntry.romType == Gen5Constants.Type_BW) { + baseOfPalettes = Gen5Constants.bw1ItemPalettesPrefix; + } else { + baseOfPalettes = Gen5Constants.bw2ItemPalettesPrefix; + } + int offsPals = find(arm9, baseOfPalettes); + if (offsPals > 0) { + // Write pals + for (int i = 0; i < Gen5Constants.tmBlockOneCount; i++) { + int itmNum = Gen5Constants.tmBlockOneOffset + i; + Move m = this.moves[moveIndexes.get(i)]; + int pal = this.typeTMPaletteNumber(m.type); + writeWord(arm9, offsPals + itmNum * 4 + 2, pal); + } + for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { + int itmNum = Gen5Constants.tmBlockTwoOffset + i; + Move m = this.moves[moveIndexes.get(i + Gen5Constants.tmBlockOneCount)]; + int pal = this.typeTMPaletteNumber(m.type); + writeWord(arm9, offsPals + itmNum * 4 + 2, pal); + } + } + } + } + + private static RomFunctions.StringSizeDeterminer ssd = encodedText -> { + int offs = 0; + int len = encodedText.length(); + while (encodedText.indexOf("\\x", offs) != -1) { + len -= 5; + offs = encodedText.indexOf("\\x", offs) + 1; + } + return len; + }; + + @Override + public int getTMCount() { + return Gen5Constants.tmCount; + } + + @Override + public int getHMCount() { + return Gen5Constants.hmCount; + } + + @Override + public Map getTMHMCompatibility() { + Map compat = new TreeMap<>(); + int formeCount = Gen5Constants.getFormeCount(romEntry.romType); + int formeOffset = Gen5Constants.getFormeOffset(romEntry.romType); + for (int i = 1; i <= Gen5Constants.pokemonCount + formeCount; i++) { + byte[] data; + if (i > Gen5Constants.pokemonCount) { + data = pokeNarc.files.get(i + formeOffset); + } else { + data = pokeNarc.files.get(i); + } + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen5Constants.tmCount + Gen5Constants.hmCount + 1]; + for (int j = 0; j < 13; j++) { + readByteIntoFlags(data, flags, j * 8 + 1, Gen5Constants.bsTMHMCompatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map compatData) { + int formeOffset = Gen5Constants.getFormeOffset(romEntry.romType); + for (Map.Entry compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int number = pkmn.number; + if (number > Gen5Constants.pokemonCount) { + number += formeOffset; + } + byte[] data = pokeNarc.files.get(number); + for (int j = 0; j < 13; j++) { + data[Gen5Constants.bsTMHMCompatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public boolean hasMoveTutors() { + return romEntry.romType == Gen5Constants.Type_BW2; + } + + @Override + public List getMoveTutorMoves() { + if (!hasMoveTutors()) { + return new ArrayList<>(); + } + int baseOffset = romEntry.getInt("MoveTutorDataOffset"); + int amount = Gen5Constants.bw2MoveTutorCount; + int bytesPer = Gen5Constants.bw2MoveTutorBytesPerEntry; + List mtMoves = new ArrayList<>(); + try { + byte[] mtFile = readOverlay(romEntry.getInt("MoveTutorOvlNumber")); + for (int i = 0; i < amount; i++) { + mtMoves.add(readWord(mtFile, baseOffset + i * bytesPer)); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return mtMoves; + } + + @Override + public void setMoveTutorMoves(List moves) { + if (!hasMoveTutors()) { + return; + } + int baseOffset = romEntry.getInt("MoveTutorDataOffset"); + int amount = Gen5Constants.bw2MoveTutorCount; + int bytesPer = Gen5Constants.bw2MoveTutorBytesPerEntry; + if (moves.size() != amount) { + return; + } + try { + byte[] mtFile = readOverlay(romEntry.getInt("MoveTutorOvlNumber")); + for (int i = 0; i < amount; i++) { + writeWord(mtFile, baseOffset + i * bytesPer, moves.get(i)); + } + writeOverlay(romEntry.getInt("MoveTutorOvlNumber"), mtFile); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public Map getMoveTutorCompatibility() { + if (!hasMoveTutors()) { + return new TreeMap<>(); + } + Map compat = new TreeMap<>(); + int[] countsPersonalOrder = new int[] { 15, 17, 13, 15 }; + int[] countsMoveOrder = new int[] { 13, 15, 15, 17 }; + int[] personalToMoveOrder = new int[] { 1, 3, 0, 2 }; + int formeCount = Gen5Constants.getFormeCount(romEntry.romType); + int formeOffset = Gen5Constants.getFormeOffset(romEntry.romType); + for (int i = 1; i <= Gen5Constants.pokemonCount + formeCount; i++) { + byte[] data; + if (i > Gen5Constants.pokemonCount) { + data = pokeNarc.files.get(i + formeOffset); + } else { + data = pokeNarc.files.get(i); + } + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen5Constants.bw2MoveTutorCount + 1]; + for (int mt = 0; mt < 4; mt++) { + boolean[] mtflags = new boolean[countsPersonalOrder[mt] + 1]; + for (int j = 0; j < 4; j++) { + readByteIntoFlags(data, mtflags, j * 8 + 1, Gen5Constants.bsMTCompatOffset + mt * 4 + j); + } + int offsetOfThisData = 0; + for (int cmoIndex = 0; cmoIndex < personalToMoveOrder[mt]; cmoIndex++) { + offsetOfThisData += countsMoveOrder[cmoIndex]; + } + System.arraycopy(mtflags, 1, flags, offsetOfThisData + 1, countsPersonalOrder[mt]); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setMoveTutorCompatibility(Map compatData) { + if (!hasMoveTutors()) { + return; + } + int formeOffset = Gen5Constants.getFormeOffset(romEntry.romType); + // BW2 move tutor flags aren't using the same order as the move tutor + // move data. + // We unscramble them from move data order to personal.narc flag order. + int[] countsPersonalOrder = new int[] { 15, 17, 13, 15 }; + int[] countsMoveOrder = new int[] { 13, 15, 15, 17 }; + int[] personalToMoveOrder = new int[] { 1, 3, 0, 2 }; + for (Map.Entry compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int number = pkmn.number; + if (number > Gen5Constants.pokemonCount) { + number += formeOffset; + } + byte[] data = pokeNarc.files.get(number); + for (int mt = 0; mt < 4; mt++) { + int offsetOfThisData = 0; + for (int cmoIndex = 0; cmoIndex < personalToMoveOrder[mt]; cmoIndex++) { + offsetOfThisData += countsMoveOrder[cmoIndex]; + } + boolean[] mtflags = new boolean[countsPersonalOrder[mt] + 1]; + System.arraycopy(flags, offsetOfThisData + 1, mtflags, 1, countsPersonalOrder[mt]); + for (int j = 0; j < 4; j++) { + data[Gen5Constants.bsMTCompatOffset + mt * 4 + j] = getByteFromFlags(mtflags, j * 8 + 1); + } + } + } + } + + private int find(byte[] data, String hexString) { + if (hexString.length() % 2 != 0) { + return -3; // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + List found = RomFunctions.search(data, searchFor); + if (found.size() == 0) { + return -1; // not found + } else if (found.size() > 1) { + return -2; // not unique + } else { + return found.get(0); + } + } + + private List getStrings(boolean isStoryText, int index) { + NARCArchive baseNARC = isStoryText ? storyTextNarc : stringsNarc; + byte[] rawFile = baseNARC.files.get(index); + return new ArrayList<>(PPTxtHandler.readTexts(rawFile)); + } + + private void setStrings(boolean isStoryText, int index, List strings) { + NARCArchive baseNARC = isStoryText ? storyTextNarc : stringsNarc; + byte[] oldRawFile = baseNARC.files.get(index); + byte[] newRawFile = PPTxtHandler.saveEntry(oldRawFile, strings); + baseNARC.files.set(index, newRawFile); + } + + @Override + public String getROMName() { + return "Pokemon " + romEntry.name; + } + + @Override + public String getROMCode() { + return romEntry.romCode; + } + + @Override + public String getSupportLevel() { + return romEntry.staticPokemonSupport ? "Complete" : "No Static Pokemon"; + } + + @Override + public boolean hasTimeBasedEncounters() { + return true; // All BW/BW2 do [seasons] + } + + @Override + public boolean hasWildAltFormes() { + return true; + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + // Read NARC + try { + NARCArchive evoNARC = readNARC(romEntry.getFile("PokemonEvolutions")); + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + Pokemon pk = pokes[i]; + byte[] evoEntry = evoNARC.files.get(i); + for (int evo = 0; evo < 7; evo++) { + int method = readWord(evoEntry, evo * 6); + int species = readWord(evoEntry, evo * 6 + 4); + if (method >= 1 && method <= Gen5Constants.evolutionMethodCount && species >= 1) { + EvolutionType et = EvolutionType.fromIndex(5, method); + if (et.equals(EvolutionType.LEVEL_HIGH_BEAUTY)) continue; // Remove Feebas "split" evolution + int extraInfo = readWord(evoEntry, evo * 6 + 2); + Evolution evol = new Evolution(pk, pokes[species], true, et, extraInfo); + if (!pk.evolutionsFrom.contains(evol)) { + pk.evolutionsFrom.add(evol); + pokes[species].evolutionsTo.add(evol); + } + } + } + // Split evos shouldn't carry stats unless the evo is Nincada's + // In that case, we should have Ninjask carry stats + if (pk.evolutionsFrom.size() > 1) { + for (Evolution e : pk.evolutionsFrom) { + if (e.type != EvolutionType.LEVEL_CREATE_EXTRA) { + e.carryStats = false; + } + } + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void writeEvolutions() { + try { + NARCArchive evoNARC = readNARC(romEntry.getFile("PokemonEvolutions")); + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + byte[] evoEntry = evoNARC.files.get(i); + Pokemon pk = pokes[i]; + if (pk.number == Species.nincada && romEntry.tweakFiles.containsKey("ShedinjaEvolutionTweak")) { + writeShedinjaEvolution(); + } + int evosWritten = 0; + for (Evolution evo : pk.evolutionsFrom) { + writeWord(evoEntry, evosWritten * 6, evo.type.toIndex(5)); + writeWord(evoEntry, evosWritten * 6 + 2, evo.extraInfo); + writeWord(evoEntry, evosWritten * 6 + 4, evo.to.number); + evosWritten++; + if (evosWritten == 7) { + break; + } + } + while (evosWritten < 7) { + writeWord(evoEntry, evosWritten * 6, 0); + writeWord(evoEntry, evosWritten * 6 + 2, 0); + writeWord(evoEntry, evosWritten * 6 + 4, 0); + evosWritten++; + } + } + writeNARC(romEntry.getFile("PokemonEvolutions"), evoNARC); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void writeShedinjaEvolution() throws IOException { + Pokemon nincada = pokes[Species.nincada]; + + // When the "Limit Pokemon" setting is enabled and Gen 3 is disabled, or when + // "Random Every Level" evolutions are selected, we end up clearing out Nincada's + // vanilla evolutions. In that case, there's no point in even worrying about + // Shedinja, so just return. + if (nincada.evolutionsFrom.size() < 2) { + return; + } + + Pokemon extraEvolution = nincada.evolutionsFrom.get(1).to; + + // Update the evolution overlay to point towards our custom code in the expanded arm9. + byte[] evolutionOverlay = readOverlay(romEntry.getInt("EvolutionOvlNumber")); + genericIPSPatch(evolutionOverlay, "ShedinjaEvolutionOvlTweak"); + writeOverlay(romEntry.getInt("EvolutionOvlNumber"), evolutionOverlay); + + // Relies on arm9 already being extended, which it *should* have been in loadedROM + genericIPSPatch(arm9, "ShedinjaEvolutionTweak"); + + // After applying the tweak, Shedinja's ID is simply pc-relative loaded, so just + // update the constant + int offset = romEntry.getInt("ShedinjaSpeciesOffset"); + if (offset > 0) { + FileFunctions.writeFullInt(arm9, offset, extraEvolution.number); + } + } + + @Override + public void removeImpossibleEvolutions(Settings settings) { + boolean changeMoveEvos = !(settings.getMovesetsMod() == Settings.MovesetsMod.UNCHANGED); + + Map> movesets = this.getMovesLearnt(); + Set extraEvolutions = new HashSet<>(); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + extraEvolutions.clear(); + for (Evolution evo : pkmn.evolutionsFrom) { + if (changeMoveEvos && evo.type == EvolutionType.LEVEL_WITH_MOVE) { + // read move + int move = evo.extraInfo; + int levelLearntAt = 1; + for (MoveLearnt ml : movesets.get(evo.from.number)) { + if (ml.move == move) { + levelLearntAt = ml.level; + break; + } + } + if (levelLearntAt == 1) { + // override for piloswine + levelLearntAt = 45; + } + // change to pure level evo + evo.type = EvolutionType.LEVEL; + evo.extraInfo = levelLearntAt; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // Pure Trade + if (evo.type == EvolutionType.TRADE) { + // Replace w/ level 37 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // Trade w/ Item + if (evo.type == EvolutionType.TRADE_ITEM) { + // Get the current item & evolution + int item = evo.extraInfo; + if (evo.from.number == Species.slowpoke) { + // Slowpoke is awkward - he already has a level evo + // So we can't do Level up w/ Held Item for him + // Put Water Stone instead + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.waterStone; + addEvoUpdateStone(impossibleEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + addEvoUpdateHeldItem(impossibleEvolutionUpdates, evo, itemNames.get(item)); + // Replace, for this entry, w/ + // Level up w/ Held Item at Day + evo.type = EvolutionType.LEVEL_ITEM_DAY; + // now add an extra evo for + // Level up w/ Held Item at Night + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_NIGHT, item); + extraEvolutions.add(extraEntry); + } + } + if (evo.type == EvolutionType.TRADE_SPECIAL) { + // This is the karrablast <-> shelmet trade + // Replace it with Level up w/ Other Species in Party + // (22) + // Based on what species we're currently dealing with + evo.type = EvolutionType.LEVEL_WITH_OTHER; + evo.extraInfo = (evo.from.number == Species.karrablast ? Species.shelmet : Species.karrablast); + addEvoUpdateParty(impossibleEvolutionUpdates, evo, pokes[evo.extraInfo].fullName()); + } + } + + pkmn.evolutionsFrom.addAll(extraEvolutions); + for (Evolution ev : extraEvolutions) { + ev.to.evolutionsTo.add(ev); + } + } + } + + } + + @Override + public void makeEvolutionsEasier(Settings settings) { + boolean wildsRandomized = !settings.getWildPokemonMod().equals(Settings.WildPokemonMod.UNCHANGED); + + // Reduce the amount of happiness required to evolve. + int offset = find(arm9, Gen5Constants.friendshipValueForEvoLocator); + if (offset > 0) { + // Amount of required happiness for HAPPINESS evolutions. + if (arm9[offset] == (byte)220) { + arm9[offset] = (byte)160; + } + // Amount of required happiness for HAPPINESS_DAY evolutions. + if (arm9[offset + 20] == (byte)220) { + arm9[offset + 20] = (byte)160; + } + // Amount of required happiness for HAPPINESS_NIGHT evolutions. + if (arm9[offset + 38] == (byte)220) { + arm9[offset + 38] = (byte)160; + } + } + + if (wildsRandomized) { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + for (Evolution evo : pkmn.evolutionsFrom) { + if (evo.type == EvolutionType.LEVEL_WITH_OTHER) { + // Replace w/ level 35 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 35; + addEvoUpdateCondensed(easierEvolutionUpdates, evo, false); + } + } + } + } + } + } + + @Override + public void removeTimeBasedEvolutions() { + Set extraEvolutions = new HashSet<>(); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + extraEvolutions.clear(); + for (Evolution evo : pkmn.evolutionsFrom) { + if (evo.type == EvolutionType.HAPPINESS_DAY) { + if (evo.from.number == Species.eevee) { + // We can't set Eevee to evolve into Espeon with happiness at night because that's how + // Umbreon works in the original game. Instead, make Eevee: == sun stone => Espeon + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.sunStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + // Add an extra evo for Happiness at Night + addEvoUpdateHappiness(timeBasedEvolutionUpdates, evo); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.HAPPINESS_NIGHT, 0); + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.HAPPINESS_NIGHT) { + if (evo.from.number == Species.eevee) { + // We can't set Eevee to evolve into Umbreon with happiness at day because that's how + // Espeon works in the original game. Instead, make Eevee: == moon stone => Umbreon + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.moonStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + // Add an extra evo for Happiness at Day + addEvoUpdateHappiness(timeBasedEvolutionUpdates, evo); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.HAPPINESS_DAY, 0); + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.LEVEL_ITEM_DAY) { + int item = evo.extraInfo; + // Make sure we don't already have an evo for the same item at night (e.g., when using Change Impossible Evos) + if (evo.from.evolutionsFrom.stream().noneMatch(e -> e.type == EvolutionType.LEVEL_ITEM_NIGHT && e.extraInfo == item)) { + // Add an extra evo for Level w/ Item During Night + addEvoUpdateHeldItem(timeBasedEvolutionUpdates, evo, itemNames.get(item)); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_NIGHT, item); + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.LEVEL_ITEM_NIGHT) { + int item = evo.extraInfo; + // Make sure we don't already have an evo for the same item at day (e.g., when using Change Impossible Evos) + if (evo.from.evolutionsFrom.stream().noneMatch(e -> e.type == EvolutionType.LEVEL_ITEM_DAY && e.extraInfo == item)) { + // Add an extra evo for Level w/ Item During Day + addEvoUpdateHeldItem(timeBasedEvolutionUpdates, evo, itemNames.get(item)); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_DAY, item); + extraEvolutions.add(extraEntry); + } + } + } + pkmn.evolutionsFrom.addAll(extraEvolutions); + for (Evolution ev : extraEvolutions) { + ev.to.evolutionsTo.add(ev); + } + } + } + + } + + @Override + public boolean hasShopRandomization() { + return true; + } + + @Override + public boolean canChangeTrainerText() { + return true; + } + + @Override + public List getTrainerNames() { + List tnames = getStrings(false, romEntry.getInt("TrainerNamesTextOffset")); + tnames.remove(0); // blank one + if (romEntry.romType == Gen5Constants.Type_BW2) { + List pwtNames = getStrings(false, romEntry.getInt("PWTTrainerNamesTextOffset")); + tnames.addAll(pwtNames); + } + // Tack the mugshot names on the end + List mnames = getStrings(false, romEntry.getInt("TrainerMugshotsTextOffset")); + for (String mname : mnames) { + if (!mname.isEmpty() && (mname.charAt(0) >= 'A' && mname.charAt(0) <= 'Z')) { + tnames.add(mname); + } + } + return tnames; + } + + @Override + public int maxTrainerNameLength() { + return 10;// based off the english ROMs + } + + @Override + public void setTrainerNames(List trainerNames) { + List tnames = getStrings(false, romEntry.getInt("TrainerNamesTextOffset")); + // Grab the mugshot names off the back of the list of trainer names + // we got back + List mnames = getStrings(false, romEntry.getInt("TrainerMugshotsTextOffset")); + int trNamesSize = trainerNames.size(); + for (int i = mnames.size() - 1; i >= 0; i--) { + String origMName = mnames.get(i); + if (!origMName.isEmpty() && (origMName.charAt(0) >= 'A' && origMName.charAt(0) <= 'Z')) { + // Grab replacement + String replacement = trainerNames.remove(--trNamesSize); + mnames.set(i, replacement); + } + } + // Save back mugshot names + setStrings(false, romEntry.getInt("TrainerMugshotsTextOffset"), mnames); + + // Now save the rest of trainer names + if (romEntry.romType == Gen5Constants.Type_BW2) { + List pwtNames = getStrings(false, romEntry.getInt("PWTTrainerNamesTextOffset")); + List newTNames = new ArrayList<>(); + List newPWTNames = new ArrayList<>(); + newTNames.add(0, tnames.get(0)); // the 0-entry, preserve it + for (int i = 1; i < tnames.size() + pwtNames.size(); i++) { + if (i < tnames.size()) { + newTNames.add(trainerNames.get(i - 1)); + } else { + newPWTNames.add(trainerNames.get(i - 1)); + } + } + setStrings(false, romEntry.getInt("TrainerNamesTextOffset"), newTNames); + setStrings(false, romEntry.getInt("PWTTrainerNamesTextOffset"), newPWTNames); + } else { + List newTNames = new ArrayList<>(trainerNames); + newTNames.add(0, tnames.get(0)); // the 0-entry, preserve it + setStrings(false, romEntry.getInt("TrainerNamesTextOffset"), newTNames); + } + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.MAX_LENGTH; + } + + @Override + public List getTCNameLengthsByTrainer() { + // not needed + return new ArrayList<>(); + } + + @Override + public List getTrainerClassNames() { + List classNames = getStrings(false, romEntry.getInt("TrainerClassesTextOffset")); + if (romEntry.romType == Gen5Constants.Type_BW2) { + classNames.addAll(getStrings(false, romEntry.getInt("PWTTrainerClassesTextOffset"))); + } + return classNames; + } + + @Override + public void setTrainerClassNames(List trainerClassNames) { + if (romEntry.romType == Gen5Constants.Type_BW2) { + List newTClasses = new ArrayList<>(); + List newPWTClasses = new ArrayList<>(); + List classNames = getStrings(false, romEntry.getInt("TrainerClassesTextOffset")); + List pwtClassNames = getStrings(false, romEntry.getInt("PWTTrainerClassesTextOffset")); + for (int i = 0; i < classNames.size() + pwtClassNames.size(); i++) { + if (i < classNames.size()) { + newTClasses.add(trainerClassNames.get(i)); + } else { + newPWTClasses.add(trainerClassNames.get(i)); + } + } + setStrings(false, romEntry.getInt("TrainerClassesTextOffset"), newTClasses); + setStrings(false, romEntry.getInt("PWTTrainerClassesTextOffset"), newPWTClasses); + } else { + setStrings(false, romEntry.getInt("TrainerClassesTextOffset"), trainerClassNames); + } + } + + @Override + public int maxTrainerClassNameLength() { + return 12;// based off the english ROMs + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return false; + } + + @Override + public List getDoublesTrainerClasses() { + int[] doublesClasses = romEntry.arrayEntries.get("DoublesTrainerClasses"); + List doubles = new ArrayList<>(); + for (int tClass : doublesClasses) { + doubles.add(tClass); + } + return doubles; + } + + @Override + public String getDefaultExtension() { + return "nds"; + } + + @Override + public int abilitiesPerPokemon() { + return 3; + } + + @Override + public int highestAbilityIndex() { + return Gen5Constants.highestAbilityIndex; + } + + @Override + public int internalStringLength(String string) { + return ssd.lengthFor(string); + } + + @Override + public void randomizeIntroPokemon() { + try { + int introPokemon = randomPokemon().number; + byte[] introGraphicOverlay = readOverlay(romEntry.getInt("IntroGraphicOvlNumber")); + int offset = find(introGraphicOverlay, Gen5Constants.introGraphicPrefix); + if (offset > 0) { + offset += Gen5Constants.introGraphicPrefix.length() / 2; // because it was a prefix + // offset is now pointing at the species constant that gets pc-relative + // loaded to determine what sprite to load. + writeWord(introGraphicOverlay, offset, introPokemon); + writeOverlay(romEntry.getInt("IntroGraphicOvlNumber"), introGraphicOverlay); + } + + if (romEntry.romType == Gen5Constants.Type_BW) { + byte[] introCryOverlay = readOverlay(romEntry.getInt("IntroCryOvlNumber")); + offset = find(introCryOverlay, Gen5Constants.bw1IntroCryPrefix); + if (offset > 0) { + offset += Gen5Constants.bw1IntroCryPrefix.length() / 2; // because it was a prefix + // The function starting from the offset looks like this: + // mov r0, #0x8f + // str r1, [sp, #local_94] + // lsl r0, r0, #0x2 + // mov r2, #0x40 + // mov r3, #0x0 + // bl PlayCry + // [rest of the function...] + // pop { r3, r4, r5, r6, r7, pc } + // C0 46 (these are useless padding bytes) + // To make this more extensible, we want to pc-relative load a species ID into r0 instead. + // Start by moving everything below the left shift up by 2 bytes. We won't need the left + // shift later, and it will give us 4 bytes after the pop to use for the ID. + for (int i = offset + 6; i < offset + 40; i++) { + introCryOverlay[i - 2] = introCryOverlay[i]; + } + + // The call to PlayCry needs to be adjusted as well, since it got moved. + introCryOverlay[offset + 10]++; + + // Now write the species ID in the 4 bytes of space now available at the bottom, + // and then write a pc-relative load to this species ID at the offset. + FileFunctions.writeFullInt(introCryOverlay, offset + 38, introPokemon); + introCryOverlay[offset] = 0x9; + introCryOverlay[offset + 1] = 0x48; + writeOverlay(romEntry.getInt("IntroCryOvlNumber"), introCryOverlay); + } + } else { + byte[] introCryOverlay = readOverlay(romEntry.getInt("IntroCryOvlNumber")); + offset = find(introCryOverlay, Gen5Constants.bw2IntroCryLocator); + if (offset > 0) { + // offset is now pointing at the species constant that gets pc-relative + // loaded to determine what cry to play. + writeWord(introCryOverlay, offset, introPokemon); + writeOverlay(romEntry.getInt("IntroCryOvlNumber"), introCryOverlay); + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public ItemList getAllowedItems() { + return allowedItems; + } + + @Override + public ItemList getNonBadItems() { + return nonBadItems; + } + + @Override + public List getUniqueNoSellItems() { + return new ArrayList<>(); + } + + @Override + public List getRegularShopItems() { + return regularShopItems; + } + + @Override + public List getOPShopItems() { + return opShopItems; + } + + + @Override + public String[] getItemNames() { + return itemNames.toArray(new String[0]); + } + + @Override + public String abilityName(int number) { + return abilityNames.get(number); + } + + @Override + public Map> getAbilityVariations() { + return Gen5Constants.abilityVariations; + } + + @Override + public List getUselessAbilities() { + return new ArrayList<>(Gen5Constants.uselessAbilities); + } + + @Override + public int getAbilityForTrainerPokemon(TrainerPokemon tp) { + // Before randomizing Trainer Pokemon, one possible value for abilitySlot is 0, + // which represents "Either Ability 1 or 2". During randomization, we make sure to + // to set abilitySlot to some non-zero value, but if you call this method without + // randomization, then you'll hit this case. + if (tp.abilitySlot < 1 || tp.abilitySlot > 3) { + return 0; + } + + // In Gen 5, alt formes for Trainer Pokemon use the base forme's ability + Pokemon pkmn = tp.pokemon; + while (pkmn.baseForme != null) { + pkmn = pkmn.baseForme; + } + + List abilityList = Arrays.asList(pkmn.ability1, pkmn.ability2, pkmn.ability3); + return abilityList.get(tp.abilitySlot - 1); + } + + @Override + public boolean hasMegaEvolutions() { + return false; + } + + private List getFieldItems() { + List fieldItems = new ArrayList<>(); + // normal items + int scriptFileNormal = romEntry.getInt("ItemBallsScriptOffset"); + int scriptFileHidden = romEntry.getInt("HiddenItemsScriptOffset"); + int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); + int[] skipTableH = romEntry.arrayEntries.get("HiddenItemsSkip"); + int setVarNormal = Gen5Constants.normalItemSetVarCommand; + int setVarHidden = Gen5Constants.hiddenItemSetVarCommand; + + byte[] itemScripts = scriptNarc.files.get(scriptFileNormal); + int offset = 0; + int skipTableOffset = 0; + while (true) { + int part1 = readWord(itemScripts, offset); + if (part1 == Gen5Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(itemScripts, offset); + offset += 4; + if (offsetInFile > itemScripts.length) { + break; + } + if (skipTableOffset < skipTable.length && (skipTable[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(itemScripts, offsetInFile + 2); + int variable = readWord(itemScripts, offsetInFile + 4); + if (command == setVarNormal && variable == Gen5Constants.normalItemVarSet) { + int item = readWord(itemScripts, offsetInFile + 6); + fieldItems.add(item); + } + + } + + // hidden items + byte[] hitemScripts = scriptNarc.files.get(scriptFileHidden); + offset = 0; + skipTableOffset = 0; + while (true) { + int part1 = readWord(hitemScripts, offset); + if (part1 == Gen5Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(hitemScripts, offset); + if (offsetInFile > hitemScripts.length) { + break; + } + offset += 4; + if (skipTableOffset < skipTable.length && (skipTableH[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(hitemScripts, offsetInFile + 2); + int variable = readWord(hitemScripts, offsetInFile + 4); + if (command == setVarHidden && variable == Gen5Constants.hiddenItemVarSet) { + int item = readWord(hitemScripts, offsetInFile + 6); + fieldItems.add(item); + } + + } + + return fieldItems; + } + + private void setFieldItems(List fieldItems) { + Iterator iterItems = fieldItems.iterator(); + + // normal items + int scriptFileNormal = romEntry.getInt("ItemBallsScriptOffset"); + int scriptFileHidden = romEntry.getInt("HiddenItemsScriptOffset"); + int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); + int[] skipTableH = romEntry.arrayEntries.get("HiddenItemsSkip"); + int setVarNormal = Gen5Constants.normalItemSetVarCommand; + int setVarHidden = Gen5Constants.hiddenItemSetVarCommand; + + byte[] itemScripts = scriptNarc.files.get(scriptFileNormal); + int offset = 0; + int skipTableOffset = 0; + while (true) { + int part1 = readWord(itemScripts, offset); + if (part1 == Gen5Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(itemScripts, offset); + offset += 4; + if (offsetInFile > itemScripts.length) { + break; + } + if (skipTableOffset < skipTable.length && (skipTable[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(itemScripts, offsetInFile + 2); + int variable = readWord(itemScripts, offsetInFile + 4); + if (command == setVarNormal && variable == Gen5Constants.normalItemVarSet) { + int item = iterItems.next(); + writeWord(itemScripts, offsetInFile + 6, item); + } + + } + + // hidden items + byte[] hitemScripts = scriptNarc.files.get(scriptFileHidden); + offset = 0; + skipTableOffset = 0; + while (true) { + int part1 = readWord(hitemScripts, offset); + if (part1 == Gen5Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(hitemScripts, offset); + offset += 4; + if (offsetInFile > hitemScripts.length) { + break; + } + if (skipTableOffset < skipTable.length && (skipTableH[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(hitemScripts, offsetInFile + 2); + int variable = readWord(hitemScripts, offsetInFile + 4); + if (command == setVarHidden && variable == Gen5Constants.hiddenItemVarSet) { + int item = iterItems.next(); + writeWord(hitemScripts, offsetInFile + 6, item); + } + + } + } + + private int tmFromIndex(int index) { + if (index >= Gen5Constants.tmBlockOneOffset + && index < Gen5Constants.tmBlockOneOffset + Gen5Constants.tmBlockOneCount) { + return index - (Gen5Constants.tmBlockOneOffset - 1); + } else { + return (index + Gen5Constants.tmBlockOneCount) - (Gen5Constants.tmBlockTwoOffset - 1); + } + } + + private int indexFromTM(int tm) { + if (tm >= 1 && tm <= Gen5Constants.tmBlockOneCount) { + return tm + (Gen5Constants.tmBlockOneOffset - 1); + } else { + return tm + (Gen5Constants.tmBlockTwoOffset - 1 - Gen5Constants.tmBlockOneCount); + } + } + + @Override + public List getCurrentFieldTMs() { + List fieldItems = this.getFieldItems(); + List fieldTMs = new ArrayList<>(); + + for (int item : fieldItems) { + if (Gen5Constants.allowedItems.isTM(item)) { + fieldTMs.add(tmFromIndex(item)); + } + } + + return fieldTMs; + } + + @Override + public void setFieldTMs(List fieldTMs) { + List fieldItems = this.getFieldItems(); + int fiLength = fieldItems.size(); + Iterator iterTMs = fieldTMs.iterator(); + + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (Gen5Constants.allowedItems.isTM(oldItem)) { + int newItem = indexFromTM(iterTMs.next()); + fieldItems.set(i, newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List getRegularFieldItems() { + List fieldItems = this.getFieldItems(); + List fieldRegItems = new ArrayList<>(); + + for (int item : fieldItems) { + if (Gen5Constants.allowedItems.isAllowed(item) && !(Gen5Constants.allowedItems.isTM(item))) { + fieldRegItems.add(item); + } + } + + return fieldRegItems; + } + + @Override + public void setRegularFieldItems(List items) { + List fieldItems = this.getFieldItems(); + int fiLength = fieldItems.size(); + Iterator iterNewItems = items.iterator(); + + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (!(Gen5Constants.allowedItems.isTM(oldItem)) && Gen5Constants.allowedItems.isAllowed(oldItem)) { + int newItem = iterNewItems.next(); + fieldItems.set(i, newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List getRequiredFieldTMs() { + if (romEntry.romType == Gen5Constants.Type_BW) { + return Gen5Constants.bw1RequiredFieldTMs; + } else { + return Gen5Constants.bw2RequiredFieldTMs; + } + } + + @Override + public List getIngameTrades() { + List trades = new ArrayList<>(); + try { + NARCArchive tradeNARC = this.readNARC(romEntry.getFile("InGameTrades")); + List tradeStrings = getStrings(false, romEntry.getInt("IngameTradesTextOffset")); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int tableSize = tradeNARC.files.size(); + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = new IngameTrade(); + byte[] tfile = tradeNARC.files.get(entry); + trade.nickname = tradeStrings.get(entry * 2); + trade.givenPokemon = pokes[readLong(tfile, 4)]; + trade.ivs = new int[6]; + for (int iv = 0; iv < 6; iv++) { + trade.ivs[iv] = readLong(tfile, 0x10 + iv * 4); + } + trade.otId = readWord(tfile, 0x34); + trade.item = readLong(tfile, 0x4C); + trade.otName = tradeStrings.get(entry * 2 + 1); + trade.requestedPokemon = pokes[readLong(tfile, 0x5C)]; + trades.add(trade); + } + } catch (Exception ex) { + throw new RandomizerIOException(ex); + } + + return trades; + + } + + @Override + public void setIngameTrades(List trades) { + // info + int tradeOffset = 0; + List oldTrades = this.getIngameTrades(); + try { + NARCArchive tradeNARC = this.readNARC(romEntry.getFile("InGameTrades")); + List tradeStrings = getStrings(false, romEntry.getInt("IngameTradesTextOffset")); + int tradeCount = tradeNARC.files.size(); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + for (int i = 0; i < tradeCount; i++) { + if (unusedOffset < unused.length && unused[unusedOffset] == i) { + unusedOffset++; + continue; + } + byte[] tfile = tradeNARC.files.get(i); + IngameTrade trade = trades.get(tradeOffset++); + tradeStrings.set(i * 2, trade.nickname); + tradeStrings.set(i * 2 + 1, trade.otName); + writeLong(tfile, 4, trade.givenPokemon.number); + writeLong(tfile, 8, 0); // disable forme + for (int iv = 0; iv < 6; iv++) { + writeLong(tfile, 0x10 + iv * 4, trade.ivs[iv]); + } + writeLong(tfile, 0x2C, 0xFF); // random nature + writeWord(tfile, 0x34, trade.otId); + writeLong(tfile, 0x4C, trade.item); + writeLong(tfile, 0x5C, trade.requestedPokemon.number); + if (romEntry.tradeScripts.size() > 0) { + romEntry.tradeScripts.get(i - unusedOffset).setPokemon(this,scriptNarc,trade.requestedPokemon,trade.givenPokemon); + } + } + this.writeNARC(romEntry.getFile("InGameTrades"), tradeNARC); + this.setStrings(false, romEntry.getInt("IngameTradesTextOffset"), tradeStrings); + // update what the people say when they talk to you + unusedOffset = 0; + if (romEntry.arrayEntries.containsKey("IngameTradePersonTextOffsets")) { + int[] textOffsets = romEntry.arrayEntries.get("IngameTradePersonTextOffsets"); + for (int tr = 0; tr < textOffsets.length; tr++) { + if (unusedOffset < unused.length && unused[unusedOffset] == tr+24) { + unusedOffset++; + continue; + } + if (textOffsets[tr] > 0) { + if (tr+24 >= oldTrades.size() || tr+24 >= trades.size()) { + break; + } + IngameTrade oldTrade = oldTrades.get(tr+24); + IngameTrade newTrade = trades.get(tr+24); + Map replacements = new TreeMap<>(); + replacements.put(oldTrade.givenPokemon.name, newTrade.givenPokemon.name); + if (oldTrade.requestedPokemon != newTrade.requestedPokemon) { + replacements.put(oldTrade.requestedPokemon.name, newTrade.requestedPokemon.name); + } + replaceAllStringsInEntry(textOffsets[tr], replacements); + } + } + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + } + + private void replaceAllStringsInEntry(int entry, Map replacements) { + List thisTradeStrings = this.getStrings(true, entry); + int ttsCount = thisTradeStrings.size(); + for (int strNum = 0; strNum < ttsCount; strNum++) { + String newString = thisTradeStrings.get(strNum); + for (String old: replacements.keySet()) { + newString = newString.replaceAll(old,replacements.get(old)); + } + thisTradeStrings.set(strNum, newString); + } + this.setStrings(true, entry, thisTradeStrings); + } + + @Override + public boolean hasDVs() { + return false; + } + + @Override + public int generationOfPokemon() { + return 5; + } + + @Override + public void removeEvosForPokemonPool() { + // slightly more complicated than gen2/3 + // we have to update a "baby table" too + List pokemonIncluded = this.mainPokemonList; + Set keepEvos = new HashSet<>(); + for (Pokemon pk : pokes) { + if (pk != null) { + keepEvos.clear(); + for (Evolution evol : pk.evolutionsFrom) { + if (pokemonIncluded.contains(evol.from) && pokemonIncluded.contains(evol.to)) { + keepEvos.add(evol); + } else { + evol.to.evolutionsTo.remove(evol); + } + } + pk.evolutionsFrom.retainAll(keepEvos); + } + } + + try { + NARCArchive babyNARC = readNARC(romEntry.getFile("BabyPokemon")); + // baby pokemon + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + Pokemon baby = pokes[i]; + while (baby.evolutionsTo.size() > 0) { + // Grab the first "to evolution" even if there are multiple + baby = baby.evolutionsTo.get(0).from; + } + writeWord(babyNARC.files.get(i), 0, baby.number); + } + // finish up + writeNARC(romEntry.getFile("BabyPokemon"), babyNARC); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public boolean supportsFourStartingMoves() { + return true; + } + + @Override + public List getFieldMoves() { + // cut, fly, surf, strength, flash, dig, teleport, waterfall, + // sweet scent, dive + return Gen5Constants.fieldMoves; + } + + @Override + public List getEarlyRequiredHMMoves() { + // BW1: cut + // BW2: none + if (romEntry.romType == Gen5Constants.Type_BW2) { + return Gen5Constants.bw2EarlyRequiredHMMoves; + } else { + return Gen5Constants.bw1EarlyRequiredHMMoves; + } + } + + @Override + public Map getShopItems() { + int[] tmShops = romEntry.arrayEntries.get("TMShops"); + int[] regularShops = romEntry.arrayEntries.get("RegularShops"); + int[] shopItemOffsets = romEntry.arrayEntries.get("ShopItemOffsets"); + int[] shopItemSizes = romEntry.arrayEntries.get("ShopItemSizes"); + int shopCount = romEntry.getInt("ShopCount"); + List shopItems = new ArrayList<>(); + Map shopItemsMap = new TreeMap<>(); + + try { + byte[] shopItemOverlay = readOverlay(romEntry.getInt("ShopItemOvlNumber")); + IntStream.range(0, shopCount).forEachOrdered(i -> { + boolean badShop = false; + for (int tmShop : tmShops) { + if (i == tmShop) { + badShop = true; + break; + } + } + for (int regularShop : regularShops) { + if (badShop) break; + if (i == regularShop) { + badShop = true; + break; + } + } + if (!badShop) { + List items = new ArrayList<>(); + if (romEntry.romType == Gen5Constants.Type_BW) { + for (int j = 0; j < shopItemSizes[i]; j++) { + items.add(readWord(shopItemOverlay, shopItemOffsets[i] + j * 2)); + } + } else if (romEntry.romType == Gen5Constants.Type_BW2) { + byte[] shop = shopNarc.files.get(i); + for (int j = 0; j < shop.length; j += 2) { + items.add(readWord(shop, j)); + } + } + Shop shop = new Shop(); + shop.items = items; + shop.name = shopNames.get(i); + shop.isMainGame = Gen5Constants.getMainGameShops(romEntry.romType).contains(i); + shopItemsMap.put(i, shop); + } + }); + return shopItemsMap; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public void setShopItems(Map shopItems) { + int[] shopItemOffsets = romEntry.arrayEntries.get("ShopItemOffsets"); + int[] shopItemSizes = romEntry.arrayEntries.get("ShopItemSizes"); + int[] tmShops = romEntry.arrayEntries.get("TMShops"); + int[] regularShops = romEntry.arrayEntries.get("RegularShops"); + int shopCount = romEntry.getInt("ShopCount"); + + try { + byte[] shopItemOverlay = readOverlay(romEntry.getInt("ShopItemOvlNumber")); + IntStream.range(0, shopCount).forEachOrdered(i -> { + boolean badShop = false; + for (int tmShop : tmShops) { + if (badShop) break; + if (i == tmShop) badShop = true; + } + for (int regularShop : regularShops) { + if (badShop) break; + if (i == regularShop) badShop = true; + } + if (!badShop) { + List shopContents = shopItems.get(i).items; + Iterator iterItems = shopContents.iterator(); + if (romEntry.romType == Gen5Constants.Type_BW) { + for (int j = 0; j < shopItemSizes[i]; j++) { + Integer item = iterItems.next(); + writeWord(shopItemOverlay, shopItemOffsets[i] + j * 2, item); + } + } else if (romEntry.romType == Gen5Constants.Type_BW2) { + byte[] shop = shopNarc.files.get(i); + for (int j = 0; j < shop.length; j += 2) { + Integer item = iterItems.next(); + writeWord(shop, j, item); + } + } + } + }); + if (romEntry.romType == Gen5Constants.Type_BW2) { + writeNARC(romEntry.getFile("ShopItems"), shopNarc); + } else { + writeOverlay(romEntry.getInt("ShopItemOvlNumber"), shopItemOverlay); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public void setShopPrices() { + try { + NARCArchive itemPriceNarc = this.readNARC(romEntry.getFile("ItemData")); + for (int i = 1; i < itemPriceNarc.files.size(); i++) { + writeWord(itemPriceNarc.files.get(i),0,Gen5Constants.balancedItemPrices.get(i)); + } + writeNARC(romEntry.getFile("ItemData"),itemPriceNarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getPickupItems() { + List pickupItems = new ArrayList<>(); + try { + byte[] battleOverlay = readOverlay(romEntry.getInt("PickupOvlNumber")); + + // If we haven't found the pickup table for this ROM already, find it. + if (pickupItemsTableOffset == 0) { + int offset = find(battleOverlay, Gen5Constants.pickupTableLocator); + if (offset > 0) { + pickupItemsTableOffset = offset; + } + } + + // Assuming we've found the pickup table, extract the items out of it. + if (pickupItemsTableOffset > 0) { + for (int i = 0; i < Gen5Constants.numberOfPickupItems; i++) { + int itemOffset = pickupItemsTableOffset + (2 * i); + int item = FileFunctions.read2ByteInt(battleOverlay, itemOffset); + PickupItem pickupItem = new PickupItem(item); + pickupItems.add(pickupItem); + } + } + + // Assuming we got the items from the last step, fill out the probabilities. + if (pickupItems.size() > 0) { + for (int levelRange = 0; levelRange < 10; levelRange++) { + int startingRareItemOffset = levelRange; + int startingCommonItemOffset = 11 + levelRange; + pickupItems.get(startingCommonItemOffset).probabilities[levelRange] = 30; + for (int i = 1; i < 7; i++) { + pickupItems.get(startingCommonItemOffset + i).probabilities[levelRange] = 10; + } + pickupItems.get(startingCommonItemOffset + 7).probabilities[levelRange] = 4; + pickupItems.get(startingCommonItemOffset + 8).probabilities[levelRange] = 4; + pickupItems.get(startingRareItemOffset).probabilities[levelRange] = 1; + pickupItems.get(startingRareItemOffset + 1).probabilities[levelRange] = 1; + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return pickupItems; + } + + @Override + public void setPickupItems(List pickupItems) { + try { + if (pickupItemsTableOffset > 0) { + byte[] battleOverlay = readOverlay(romEntry.getInt("PickupOvlNumber")); + for (int i = 0; i < Gen5Constants.numberOfPickupItems; i++) { + int itemOffset = pickupItemsTableOffset + (2 * i); + int item = pickupItems.get(i).item; + FileFunctions.write2ByteInt(battleOverlay, itemOffset, item); + } + writeOverlay(romEntry.getInt("PickupOvlNumber"), battleOverlay); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void computeCRC32sForRom() throws IOException { + this.actualOverlayCRC32s = new HashMap<>(); + this.actualFileCRC32s = new HashMap<>(); + this.actualArm9CRC32 = FileFunctions.getCRC32(arm9); + for (int overlayNumber : romEntry.overlayExpectedCRC32s.keySet()) { + byte[] overlay = readOverlay(overlayNumber); + long crc32 = FileFunctions.getCRC32(overlay); + this.actualOverlayCRC32s.put(overlayNumber, crc32); + } + for (String fileKey : romEntry.files.keySet()) { + byte[] file = readFile(romEntry.getFile(fileKey)); + long crc32 = FileFunctions.getCRC32(file); + this.actualFileCRC32s.put(fileKey, crc32); + } + } + + @Override + public boolean isRomValid() { + if (romEntry.arm9ExpectedCRC32 != actualArm9CRC32) { + return false; + } + + for (int overlayNumber : romEntry.overlayExpectedCRC32s.keySet()) { + long expectedCRC32 = romEntry.overlayExpectedCRC32s.get(overlayNumber); + long actualCRC32 = actualOverlayCRC32s.get(overlayNumber); + if (expectedCRC32 != actualCRC32) { + return false; + } + } + + for (String fileKey : romEntry.files.keySet()) { + long expectedCRC32 = romEntry.files.get(fileKey).expectedCRC32; + long actualCRC32 = actualFileCRC32s.get(fileKey); + if (expectedCRC32 != actualCRC32) { + return false; + } + } + + return true; + } + + @Override + public BufferedImage getMascotImage() { + try { + Pokemon pk = randomPokemonInclFormes(); + NARCArchive pokespritesNARC = this.readNARC(romEntry.getFile("PokemonGraphics")); + + // First prepare the palette, it's the easy bit + int palIndex = pk.getSpriteIndex() * 20 + 18; + if (random.nextInt(10) == 0) { + // shiny + palIndex++; + } + byte[] rawPalette = pokespritesNARC.files.get(palIndex); + int[] palette = new int[16]; + for (int i = 1; i < 16; i++) { + palette[i] = GFXFunctions.conv16BitColorToARGB(readWord(rawPalette, 40 + i * 2)); + } + + // Get the picture and uncompress it. + byte[] compressedPic = pokespritesNARC.files.get(pk.getSpriteIndex() * 20); + byte[] uncompressedPic = DSDecmp.Decompress(compressedPic); + + // Output to 64x144 tiled image to prepare for unscrambling + BufferedImage bim = GFXFunctions.drawTiledImage(uncompressedPic, palette, 48, 64, 144, 4); + + // Unscramble the above onto a 96x96 canvas + BufferedImage finalImage = new BufferedImage(96, 96, BufferedImage.TYPE_INT_ARGB); + Graphics g = finalImage.getGraphics(); + g.drawImage(bim, 0, 0, 64, 64, 0, 0, 64, 64, null); + g.drawImage(bim, 64, 0, 96, 8, 0, 64, 32, 72, null); + g.drawImage(bim, 64, 8, 96, 16, 32, 64, 64, 72, null); + g.drawImage(bim, 64, 16, 96, 24, 0, 72, 32, 80, null); + g.drawImage(bim, 64, 24, 96, 32, 32, 72, 64, 80, null); + g.drawImage(bim, 64, 32, 96, 40, 0, 80, 32, 88, null); + g.drawImage(bim, 64, 40, 96, 48, 32, 80, 64, 88, null); + g.drawImage(bim, 64, 48, 96, 56, 0, 88, 32, 96, null); + g.drawImage(bim, 64, 56, 96, 64, 32, 88, 64, 96, null); + g.drawImage(bim, 0, 64, 64, 96, 0, 96, 64, 128, null); + g.drawImage(bim, 64, 64, 96, 72, 0, 128, 32, 136, null); + g.drawImage(bim, 64, 72, 96, 80, 32, 128, 64, 136, null); + g.drawImage(bim, 64, 80, 96, 88, 0, 136, 32, 144, null); + g.drawImage(bim, 64, 88, 96, 96, 32, 136, 64, 144, null); + + // Phew, all done. + return finalImage; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getAllHeldItems() { + return Gen5Constants.allHeldItems; + } + + @Override + public List getAllConsumableHeldItems() { + return Gen5Constants.consumableHeldItems; + } + + @Override + public List getSensibleHeldItemsFor(TrainerPokemon tp, boolean consumableOnly, List moves, int[] pokeMoves) { + List items = new ArrayList<>(); + items.addAll(Gen5Constants.generalPurposeConsumableItems); + int frequencyBoostCount = 6; // Make some very good items more common, but not too common + if (!consumableOnly) { + frequencyBoostCount = 8; // bigger to account for larger item pool. + items.addAll(Gen5Constants.generalPurposeItems); + } + for (int moveIdx : pokeMoves) { + Move move = moves.get(moveIdx); + if (move == null) { + continue; + } + if (move.category == MoveCategory.PHYSICAL) { + items.add(Items.liechiBerry); + items.add(Gen5Constants.consumableTypeBoostingItems.get(move.type)); + if (!consumableOnly) { + items.addAll(Gen5Constants.typeBoostingItems.get(move.type)); + items.add(Items.choiceBand); + items.add(Items.muscleBand); + } + } + if (move.category == MoveCategory.SPECIAL) { + items.add(Items.petayaBerry); + items.add(Gen5Constants.consumableTypeBoostingItems.get(move.type)); + if (!consumableOnly) { + items.addAll(Gen5Constants.typeBoostingItems.get(move.type)); + items.add(Items.wiseGlasses); + items.add(Items.choiceSpecs); + } + } + if (!consumableOnly && Gen5Constants.moveBoostingItems.containsKey(moveIdx)) { + items.addAll(Gen5Constants.moveBoostingItems.get(moveIdx)); + } + } + Map byType = Effectiveness.against(tp.pokemon.primaryType, tp.pokemon.secondaryType, 5, effectivenessUpdated); + for(Map.Entry entry : byType.entrySet()) { + Integer berry = Gen5Constants.weaknessReducingBerries.get(entry.getKey()); + if (entry.getValue() == Effectiveness.DOUBLE) { + items.add(berry); + } else if (entry.getValue() == Effectiveness.QUADRUPLE) { + for (int i = 0; i < frequencyBoostCount; i++) { + items.add(berry); + } + } + } + if (byType.get(Type.NORMAL) == Effectiveness.NEUTRAL) { + items.add(Items.chilanBerry); + } + + int ability = this.getAbilityForTrainerPokemon(tp); + if (ability == Abilities.levitate) { + items.removeAll(Arrays.asList(Items.shucaBerry)); + } else if (byType.get(Type.GROUND) == Effectiveness.DOUBLE || byType.get(Type.GROUND) == Effectiveness.QUADRUPLE) { + items.add(Items.airBalloon); + } + + if (!consumableOnly) { + if (Gen5Constants.abilityBoostingItems.containsKey(ability)) { + items.addAll(Gen5Constants.abilityBoostingItems.get(ability)); + } + if (tp.pokemon.primaryType == Type.POISON || tp.pokemon.secondaryType == Type.POISON) { + items.add(Items.blackSludge); + } + List speciesItems = Gen5Constants.speciesBoostingItems.get(tp.pokemon.number); + if (speciesItems != null) { + for (int i = 0; i < frequencyBoostCount; i++) { + items.addAll(speciesItems); + } + } + if (!tp.pokemon.evolutionsFrom.isEmpty() && tp.level >= 20) { + // eviolite can be too good for early game, so we gate it behind a minimum level. + // We go with the same level as the option for "No early wonder guard". + items.add(Items.eviolite); + } + } + return items; + } +} diff --git a/src/com/pkrandom/romhandlers/Gen6RomHandler.java b/src/com/pkrandom/romhandlers/Gen6RomHandler.java new file mode 100644 index 0000000..3148238 --- /dev/null +++ b/src/com/pkrandom/romhandlers/Gen6RomHandler.java @@ -0,0 +1,4270 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- Gen6RomHandler.java - randomizer handler for X/Y/OR/AS. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.*; +import com.pkrandom.constants.*; +import com.pkrandom.ctr.AMX; +import com.pkrandom.ctr.GARCArchive; +import com.pkrandom.ctr.Mini; +import com.pkrandom.exceptions.RandomizerIOException; +import com.pkrandom.pokemon.*; +import pptxt.N3DSTxtHandler; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.*; +import java.util.List; +import java.util.stream.Collectors; + +public class Gen6RomHandler extends Abstract3DSRomHandler { + + public static class Factory extends RomHandler.Factory { + + @Override + public Gen6RomHandler create(Random random, PrintStream logStream) { + return new Gen6RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + return detect3DSRomInner(getProductCodeFromFile(filename), getTitleIdFromFile(filename)); + } + } + + public Gen6RomHandler(Random random) { + super(random, null); + } + + public Gen6RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + private static class OffsetWithinEntry { + private int entry; + private int offset; + } + + private static class RomFileEntry { + public String path; + public long[] expectedCRC32s; + } + + private static class RomEntry { + private String name; + private String romCode; + private String titleId; + private String acronym; + private int romType; + private long[] expectedCodeCRC32s = new long[2]; + private Map files = new HashMap<>(); + private boolean staticPokemonSupport = true, copyStaticPokemon = true; + private Map linkedStaticOffsets = new HashMap<>(); + private Map strings = new HashMap<>(); + private Map numbers = new HashMap<>(); + private Map arrayEntries = new HashMap<>(); + private Map offsetArrayEntries = new HashMap<>(); + + private int getInt(String key) { + if (!numbers.containsKey(key)) { + numbers.put(key, 0); + } + return numbers.get(key); + } + + private String getString(String key) { + if (!strings.containsKey(key)) { + strings.put(key, ""); + } + return strings.get(key); + } + + private String getFile(String key) { + if (!files.containsKey(key)) { + files.put(key, new RomFileEntry()); + } + return files.get(key).path; + } + } + + private static List roms; + + static { + loadROMInfo(); + } + + private static void loadROMInfo() { + roms = new ArrayList<>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen6_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + if (r[0].equals("Game")) { + current.romCode = r[1]; + } else if (r[0].equals("Type")) { + if (r[1].equalsIgnoreCase("ORAS")) { + current.romType = Gen6Constants.Type_ORAS; + } else { + current.romType = Gen6Constants.Type_XY; + } + } else if (r[0].equals("TitleId")) { + current.titleId = r[1]; + } else if (r[0].equals("Acronym")) { + current.acronym = r[1]; + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.romCode)) { + // copy from here + current.linkedStaticOffsets.putAll(otherEntry.linkedStaticOffsets); + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.numbers.putAll(otherEntry.numbers); + current.strings.putAll(otherEntry.strings); + current.offsetArrayEntries.putAll(otherEntry.offsetArrayEntries); + current.files.putAll(otherEntry.files); + } + } + } else if (r[0].startsWith("File<")) { + String key = r[0].split("<")[1].split(">")[0]; + String[] values = r[1].substring(1, r[1].length() - 1).split(","); + String path = values[0]; + String crcString = values[1].trim() + ", " + values[2].trim(); + String[] crcs = crcString.substring(1, crcString.length() - 1).split(","); + RomFileEntry entry = new RomFileEntry(); + entry.path = path.trim(); + entry.expectedCRC32s = new long[2]; + entry.expectedCRC32s[0] = parseRILong("0x" + crcs[0].trim()); + entry.expectedCRC32s[1] = parseRILong("0x" + crcs[1].trim()); + current.files.put(key, entry); + } else if (r[0].equals("CodeCRC32")) { + String[] values = r[1].substring(1, r[1].length() - 1).split(","); + current.expectedCodeCRC32s[0] = parseRILong("0x" + values[0].trim()); + current.expectedCodeCRC32s[1] = parseRILong("0x" + values[1].trim()); + } else if (r[0].equals("LinkedStaticEncounterOffsets")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + for (int i = 0; i < offsets.length; i++) { + String[] parts = offsets[i].split(":"); + current.linkedStaticOffsets.put(Integer.parseInt(parts[0].trim()), Integer.parseInt(parts[1].trim())); + } + } else if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.arrayEntries.put(r[0], offs); + } + } else if (r[0].endsWith("Offset") || r[0].endsWith("Count") || r[0].endsWith("Number")) { + int offs = parseRIInt(r[1]); + current.numbers.put(r[0], offs); + } else { + current.strings.put(r[0],r[1]); + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + System.err.println("File not found!"); + } + } + + private static int parseRIInt(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Integer.parseInt(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + private static long parseRILong(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Long.parseLong(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + // This ROM + private Pokemon[] pokes; + private Map formeMappings = new TreeMap<>(); + private Map> absolutePokeNumByBaseForme; + private Map dummyAbsolutePokeNums; + private List pokemonList; + private List pokemonListInclFormes; + private List megaEvolutions; + private Move[] moves; + private RomEntry romEntry; + private byte[] code; + private List abilityNames; + private boolean loadedWildMapNames; + private Map wildMapNames; + private int moveTutorMovesOffset; + private List itemNames; + private List shopNames; + private int shopItemsOffset; + private ItemList allowedItems, nonBadItems; + private int pickupItemsTableOffset; + private long actualCodeCRC32; + private Map actualFileCRC32s; + + private GARCArchive pokeGarc, moveGarc, stringsGarc, storyTextGarc; + + @Override + protected boolean detect3DSRom(String productCode, String titleId) { + return detect3DSRomInner(productCode, titleId); + } + + private static boolean detect3DSRomInner(String productCode, String titleId) { + return entryFor(productCode, titleId) != null; + } + + private static RomEntry entryFor(String productCode, String titleId) { + if (productCode == null || titleId == null) { + return null; + } + + for (RomEntry re : roms) { + if (productCode.equals(re.romCode) && titleId.equals(re.titleId)) { + return re; + } + } + return null; + } + + @Override + protected void loadedROM(String productCode, String titleId) { + this.romEntry = entryFor(productCode, titleId); + + try { + code = readCode(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + try { + stringsGarc = readGARC(romEntry.getFile("TextStrings"),true); + storyTextGarc = readGARC(romEntry.getFile("StoryText"), true); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + loadPokemonStats(); + loadMoves(); + + pokemonListInclFormes = Arrays.asList(pokes); + pokemonList = Arrays.asList(Arrays.copyOfRange(pokes,0,Gen6Constants.pokemonCount + 1)); + + abilityNames = getStrings(false,romEntry.getInt("AbilityNamesTextOffset")); + itemNames = getStrings(false,romEntry.getInt("ItemNamesTextOffset")); + shopNames = Gen6Constants.getShopNames(romEntry.romType); + + loadedWildMapNames = false; + if (romEntry.romType == Gen6Constants.Type_ORAS) { + isORAS = true; + } + + allowedItems = Gen6Constants.getAllowedItems(romEntry.romType).copy(); + nonBadItems = Gen6Constants.getNonBadItems(romEntry.romType).copy(); + + try { + computeCRC32sForRom(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void loadPokemonStats() { + try { + pokeGarc = this.readGARC(romEntry.getFile("PokemonStats"),true); + String[] pokeNames = readPokemonNames(); + int formeCount = Gen6Constants.getFormeCount(romEntry.romType); + pokes = new Pokemon[Gen6Constants.pokemonCount + formeCount + 1]; + for (int i = 1; i <= Gen6Constants.pokemonCount; i++) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i],pokeGarc.files.get(i).get(0),formeMappings); + pokes[i].name = pokeNames[i]; + } + + absolutePokeNumByBaseForme = new HashMap<>(); + dummyAbsolutePokeNums = new HashMap<>(); + dummyAbsolutePokeNums.put(255,0); + + int i = Gen6Constants.pokemonCount + 1; + int formNum = 1; + int prevSpecies = 0; + Map currentMap = new HashMap<>(); + for (int k: formeMappings.keySet()) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i], pokeGarc.files.get(k).get(0),formeMappings); + FormeInfo fi = formeMappings.get(k); + pokes[i].name = pokeNames[fi.baseForme]; + pokes[i].baseForme = pokes[fi.baseForme]; + pokes[i].formeNumber = fi.formeNumber; + pokes[i].formeSuffix = Gen6Constants.formeSuffixes.getOrDefault(k,""); + if (fi.baseForme == prevSpecies) { + formNum++; + currentMap.put(formNum,i); + } else { + if (prevSpecies != 0) { + absolutePokeNumByBaseForme.put(prevSpecies,currentMap); + } + prevSpecies = fi.baseForme; + formNum = 1; + currentMap = new HashMap<>(); + currentMap.put(formNum,i); + } + i++; + } + if (prevSpecies != 0) { + absolutePokeNumByBaseForme.put(prevSpecies,currentMap); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + populateEvolutions(); + populateMegaEvolutions(); + } + + private void loadBasicPokeStats(Pokemon pkmn, byte[] stats, Map altFormes) { + pkmn.hp = stats[Gen6Constants.bsHPOffset] & 0xFF; + pkmn.attack = stats[Gen6Constants.bsAttackOffset] & 0xFF; + pkmn.defense = stats[Gen6Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = stats[Gen6Constants.bsSpeedOffset] & 0xFF; + pkmn.spatk = stats[Gen6Constants.bsSpAtkOffset] & 0xFF; + pkmn.spdef = stats[Gen6Constants.bsSpDefOffset] & 0xFF; + // Type + pkmn.primaryType = Gen6Constants.typeTable[stats[Gen6Constants.bsPrimaryTypeOffset] & 0xFF]; + pkmn.secondaryType = Gen6Constants.typeTable[stats[Gen6Constants.bsSecondaryTypeOffset] & 0xFF]; + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + pkmn.catchRate = stats[Gen6Constants.bsCatchRateOffset] & 0xFF; + pkmn.growthCurve = ExpCurve.fromByte(stats[Gen6Constants.bsGrowthCurveOffset]); + + pkmn.ability1 = stats[Gen6Constants.bsAbility1Offset] & 0xFF; + pkmn.ability2 = stats[Gen6Constants.bsAbility2Offset] & 0xFF; + pkmn.ability3 = stats[Gen6Constants.bsAbility3Offset] & 0xFF; + if (pkmn.ability1 == pkmn.ability2) { + pkmn.ability2 = 0; + } + + // Held Items? + int item1 = FileFunctions.read2ByteInt(stats, Gen6Constants.bsCommonHeldItemOffset); + int item2 = FileFunctions.read2ByteInt(stats, Gen6Constants.bsRareHeldItemOffset); + + if (item1 == item2) { + // guaranteed + pkmn.guaranteedHeldItem = item1; + pkmn.commonHeldItem = 0; + pkmn.rareHeldItem = 0; + pkmn.darkGrassHeldItem = -1; + } else { + pkmn.guaranteedHeldItem = 0; + pkmn.commonHeldItem = item1; + pkmn.rareHeldItem = item2; + pkmn.darkGrassHeldItem = -1; + } + + int formeCount = stats[Gen6Constants.bsFormeCountOffset] & 0xFF; + if (formeCount > 1) { + if (!altFormes.keySet().contains(pkmn.number)) { + int firstFormeOffset = FileFunctions.read2ByteInt(stats, Gen6Constants.bsFormeOffset); + if (firstFormeOffset != 0) { + for (int i = 1; i < formeCount; i++) { + altFormes.put(firstFormeOffset + i - 1,new FormeInfo(pkmn.number,i,FileFunctions.read2ByteInt(stats,Gen6Constants.bsFormeSpriteOffset))); // Assumes that formes are in memory in the same order as their numbers + if (Gen6Constants.actuallyCosmeticForms.contains(firstFormeOffset+i-1)) { + if (pkmn.number != Species.pikachu && pkmn.number != Species.cherrim) { // No Pikachu/Cherrim + pkmn.cosmeticForms += 1; + } + } + } + } else { + if (pkmn.number != Species.arceus && pkmn.number != Species.genesect && pkmn.number != Species.xerneas) { + // Reason for exclusions: + // Arceus/Genesect: to avoid confusion + // Xerneas: Should be handled automatically? + pkmn.cosmeticForms = formeCount; + } + } + } else { + if (Gen6Constants.actuallyCosmeticForms.contains(pkmn.number)) { + pkmn.actuallyCosmetic = true; + } + } + } + } + + private String[] readPokemonNames() { + String[] pokeNames = new String[Gen6Constants.pokemonCount + 1]; + List nameList = getStrings(false, romEntry.getInt("PokemonNamesTextOffset")); + for (int i = 1; i <= Gen6Constants.pokemonCount; i++) { + pokeNames[i] = nameList.get(i); + } + return pokeNames; + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + // Read GARC + try { + GARCArchive evoGARC = readGARC(romEntry.getFile("PokemonEvolutions"),true); + for (int i = 1; i <= Gen6Constants.pokemonCount + Gen6Constants.getFormeCount(romEntry.romType); i++) { + Pokemon pk = pokes[i]; + byte[] evoEntry = evoGARC.files.get(i).get(0); + for (int evo = 0; evo < 8; evo++) { + int method = readWord(evoEntry, evo * 6); + int species = readWord(evoEntry, evo * 6 + 4); + if (method >= 1 && method <= Gen6Constants.evolutionMethodCount && species >= 1) { + EvolutionType et = EvolutionType.fromIndex(6, method); + if (et.equals(EvolutionType.LEVEL_HIGH_BEAUTY)) continue; // Remove Feebas "split" evolution + int extraInfo = readWord(evoEntry, evo * 6 + 2); + Evolution evol = new Evolution(pk, pokes[species], true, et, extraInfo); + if (!pk.evolutionsFrom.contains(evol)) { + pk.evolutionsFrom.add(evol); + if (!pk.actuallyCosmetic) pokes[species].evolutionsTo.add(evol); + } + } + } + // Nincada's Shedinja evo is hardcoded into the game's executable, so + // if the Pokemon is Nincada, then let's put it as one of its evolutions + if (pk.number == Species.nincada) { + Pokemon shedinja = pokes[Species.shedinja]; + Evolution evol = new Evolution(pk, shedinja, false, EvolutionType.LEVEL_IS_EXTRA, 20); + pk.evolutionsFrom.add(evol); + shedinja.evolutionsTo.add(evol); + } + + // Split evos shouldn't carry stats unless the evo is Nincada's + // In that case, we should have Ninjask carry stats + if (pk.evolutionsFrom.size() > 1) { + for (Evolution e : pk.evolutionsFrom) { + if (e.type != EvolutionType.LEVEL_CREATE_EXTRA) { + e.carryStats = false; + } + } + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void populateMegaEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.megaEvolutionsFrom.clear(); + pkmn.megaEvolutionsTo.clear(); + } + } + + // Read GARC + try { + megaEvolutions = new ArrayList<>(); + GARCArchive megaEvoGARC = readGARC(romEntry.getFile("MegaEvolutions"),true); + for (int i = 1; i <= Gen6Constants.pokemonCount; i++) { + Pokemon pk = pokes[i]; + byte[] megaEvoEntry = megaEvoGARC.files.get(i).get(0); + for (int evo = 0; evo < 3; evo++) { + int formNum = readWord(megaEvoEntry, evo * 8); + int method = readWord(megaEvoEntry, evo * 8 + 2); + if (method >= 1) { + int argument = readWord(megaEvoEntry, evo * 8 + 4); + int megaSpecies = absolutePokeNumByBaseForme + .getOrDefault(pk.number,dummyAbsolutePokeNums) + .getOrDefault(formNum,0); + MegaEvolution megaEvo = new MegaEvolution(pk, pokes[megaSpecies], method, argument); + if (!pk.megaEvolutionsFrom.contains(megaEvo)) { + pk.megaEvolutionsFrom.add(megaEvo); + pokes[megaSpecies].megaEvolutionsTo.add(megaEvo); + } + megaEvolutions.add(megaEvo); + } + } + // split evos don't carry stats + if (pk.megaEvolutionsFrom.size() > 1) { + for (MegaEvolution e : pk.megaEvolutionsFrom) { + e.carryStats = false; + } + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private List getStrings(boolean isStoryText, int index) { + GARCArchive baseGARC = isStoryText ? storyTextGarc : stringsGarc; + return getStrings(baseGARC, index); + } + + private List getStrings(GARCArchive textGARC, int index) { + byte[] rawFile = textGARC.files.get(index).get(0); + return new ArrayList<>(N3DSTxtHandler.readTexts(rawFile,true,romEntry.romType)); + } + + private void setStrings(boolean isStoryText, int index, List strings) { + GARCArchive baseGARC = isStoryText ? storyTextGarc : stringsGarc; + setStrings(baseGARC, index, strings); + } + + private void setStrings(GARCArchive textGARC, int index, List strings) { + byte[] oldRawFile = textGARC.files.get(index).get(0); + try { + byte[] newRawFile = N3DSTxtHandler.saveEntry(oldRawFile, strings, romEntry.romType); + textGARC.setFile(index, newRawFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void loadMoves() { + try { + moveGarc = this.readGARC(romEntry.getFile("MoveData"),true); + int moveCount = Gen6Constants.getMoveCount(romEntry.romType); + moves = new Move[moveCount + 1]; + List moveNames = getStrings(false, romEntry.getInt("MoveNamesTextOffset")); + for (int i = 1; i <= moveCount; i++) { + byte[] moveData; + if (romEntry.romType == Gen6Constants.Type_ORAS) { + moveData = Mini.UnpackMini(moveGarc.files.get(0).get(0), "WD")[i]; + } else { + moveData = moveGarc.files.get(i).get(0); + } + moves[i] = new Move(); + moves[i].name = moveNames.get(i); + moves[i].number = i; + moves[i].internalId = i; + moves[i].effectIndex = readWord(moveData, 16); + moves[i].hitratio = (moveData[4] & 0xFF); + moves[i].power = moveData[3] & 0xFF; + moves[i].pp = moveData[5] & 0xFF; + moves[i].type = Gen6Constants.typeTable[moveData[0] & 0xFF]; + moves[i].flinchPercentChance = moveData[15] & 0xFF; + moves[i].target = moveData[20] & 0xFF; + moves[i].category = Gen6Constants.moveCategoryIndices[moveData[2] & 0xFF]; + moves[i].priority = moveData[6]; + + int critStages = moveData[14] & 0xFF; + if (critStages == 6) { + moves[i].criticalChance = CriticalChance.GUARANTEED; + } else if (critStages > 0) { + moves[i].criticalChance = CriticalChance.INCREASED; + } + + int internalStatusType = readWord(moveData, 8); + int flags = FileFunctions.readFullInt(moveData, 32); + moves[i].makesContact = (flags & 0x001) != 0; + moves[i].isChargeMove = (flags & 0x002) != 0; + moves[i].isRechargeMove = (flags & 0x004) != 0; + moves[i].isPunchMove = (flags & 0x080) != 0; + moves[i].isSoundMove = (flags & 0x100) != 0; + moves[i].isTrapMove = internalStatusType == 8; + switch (moves[i].effectIndex) { + case Gen6Constants.noDamageTargetTrappingEffect: + case Gen6Constants.noDamageFieldTrappingEffect: + case Gen6Constants.damageAdjacentFoesTrappingEffect: + moves[i].isTrapMove = true; + break; + } + + int qualities = moveData[1]; + int recoilOrAbsorbPercent = moveData[18]; + if (qualities == Gen6Constants.damageAbsorbQuality) { + moves[i].absorbPercent = recoilOrAbsorbPercent; + } else { + moves[i].recoilPercent = -recoilOrAbsorbPercent; + } + + if (i == Moves.swift) { + perfectAccuracy = (int)moves[i].hitratio; + } + + if (GlobalConstants.normalMultihitMoves.contains(i)) { + moves[i].hitCount = 19 / 6.0; + } else if (GlobalConstants.doubleHitMoves.contains(i)) { + moves[i].hitCount = 2; + } else if (i == Moves.tripleKick) { + moves[i].hitCount = 2.71; // this assumes the first hit lands + } + + switch (qualities) { + case Gen6Constants.noDamageStatChangeQuality: + case Gen6Constants.noDamageStatusAndStatChangeQuality: + // All Allies or Self + if (moves[i].target == 6 || moves[i].target == 7) { + moves[i].statChangeMoveType = StatChangeMoveType.NO_DAMAGE_USER; + } else if (moves[i].target == 2) { + moves[i].statChangeMoveType = StatChangeMoveType.NO_DAMAGE_ALLY; + } else if (moves[i].target == 8) { + moves[i].statChangeMoveType = StatChangeMoveType.NO_DAMAGE_ALL; + } else { + moves[i].statChangeMoveType = StatChangeMoveType.NO_DAMAGE_TARGET; + } + break; + case Gen6Constants.damageTargetDebuffQuality: + moves[i].statChangeMoveType = StatChangeMoveType.DAMAGE_TARGET; + break; + case Gen6Constants.damageUserBuffQuality: + moves[i].statChangeMoveType = StatChangeMoveType.DAMAGE_USER; + break; + default: + moves[i].statChangeMoveType = StatChangeMoveType.NONE_OR_UNKNOWN; + break; + } + + for (int statChange = 0; statChange < 3; statChange++) { + moves[i].statChanges[statChange].type = StatChangeType.values()[moveData[21 + statChange]]; + moves[i].statChanges[statChange].stages = moveData[24 + statChange]; + moves[i].statChanges[statChange].percentChance = moveData[27 + statChange]; + } + + // Exclude status types that aren't in the StatusType enum. + if (internalStatusType < 7) { + moves[i].statusType = StatusType.values()[internalStatusType]; + if (moves[i].statusType == StatusType.POISON && (i == Moves.toxic || i == Moves.poisonFang)) { + moves[i].statusType = StatusType.TOXIC_POISON; + } + moves[i].statusPercentChance = moveData[10] & 0xFF; + switch (qualities) { + case Gen6Constants.noDamageStatusQuality: + case Gen6Constants.noDamageStatusAndStatChangeQuality: + moves[i].statusMoveType = StatusMoveType.NO_DAMAGE; + break; + case Gen6Constants.damageStatusQuality: + moves[i].statusMoveType = StatusMoveType.DAMAGE; + break; + } + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + protected void savingROM() throws IOException { + savePokemonStats(); + saveMoves(); + try { + writeCode(code); + writeGARC(romEntry.getFile("TextStrings"), stringsGarc); + writeGARC(romEntry.getFile("StoryText"), storyTextGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + protected String getGameAcronym() { + return romEntry.acronym; + } + + @Override + protected boolean isGameUpdateSupported(int version) { + return version == romEntry.numbers.get("FullyUpdatedVersionNumber"); + } + + @Override + protected String getGameVersion() { + List titleScreenText = getStrings(false, romEntry.getInt("TitleScreenTextOffset")); + if (titleScreenText.size() > romEntry.getInt("UpdateStringOffset")) { + return titleScreenText.get(romEntry.getInt("UpdateStringOffset")); + } + // This shouldn't be seen by users, but is correct assuming we accidentally show it to them. + return "Unpatched"; + } + + private void savePokemonStats() { + int k = Gen6Constants.getBsSize(romEntry.romType); + byte[] duplicateData = pokeGarc.files.get(Gen6Constants.pokemonCount + Gen6Constants.getFormeCount(romEntry.romType) + 1).get(0); + for (int i = 1; i <= Gen6Constants.pokemonCount + Gen6Constants.getFormeCount(romEntry.romType); i++) { + byte[] pokeData = pokeGarc.files.get(i).get(0); + saveBasicPokeStats(pokes[i], pokeData); + for (byte pokeDataByte : pokeData) { + duplicateData[k] = pokeDataByte; + k++; + } + } + + try { + this.writeGARC(romEntry.getFile("PokemonStats"),pokeGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + writeEvolutions(); + } + + private void saveBasicPokeStats(Pokemon pkmn, byte[] stats) { + stats[Gen6Constants.bsHPOffset] = (byte) pkmn.hp; + stats[Gen6Constants.bsAttackOffset] = (byte) pkmn.attack; + stats[Gen6Constants.bsDefenseOffset] = (byte) pkmn.defense; + stats[Gen6Constants.bsSpeedOffset] = (byte) pkmn.speed; + stats[Gen6Constants.bsSpAtkOffset] = (byte) pkmn.spatk; + stats[Gen6Constants.bsSpDefOffset] = (byte) pkmn.spdef; + stats[Gen6Constants.bsPrimaryTypeOffset] = Gen6Constants.typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + stats[Gen6Constants.bsSecondaryTypeOffset] = stats[Gen6Constants.bsPrimaryTypeOffset]; + } else { + stats[Gen6Constants.bsSecondaryTypeOffset] = Gen6Constants.typeToByte(pkmn.secondaryType); + } + stats[Gen6Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + stats[Gen6Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + + stats[Gen6Constants.bsAbility1Offset] = (byte) pkmn.ability1; + stats[Gen6Constants.bsAbility2Offset] = pkmn.ability2 != 0 ? (byte) pkmn.ability2 : (byte) pkmn.ability1; + stats[Gen6Constants.bsAbility3Offset] = (byte) pkmn.ability3; + + // Held items + if (pkmn.guaranteedHeldItem > 0) { + FileFunctions.write2ByteInt(stats, Gen6Constants.bsCommonHeldItemOffset, pkmn.guaranteedHeldItem); + FileFunctions.write2ByteInt(stats, Gen6Constants.bsRareHeldItemOffset, pkmn.guaranteedHeldItem); + FileFunctions.write2ByteInt(stats, Gen6Constants.bsDarkGrassHeldItemOffset, 0); + } else { + FileFunctions.write2ByteInt(stats, Gen6Constants.bsCommonHeldItemOffset, pkmn.commonHeldItem); + FileFunctions.write2ByteInt(stats, Gen6Constants.bsRareHeldItemOffset, pkmn.rareHeldItem); + FileFunctions.write2ByteInt(stats, Gen6Constants.bsDarkGrassHeldItemOffset, 0); + } + + if (pkmn.fullName().equals("Meowstic")) { + stats[Gen6Constants.bsGenderOffset] = 0; + } else if (pkmn.fullName().equals("Meowstic-F")) { + stats[Gen6Constants.bsGenderOffset] = (byte)0xFE; + } + } + + private void writeEvolutions() { + try { + GARCArchive evoGARC = readGARC(romEntry.getFile("PokemonEvolutions"),true); + for (int i = 1; i <= Gen6Constants.pokemonCount + Gen6Constants.getFormeCount(romEntry.romType); i++) { + byte[] evoEntry = evoGARC.files.get(i).get(0); + Pokemon pk = pokes[i]; + if (pk.number == Species.nincada) { + writeShedinjaEvolution(); + } else if (pk.number == Species.feebas && romEntry.romType == Gen6Constants.Type_ORAS) { + recreateFeebasBeautyEvolution(); + } + int evosWritten = 0; + for (Evolution evo : pk.evolutionsFrom) { + writeWord(evoEntry, evosWritten * 6, evo.type.toIndex(6)); + writeWord(evoEntry, evosWritten * 6 + 2, evo.extraInfo); + writeWord(evoEntry, evosWritten * 6 + 4, evo.to.number); + evosWritten++; + if (evosWritten == 8) { + break; + } + } + while (evosWritten < 8) { + writeWord(evoEntry, evosWritten * 6, 0); + writeWord(evoEntry, evosWritten * 6 + 2, 0); + writeWord(evoEntry, evosWritten * 6 + 4, 0); + evosWritten++; + } + } + writeGARC(romEntry.getFile("PokemonEvolutions"), evoGARC); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void writeShedinjaEvolution() throws IOException { + Pokemon nincada = pokes[Species.nincada]; + + // When the "Limit Pokemon" setting is enabled and Gen 3 is disabled, or when + // "Random Every Level" evolutions are selected, we end up clearing out Nincada's + // vanilla evolutions. In that case, there's no point in even worrying about + // Shedinja, so just return. + if (nincada.evolutionsFrom.size() < 2) { + return; + } + Pokemon primaryEvolution = nincada.evolutionsFrom.get(0).to; + Pokemon extraEvolution = nincada.evolutionsFrom.get(1).to; + + // In the CRO that handles the evolution cutscene, there's a hardcoded check to + // see if the Pokemon that just evolved is now a Ninjask after evolving. It + // performs that check using the following instructions: + // sub r0, r1, #0x100 + // subs r0, r0, #0x23 + // bne skipMakingShedinja + // The below code tweaks these instructions to use the species ID of Nincada's + // new primary evolution; that way, evolving Nincada will still produce an "extra" + // Pokemon like in older generations. + byte[] evolutionCRO = readFile(romEntry.getFile("Evolution")); + int offset = find(evolutionCRO, Gen6Constants.ninjaskSpeciesPrefix); + if (offset > 0) { + offset += Gen6Constants.ninjaskSpeciesPrefix.length() / 2; // because it was a prefix + int primaryEvoLower = primaryEvolution.number & 0x00FF; + int primaryEvoUpper = (primaryEvolution.number & 0xFF00) >> 8; + evolutionCRO[offset] = (byte) primaryEvoUpper; + evolutionCRO[offset + 4] = (byte) primaryEvoLower; + } + + // In the game's executable, there's a hardcoded value to indicate what "extra" + // Pokemon to create. It produces a Shedinja using the following instruction: + // mov r1, #0x124, where 0x124 = 292 in decimal, which is Shedinja's species ID. + // We can't just blindly replace it, though, because certain constants (for example, + // 0x125) cannot be moved without using the movw instruction. This works fine in + // Citra, but crashes on real hardware. Instead, we have to annoyingly shift up a + // big chunk of code to fill in a nop; we can then do a pc-relative load to a + // constant in the new free space. + offset = find(code, Gen6Constants.shedinjaSpeciesPrefix); + if (offset > 0) { + offset += Gen6Constants.shedinjaSpeciesPrefix.length() / 2; // because it was a prefix + + // Shift up everything below the last nop to make some room at the bottom of the function. + for (int i = 80; i < 188; i++) { + code[offset + i] = code[offset + i + 4]; + } + + // For every bl that we shifted up, patch them so they're now pointing to the same place they + // were before (without this, they will be pointing to 0x4 before where they're supposed to). + List blOffsetsToPatch = Arrays.asList(80, 92, 104, 116, 128, 140, 152, 164, 176); + for (int blOffsetToPatch : blOffsetsToPatch) { + code[offset + blOffsetToPatch] += 1; + } + + // Write Nincada's new extra evolution in the new free space. + writeLong(code, offset + 188, extraEvolution.number); + + // Now write the pc-relative load over the original mov instruction. + code[offset] = (byte) 0xB4; + code[offset + 1] = 0x10; + code[offset + 2] = (byte) 0x9F; + code[offset + 3] = (byte) 0xE5; + } + + // Now that we've handled the hardcoded Shedinja evolution, delete it so that + // we do *not* handle it in WriteEvolutions + nincada.evolutionsFrom.remove(1); + extraEvolution.evolutionsTo.remove(0); + writeFile(romEntry.getFile("Evolution"), evolutionCRO); + } + + private void recreateFeebasBeautyEvolution() { + Pokemon feebas = pokes[Species.feebas]; + + // When the "Limit Pokemon" setting is enabled, we clear out the evolutions of + // everything *not* in the pool, which could include Feebas. In that case, + // there's no point in even worrying about its evolutions, so just return. + if (feebas.evolutionsFrom.size() == 0) { + return; + } + + Evolution prismScaleEvo = feebas.evolutionsFrom.get(0); + Pokemon feebasEvolution = prismScaleEvo.to; + int beautyNeededToEvolve = 170; + Evolution beautyEvolution = new Evolution(feebas, feebasEvolution, true, + EvolutionType.LEVEL_HIGH_BEAUTY, beautyNeededToEvolve); + feebas.evolutionsFrom.add(beautyEvolution); + feebasEvolution.evolutionsTo.add(beautyEvolution); + } + + private void saveMoves() { + int moveCount = Gen6Constants.getMoveCount(romEntry.romType); + byte[][] miniArchive = new byte[0][0]; + if (romEntry.romType == Gen6Constants.Type_ORAS) { + miniArchive = Mini.UnpackMini(moveGarc.files.get(0).get(0), "WD"); + } + for (int i = 1; i <= moveCount; i++) { + byte[] data; + if (romEntry.romType == Gen6Constants.Type_ORAS) { + data = miniArchive[i]; + } else { + data = moveGarc.files.get(i).get(0); + } + data[2] = Gen6Constants.moveCategoryToByte(moves[i].category); + data[3] = (byte) moves[i].power; + data[0] = Gen6Constants.typeToByte(moves[i].type); + int hitratio = (int) Math.round(moves[i].hitratio); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 101) { + hitratio = 100; + } + data[4] = (byte) hitratio; + data[5] = (byte) moves[i].pp; + } + try { + if (romEntry.romType == Gen6Constants.Type_ORAS) { + moveGarc.setFile(0, Mini.PackMini(miniArchive, "WD")); + } + this.writeGARC(romEntry.getFile("MoveData"), moveGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void patchFormeReversion() throws IOException { + // Upon loading a save, all Mega Pokemon and all Primal Reversions + // in the player's party are set back to their base forme. This + // patches .code such that this reversion does not happen. + String saveLoadFormeReversionPrefix = Gen6Constants.getSaveLoadFormeReversionPrefix(romEntry.romType); + int offset = find(code, saveLoadFormeReversionPrefix); + if (offset > 0) { + offset += saveLoadFormeReversionPrefix.length() / 2; // because it was a prefix + + // The actual offset of the code we want to patch is 0x10 bytes from the end of + // the prefix. We have to do this because these 0x10 bytes differ between the + // base game and all game updates, so we cannot use them as part of our prefix. + offset += 0x10; + + // Stubs the call to the function that checks for Primal Reversions and + // Mega Pokemon + code[offset] = 0x00; + code[offset + 1] = 0x00; + code[offset + 2] = 0x00; + code[offset + 3] = 0x00; + } + + // In ORAS, the game also has hardcoded checks to revert Primal Groudon and Primal Kyogre + // immediately after catching them. + if (romEntry.romType == Gen6Constants.Type_ORAS) { + byte[] battleCRO = readFile(romEntry.getFile("Battle")); + offset = find(battleCRO, Gen6Constants.afterBattleFormeReversionPrefix); + if (offset > 0) { + offset += Gen6Constants.afterBattleFormeReversionPrefix.length() / 2; // because it was a prefix + + // The game checks for Primal Kyogre and Primal Groudon by pc-relative loading 0x17E, + // which is Kyogre's species ID. The call to pml::pokepara::CoreParam::ChangeFormNo + // is used by other species which we probably don't want to break, so instead of + // stubbing the call to the function, just break the hardcoded species ID check by + // making the game pc-relative load a total nonsense ID. + battleCRO[offset] = (byte) 0xFF; + battleCRO[offset + 1] = (byte) 0xFF; + + writeFile(romEntry.getFile("Battle"), battleCRO); + } + } + } + + @Override + public List getPokemon() { + return pokemonList; + } + + @Override + public List getPokemonInclFormes() { + return pokemonListInclFormes; + } + + @Override + public List getAltFormes() { + int formeCount = Gen6Constants.getFormeCount(romEntry.romType); + return pokemonListInclFormes.subList(Gen6Constants.pokemonCount + 1, Gen6Constants.pokemonCount + formeCount + 1); + } + + @Override + public List getMegaEvolutions() { + return megaEvolutions; + } + + @Override + public Pokemon getAltFormeOfPokemon(Pokemon pk, int forme) { + int pokeNum = absolutePokeNumByBaseForme.getOrDefault(pk.number,dummyAbsolutePokeNums).getOrDefault(forme,0); + return pokeNum != 0 ? pokes[pokeNum] : pk; + } + + @Override + public List getIrregularFormes() { + return Gen6Constants.getIrregularFormes(romEntry.romType).stream().map(i -> pokes[i]).collect(Collectors.toList()); + } + + @Override + public boolean hasFunctionalFormes() { + return true; + } + + @Override + public List getStarters() { + List starters = new ArrayList<>(); + try { + byte[] staticCRO = readFile(romEntry.getFile("StaticPokemon")); + + List starterIndices = + Arrays.stream(romEntry.arrayEntries.get("StarterIndices")).boxed().collect(Collectors.toList()); + + // Gift Pokemon + int count = Gen6Constants.getGiftPokemonCount(romEntry.romType); + int size = Gen6Constants.getGiftPokemonSize(romEntry.romType); + int offset = romEntry.getInt("GiftPokemonOffset"); + for (int i = 0; i < count; i++) { + if (!starterIndices.contains(i)) continue; + StaticEncounter se = new StaticEncounter(); + int species = FileFunctions.read2ByteInt(staticCRO,offset+i*size); + Pokemon pokemon = pokes[species]; + int forme = staticCRO[offset+i*size + 4]; + if (forme > pokemon.cosmeticForms && forme != 30 && forme != 31) { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(species, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + pokemon = pokes[speciesWithForme]; + } + se.pkmn = pokemon; + se.forme = forme; + se.level = staticCRO[offset+i*size + 5]; + starters.add(se); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + return starters.stream().map(pk -> pk.pkmn).collect(Collectors.toList()); + } + + @Override + public boolean setStarters(List newStarters) { + try { + byte[] staticCRO = readFile(romEntry.getFile("StaticPokemon")); + byte[] displayCRO = readFile(romEntry.getFile("StarterDisplay")); + + List starterIndices = + Arrays.stream(romEntry.arrayEntries.get("StarterIndices")).boxed().collect(Collectors.toList()); + + // Gift Pokemon + int count = Gen6Constants.getGiftPokemonCount(romEntry.romType); + int size = Gen6Constants.getGiftPokemonSize(romEntry.romType); + int offset = romEntry.getInt("GiftPokemonOffset"); + int displayOffset = readWord(displayCRO,romEntry.getInt("StarterOffsetOffset")) + romEntry.getInt("StarterExtraOffset"); + + Iterator starterIter = newStarters.iterator(); + + int displayIndex = 0; + + List starterText = getStrings(false,romEntry.getInt("StarterTextOffset")); + int[] starterTextIndices = romEntry.arrayEntries.get("SpecificStarterTextOffsets"); + + for (int i = 0; i < count; i++) { + if (!starterIndices.contains(i)) continue; + + StaticEncounter newStatic = new StaticEncounter(); + Pokemon starter = starterIter.next(); + if (starter.formeNumber > 0) { + newStatic.forme = starter.formeNumber; + starter = starter.baseForme; + } + newStatic.pkmn = starter; + if (starter.cosmeticForms > 0) { + newStatic.forme = this.random.nextInt(starter.cosmeticForms); + } + writeWord(staticCRO,offset+i*size,newStatic.pkmn.number); + staticCRO[offset+i*size + 4] = (byte)newStatic.forme; +// staticCRO[offset+i*size + 5] = (byte)newStatic.level; + writeWord(displayCRO,displayOffset+displayIndex*0x54,newStatic.pkmn.number); + displayCRO[displayOffset+displayIndex*0x54+2] = (byte)newStatic.forme; + if (displayIndex < 3) { + starterText.set(starterTextIndices[displayIndex], + "[VAR PKNAME(0000)]"); + } + displayIndex++; + } + writeFile(romEntry.getFile("StaticPokemon"),staticCRO); + writeFile(romEntry.getFile("StarterDisplay"),displayCRO); + setStrings(false, romEntry.getInt("StarterTextOffset"), starterText); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return true; + } + + @Override + public boolean hasStarterAltFormes() { + return true; + } + + @Override + public int starterCount() { + return romEntry.romType == Gen6Constants.Type_XY ? 6 : 12; + } + + @Override + public Map getUpdatedPokemonStats(int generation) { + Map map = GlobalConstants.getStatChanges(generation); + switch(generation) { + case 7: + map.put(Species.Gen6Formes.alakazamMega, new StatChange(Stat.SPDEF.val, 105)); + break; + case 8: + map.put(Species.Gen6Formes.aegislashB, new StatChange(Stat.ATK.val | Stat.SPATK.val, 140, 140)); + break; + } + return map; + } + + @Override + public boolean supportsStarterHeldItems() { + return true; + } + + @Override + public List getStarterHeldItems() { + List starterHeldItems = new ArrayList<>(); + try { + byte[] staticCRO = readFile(romEntry.getFile("StaticPokemon")); + + List starterIndices = + Arrays.stream(romEntry.arrayEntries.get("StarterIndices")).boxed().collect(Collectors.toList()); + + // Gift Pokemon + int count = Gen6Constants.getGiftPokemonCount(romEntry.romType); + int size = Gen6Constants.getGiftPokemonSize(romEntry.romType); + int offset = romEntry.getInt("GiftPokemonOffset"); + for (int i = 0; i < count; i++) { + if (!starterIndices.contains(i)) continue; + int heldItem = FileFunctions.readFullInt(staticCRO,offset+i*size + 12); + if (heldItem < 0) { + heldItem = 0; + } + starterHeldItems.add(heldItem); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + return starterHeldItems; + } + + @Override + public void setStarterHeldItems(List items) { + try { + byte[] staticCRO = readFile(romEntry.getFile("StaticPokemon")); + + List starterIndices = + Arrays.stream(romEntry.arrayEntries.get("StarterIndices")).boxed().collect(Collectors.toList()); + + // Gift Pokemon + int count = Gen6Constants.getGiftPokemonCount(romEntry.romType); + int size = Gen6Constants.getGiftPokemonSize(romEntry.romType); + int offset = romEntry.getInt("GiftPokemonOffset"); + + Iterator itemsIter = items.iterator(); + + for (int i = 0; i < count; i++) { + if (!starterIndices.contains(i)) continue; + int item = itemsIter.next(); + if (item == 0) { + FileFunctions.writeFullInt(staticCRO,offset+i*size + 12,-1); + } else { + FileFunctions.writeFullInt(staticCRO,offset+i*size + 12,item); + } + } + writeFile(romEntry.getFile("StaticPokemon"),staticCRO); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getMoves() { + return Arrays.asList(moves); + } + + @Override + public List getEncounters(boolean useTimeOfDay) { + if (!loadedWildMapNames) { + loadWildMapNames(); + } + try { + if (romEntry.romType == Gen6Constants.Type_ORAS) { + return getEncountersORAS(); + } else { + return getEncountersXY(); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private List getEncountersXY() throws IOException { + GARCArchive encounterGarc = readGARC(romEntry.getFile("WildPokemon"), false); + List encounters = new ArrayList<>(); + for (int i = 0; i < encounterGarc.files.size() - 1; i++) { + byte[] b = encounterGarc.files.get(i).get(0); + if (!wildMapNames.containsKey(i)) { + wildMapNames.put(i, "? Unknown ?"); + } + String mapName = wildMapNames.get(i); + int offset = FileFunctions.readFullInt(b, 0x10) + 0x10; + int length = b.length - offset; + if (length < 0x178) { // No encounters in this map + continue; + } + byte[] encounterData = new byte[0x178]; + System.arraycopy(b, offset, encounterData, 0, 0x178); + + // TODO: Is there some rate we can check like in older gens? + // First, 12 grass encounters, 12 rough terrain encounters, and 12 encounters each for yellow/purple/red flowers + EncounterSet grassEncounters = readEncounter(encounterData, 0, 12); + if (grassEncounters.encounters.size() > 0) { + grassEncounters.displayName = mapName + " Grass/Cave"; + encounters.add(grassEncounters); + } + EncounterSet yellowFlowerEncounters = readEncounter(encounterData, 48, 12); + if (yellowFlowerEncounters.encounters.size() > 0) { + yellowFlowerEncounters.displayName = mapName + " Yellow Flowers"; + encounters.add(yellowFlowerEncounters); + } + EncounterSet purpleFlowerEncounters = readEncounter(encounterData, 96, 12); + if (purpleFlowerEncounters.encounters.size() > 0) { + purpleFlowerEncounters.displayName = mapName + " Purple Flowers"; + encounters.add(purpleFlowerEncounters); + } + EncounterSet redFlowerEncounters = readEncounter(encounterData, 144, 12); + if (redFlowerEncounters.encounters.size() > 0) { + redFlowerEncounters.displayName = mapName + " Red Flowers"; + encounters.add(redFlowerEncounters); + } + EncounterSet roughTerrainEncounters = readEncounter(encounterData, 192, 12); + if (roughTerrainEncounters.encounters.size() > 0) { + roughTerrainEncounters.displayName = mapName + " Rough Terrain/Tall Grass"; + encounters.add(roughTerrainEncounters); + } + + // 5 surf and 5 rock smash encounters + EncounterSet surfEncounters = readEncounter(encounterData, 240, 5); + if (surfEncounters.encounters.size() > 0) { + surfEncounters.displayName = mapName + " Surf"; + encounters.add(surfEncounters); + } + EncounterSet rockSmashEncounters = readEncounter(encounterData, 260, 5); + if (rockSmashEncounters.encounters.size() > 0) { + rockSmashEncounters.displayName = mapName + " Rock Smash"; + encounters.add(rockSmashEncounters); + } + + // 3 Encounters for each type of rod + EncounterSet oldRodEncounters = readEncounter(encounterData, 280, 3); + if (oldRodEncounters.encounters.size() > 0) { + oldRodEncounters.displayName = mapName + " Old Rod"; + encounters.add(oldRodEncounters); + } + EncounterSet goodRodEncounters = readEncounter(encounterData, 292, 3); + if (goodRodEncounters.encounters.size() > 0) { + goodRodEncounters.displayName = mapName + " Good Rod"; + encounters.add(goodRodEncounters); + } + EncounterSet superRodEncounters = readEncounter(encounterData, 304, 3); + if (superRodEncounters.encounters.size() > 0) { + superRodEncounters.displayName = mapName + " Super Rod"; + encounters.add(superRodEncounters); + } + + // Lastly, 5 for each kind of Horde + EncounterSet hordeCommonEncounters = readEncounter(encounterData, 316, 5); + if (hordeCommonEncounters.encounters.size() > 0) { + hordeCommonEncounters.displayName = mapName + " Common Horde"; + encounters.add(hordeCommonEncounters); + } + EncounterSet hordeUncommonEncounters = readEncounter(encounterData, 336, 5); + if (hordeUncommonEncounters.encounters.size() > 0) { + hordeUncommonEncounters.displayName = mapName + " Uncommon Horde"; + encounters.add(hordeUncommonEncounters); + } + EncounterSet hordeRareEncounters = readEncounter(encounterData, 356, 5); + if (hordeRareEncounters.encounters.size() > 0) { + hordeRareEncounters.displayName = mapName + " Rare Horde"; + encounters.add(hordeRareEncounters); + } + } + + // The ceiling/flying/rustling bush encounters are hardcoded in the Field CRO + byte[] fieldCRO = readFile(romEntry.getFile("Field")); + String currentName = Gen6Constants.fallingEncounterNameMap.get(0); + int startingOffsetOfCurrentName = 0; + for (int i = 0; i < Gen6Constants.fallingEncounterCount; i++) { + int offset = Gen6Constants.fallingEncounterOffset + i * Gen6Constants.fieldEncounterSize; + EncounterSet fallingEncounter = readFieldEncounter(fieldCRO, offset); + if (Gen6Constants.fallingEncounterNameMap.containsKey(i)) { + currentName = Gen6Constants.fallingEncounterNameMap.get(i); + startingOffsetOfCurrentName = i; + } + int encounterNumber = (i - startingOffsetOfCurrentName) + 1; + fallingEncounter.displayName = currentName + " #" + encounterNumber; + encounters.add(fallingEncounter); + } + currentName = Gen6Constants.rustlingBushEncounterNameMap.get(0); + startingOffsetOfCurrentName = 0; + for (int i = 0; i < Gen6Constants.rustlingBushEncounterCount; i++) { + int offset = Gen6Constants.rustlingBushEncounterOffset + i * Gen6Constants.fieldEncounterSize; + EncounterSet rustlingBushEncounter = readFieldEncounter(fieldCRO, offset); + if (Gen6Constants.rustlingBushEncounterNameMap.containsKey(i)) { + currentName = Gen6Constants.rustlingBushEncounterNameMap.get(i); + startingOffsetOfCurrentName = i; + } + int encounterNumber = (i - startingOffsetOfCurrentName) + 1; + rustlingBushEncounter.displayName = currentName + " #" + encounterNumber; + encounters.add(rustlingBushEncounter); + } + return encounters; + } + + private List getEncountersORAS() throws IOException { + GARCArchive encounterGarc = readGARC(romEntry.getFile("WildPokemon"), false); + List encounters = new ArrayList<>(); + for (int i = 0; i < encounterGarc.files.size() - 2; i++) { + byte[] b = encounterGarc.files.get(i).get(0); + if (!wildMapNames.containsKey(i)) { + wildMapNames.put(i, "? Unknown ?"); + } + String mapName = wildMapNames.get(i); + int offset = FileFunctions.readFullInt(b, 0x10) + 0xE; + int offset2 = FileFunctions.readFullInt(b, 0x14); + int length = offset2 - offset; + if (length < 0xF6) { // No encounters in this map + continue; + } + byte[] encounterData = new byte[0xF6]; + System.arraycopy(b, offset, encounterData, 0, 0xF6); + + // First, read 12 grass encounters and 12 long grass encounters + EncounterSet grassEncounters = readEncounter(encounterData, 0, 12); + if (grassEncounters.encounters.size() > 0) { + grassEncounters.displayName = mapName + " Grass/Cave"; + grassEncounters.offset = i; + encounters.add(grassEncounters); + } + EncounterSet longGrassEncounters = readEncounter(encounterData, 48, 12); + if (longGrassEncounters.encounters.size() > 0) { + longGrassEncounters.displayName = mapName + " Long Grass"; + longGrassEncounters.offset = i; + encounters.add(longGrassEncounters); + } + + // Now, 3 DexNav Foreign encounters + EncounterSet dexNavForeignEncounters = readEncounter(encounterData, 96, 3); + if (dexNavForeignEncounters.encounters.size() > 0) { + dexNavForeignEncounters.displayName = mapName + " DexNav Foreign Encounter"; + dexNavForeignEncounters.offset = i; + encounters.add(dexNavForeignEncounters); + } + + // 5 surf and 5 rock smash encounters + EncounterSet surfEncounters = readEncounter(encounterData, 108, 5); + if (surfEncounters.encounters.size() > 0) { + surfEncounters.displayName = mapName + " Surf"; + surfEncounters.offset = i; + encounters.add(surfEncounters); + } + EncounterSet rockSmashEncounters = readEncounter(encounterData, 128, 5); + if (rockSmashEncounters.encounters.size() > 0) { + rockSmashEncounters.displayName = mapName + " Rock Smash"; + rockSmashEncounters.offset = i; + encounters.add(rockSmashEncounters); + } + + // 3 Encounters for each type of rod + EncounterSet oldRodEncounters = readEncounter(encounterData, 148, 3); + if (oldRodEncounters.encounters.size() > 0) { + oldRodEncounters.displayName = mapName + " Old Rod"; + oldRodEncounters.offset = i; + encounters.add(oldRodEncounters); + } + EncounterSet goodRodEncounters = readEncounter(encounterData, 160, 3); + if (goodRodEncounters.encounters.size() > 0) { + goodRodEncounters.displayName = mapName + " Good Rod"; + goodRodEncounters.offset = i; + encounters.add(goodRodEncounters); + } + EncounterSet superRodEncounters = readEncounter(encounterData, 172, 3); + if (superRodEncounters.encounters.size() > 0) { + superRodEncounters.displayName = mapName + " Super Rod"; + superRodEncounters.offset = i; + encounters.add(superRodEncounters); + } + + // Lastly, 5 for each kind of Horde + EncounterSet hordeCommonEncounters = readEncounter(encounterData, 184, 5); + if (hordeCommonEncounters.encounters.size() > 0) { + hordeCommonEncounters.displayName = mapName + " Common Horde"; + hordeCommonEncounters.offset = i; + encounters.add(hordeCommonEncounters); + } + EncounterSet hordeUncommonEncounters = readEncounter(encounterData, 204, 5); + if (hordeUncommonEncounters.encounters.size() > 0) { + hordeUncommonEncounters.displayName = mapName + " Uncommon Horde"; + hordeUncommonEncounters.offset = i; + encounters.add(hordeUncommonEncounters); + } + EncounterSet hordeRareEncounters = readEncounter(encounterData, 224, 5); + if (hordeRareEncounters.encounters.size() > 0) { + hordeRareEncounters.displayName = mapName + " Rare Horde"; + hordeRareEncounters.offset = i; + encounters.add(hordeRareEncounters); + } + } + return encounters; + } + + private EncounterSet readEncounter(byte[] data, int offset, int amount) { + EncounterSet es = new EncounterSet(); + es.rate = 1; + for (int i = 0; i < amount; i++) { + int species = readWord(data, offset + i * 4) & 0x7FF; + int forme = readWord(data, offset + i * 4) >> 11; + if (species != 0) { + Encounter e = new Encounter(); + Pokemon baseForme = pokes[species]; + + // If the forme is purely cosmetic, just use the base forme as the Pokemon + // for this encounter (the cosmetic forme will be stored in the encounter). + // Do the same for formes 30 and 31, because they actually aren't formes, but + // rather act as indicators for what forme should appear when encountered: + // 30 = Spawn the cosmetic forme specific to the user's region (Scatterbug line) + // 31 = Spawn *any* cosmetic forme with equal probability (Unown Mirage Cave) + if (forme <= baseForme.cosmeticForms || forme == 30 || forme == 31) { + e.pokemon = pokes[species]; + } else { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(species, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + e.pokemon = pokes[speciesWithForme]; + } + e.formeNumber = forme; + e.level = data[offset + 2 + i * 4]; + e.maxLevel = data[offset + 3 + i * 4]; + es.encounters.add(e); + } + } + return es; + } + + private EncounterSet readFieldEncounter(byte[] data, int offset) { + EncounterSet es = new EncounterSet(); + for (int i = 0; i < 7; i++) { + int species = readWord(data, offset + 4 + i * 8); + int level = data[offset + 8 + i * 8]; + if (species != 0) { + Encounter e = new Encounter(); + e.pokemon = pokes[species]; + e.formeNumber = 0; + e.level = level; + e.maxLevel = level; + es.encounters.add(e); + } + } + return es; + } + + @Override + public void setEncounters(boolean useTimeOfDay, List encountersList) { + try { + if (romEntry.romType == Gen6Constants.Type_ORAS) { + setEncountersORAS(encountersList); + } else { + setEncountersXY(encountersList); + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + } + + private void setEncountersXY(List encountersList) throws IOException { + String encountersFile = romEntry.getFile("WildPokemon"); + GARCArchive encounterGarc = readGARC(encountersFile, false); + Iterator encounters = encountersList.iterator(); + for (int i = 0; i < encounterGarc.files.size() - 1; i++) { + byte[] b = encounterGarc.files.get(i).get(0); + int offset = FileFunctions.readFullInt(b, 0x10) + 0x10; + int length = b.length - offset; + if (length < 0x178) { // No encounters in this map + continue; + } + byte[] encounterData = new byte[0x178]; + System.arraycopy(b, offset, encounterData, 0, 0x178); + + // First, 12 grass encounters, 12 rough terrain encounters, and 12 encounters each for yellow/purple/red flowers + if (readEncounter(encounterData, 0, 12).encounters.size() > 0) { + EncounterSet grass = encounters.next(); + writeEncounter(encounterData, 0, grass.encounters); + } + if (readEncounter(encounterData, 48, 12).encounters.size() > 0) { + EncounterSet yellowFlowers = encounters.next(); + writeEncounter(encounterData, 48, yellowFlowers.encounters); + } + if (readEncounter(encounterData, 96, 12).encounters.size() > 0) { + EncounterSet purpleFlowers = encounters.next(); + writeEncounter(encounterData, 96, purpleFlowers.encounters); + } + if (readEncounter(encounterData, 144, 12).encounters.size() > 0) { + EncounterSet redFlowers = encounters.next(); + writeEncounter(encounterData, 144, redFlowers.encounters); + } + if (readEncounter(encounterData, 192, 12).encounters.size() > 0) { + EncounterSet roughTerrain = encounters.next(); + writeEncounter(encounterData, 192, roughTerrain.encounters); + } + + // 5 surf and 5 rock smash encounters + if (readEncounter(encounterData, 240, 5).encounters.size() > 0) { + EncounterSet surf = encounters.next(); + writeEncounter(encounterData, 240, surf.encounters); + } + if (readEncounter(encounterData, 260, 5).encounters.size() > 0) { + EncounterSet rockSmash = encounters.next(); + writeEncounter(encounterData, 260, rockSmash.encounters); + } + + // 3 Encounters for each type of rod + if (readEncounter(encounterData, 280, 3).encounters.size() > 0) { + EncounterSet oldRod = encounters.next(); + writeEncounter(encounterData, 280, oldRod.encounters); + } + if (readEncounter(encounterData, 292, 3).encounters.size() > 0) { + EncounterSet goodRod = encounters.next(); + writeEncounter(encounterData, 292, goodRod.encounters); + } + if (readEncounter(encounterData, 304, 3).encounters.size() > 0) { + EncounterSet superRod = encounters.next(); + writeEncounter(encounterData, 304, superRod.encounters); + } + + // Lastly, 5 for each kind of Horde + if (readEncounter(encounterData, 316, 5).encounters.size() > 0) { + EncounterSet commonHorde = encounters.next(); + writeEncounter(encounterData, 316, commonHorde.encounters); + } + if (readEncounter(encounterData, 336, 5).encounters.size() > 0) { + EncounterSet uncommonHorde = encounters.next(); + writeEncounter(encounterData, 336, uncommonHorde.encounters); + } + if (readEncounter(encounterData, 356, 5).encounters.size() > 0) { + EncounterSet rareHorde = encounters.next(); + writeEncounter(encounterData, 356, rareHorde.encounters); + } + + // Write the encounter data back to the file + System.arraycopy(encounterData, 0, b, offset, 0x178); + } + + // Save + writeGARC(encountersFile, encounterGarc); + + // Now write the encounters hardcoded in the Field CRO + byte[] fieldCRO = readFile(romEntry.getFile("Field")); + for (int i = 0; i < Gen6Constants.fallingEncounterCount; i++) { + int offset = Gen6Constants.fallingEncounterOffset + i * Gen6Constants.fieldEncounterSize; + EncounterSet fallingEncounter = encounters.next(); + writeFieldEncounter(fieldCRO, offset, fallingEncounter.encounters); + } + for (int i = 0; i < Gen6Constants.rustlingBushEncounterCount; i++) { + int offset = Gen6Constants.rustlingBushEncounterOffset + i * Gen6Constants.fieldEncounterSize; + EncounterSet rustlingBushEncounter = encounters.next(); + writeFieldEncounter(fieldCRO, offset, rustlingBushEncounter.encounters); + } + + // Save + writeFile(romEntry.getFile("Field"), fieldCRO); + + this.updatePokedexAreaDataXY(encounterGarc, fieldCRO); + } + + private void setEncountersORAS(List encountersList) throws IOException { + String encountersFile = romEntry.getFile("WildPokemon"); + GARCArchive encounterGarc = readGARC(encountersFile, false); + Iterator encounters = encountersList.iterator(); + byte[] decStorage = encounterGarc.files.get(encounterGarc.files.size() - 1).get(0); + for (int i = 0; i < encounterGarc.files.size() - 2; i++) { + byte[] b = encounterGarc.files.get(i).get(0); + int offset = FileFunctions.readFullInt(b, 0x10) + 0xE; + int offset2 = FileFunctions.readFullInt(b, 0x14); + int length = offset2 - offset; + if (length < 0xF6) { // No encounters in this map + continue; + } + byte[] encounterData = new byte[0xF6]; + System.arraycopy(b, offset, encounterData, 0, 0xF6); + + // First, 12 grass encounters and 12 long grass encounters + if (readEncounter(encounterData, 0, 12).encounters.size() > 0) { + EncounterSet grass = encounters.next(); + writeEncounter(encounterData, 0, grass.encounters); + } + if (readEncounter(encounterData, 48, 12).encounters.size() > 0) { + EncounterSet longGrass = encounters.next(); + writeEncounter(encounterData, 48, longGrass.encounters); + } + + // Now, 3 DexNav Foreign encounters + if (readEncounter(encounterData, 96, 3).encounters.size() > 0) { + EncounterSet dexNav = encounters.next(); + writeEncounter(encounterData, 96, dexNav.encounters); + } + + // 5 surf and 5 rock smash encounters + if (readEncounter(encounterData, 108, 5).encounters.size() > 0) { + EncounterSet surf = encounters.next(); + writeEncounter(encounterData, 108, surf.encounters); + } + if (readEncounter(encounterData, 128, 5).encounters.size() > 0) { + EncounterSet rockSmash = encounters.next(); + writeEncounter(encounterData, 128, rockSmash.encounters); + } + + // 3 Encounters for each type of rod + if (readEncounter(encounterData, 148, 3).encounters.size() > 0) { + EncounterSet oldRod = encounters.next(); + writeEncounter(encounterData, 148, oldRod.encounters); + } + if (readEncounter(encounterData, 160, 3).encounters.size() > 0) { + EncounterSet goodRod = encounters.next(); + writeEncounter(encounterData, 160, goodRod.encounters); + } + if (readEncounter(encounterData, 172, 3).encounters.size() > 0) { + EncounterSet superRod = encounters.next(); + writeEncounter(encounterData, 172, superRod.encounters); + } + + // Lastly, 5 for each kind of Horde + if (readEncounter(encounterData, 184, 5).encounters.size() > 0) { + EncounterSet commonHorde = encounters.next(); + writeEncounter(encounterData, 184, commonHorde.encounters); + } + if (readEncounter(encounterData, 204, 5).encounters.size() > 0) { + EncounterSet uncommonHorde = encounters.next(); + writeEncounter(encounterData, 204, uncommonHorde.encounters); + } + if (readEncounter(encounterData, 224, 5).encounters.size() > 0) { + EncounterSet rareHorde = encounters.next(); + writeEncounter(encounterData, 224, rareHorde.encounters); + } + + // Write the encounter data back to the file + System.arraycopy(encounterData, 0, b, offset, 0xF6); + + // Also write the encounter data to the decStorage file + int decStorageOffset = FileFunctions.readFullInt(decStorage, (i + 1) * 4) + 0xE; + System.arraycopy(encounterData, 0, decStorage, decStorageOffset, 0xF4); + } + + // Save + writeGARC(encountersFile, encounterGarc); + + this.updatePokedexAreaDataORAS(encounterGarc); + } + + private void updatePokedexAreaDataXY(GARCArchive encounterGarc, byte[] fieldCRO) throws IOException { + byte[] pokedexAreaData = new byte[(Gen6Constants.pokemonCount + 1) * Gen6Constants.perPokemonAreaDataLengthXY]; + for (int i = 0; i < pokedexAreaData.length; i += Gen6Constants.perPokemonAreaDataLengthXY) { + // This byte is 0x10 for *every* Pokemon. Why? No clue, but let's copy it. + pokedexAreaData[i + 133] = 0x10; + } + int currentMapNum = 0; + + // Read all the "normal" encounters in the encounters GARC. + for (int i = 0; i < encounterGarc.files.size() - 1; i++) { + byte[] b = encounterGarc.files.get(i).get(0); + int offset = FileFunctions.readFullInt(b, 0x10) + 0x10; + int length = b.length - offset; + if (length < 0x178) { // No encounters in this map + continue; + } + int areaIndex = Gen6Constants.xyMapNumToPokedexIndex[currentMapNum]; + byte[] encounterData = new byte[0x178]; + System.arraycopy(b, offset, encounterData, 0, 0x178); + + EncounterSet grassEncounters = readEncounter(encounterData, 0, 12); + updatePokedexAreaDataFromEncounterSet(grassEncounters, pokedexAreaData, areaIndex, 0x1); + EncounterSet yellowFlowerEncounters = readEncounter(encounterData, 48, 12); + updatePokedexAreaDataFromEncounterSet(yellowFlowerEncounters, pokedexAreaData, areaIndex, 0x2); + EncounterSet purpleFlowerEncounters = readEncounter(encounterData, 96, 12); + updatePokedexAreaDataFromEncounterSet(purpleFlowerEncounters, pokedexAreaData, areaIndex, 0x4); + EncounterSet redFlowerEncounters = readEncounter(encounterData, 144, 12); + updatePokedexAreaDataFromEncounterSet(redFlowerEncounters, pokedexAreaData, areaIndex, 0x8); + EncounterSet roughTerrainEncounters = readEncounter(encounterData, 192, 12); + updatePokedexAreaDataFromEncounterSet(roughTerrainEncounters, pokedexAreaData, areaIndex, 0x10); + EncounterSet surfEncounters = readEncounter(encounterData, 240, 5); + updatePokedexAreaDataFromEncounterSet(surfEncounters, pokedexAreaData, areaIndex, 0x20); + EncounterSet rockSmashEncounters = readEncounter(encounterData, 260, 5); + updatePokedexAreaDataFromEncounterSet(rockSmashEncounters, pokedexAreaData, areaIndex, 0x40); + EncounterSet oldRodEncounters = readEncounter(encounterData, 280, 3); + updatePokedexAreaDataFromEncounterSet(oldRodEncounters, pokedexAreaData, areaIndex, 0x80); + EncounterSet goodRodEncounters = readEncounter(encounterData, 292, 3); + updatePokedexAreaDataFromEncounterSet(goodRodEncounters, pokedexAreaData, areaIndex, 0x100); + EncounterSet superRodEncounters = readEncounter(encounterData, 304, 3); + updatePokedexAreaDataFromEncounterSet(superRodEncounters, pokedexAreaData, areaIndex, 0x200); + EncounterSet hordeCommonEncounters = readEncounter(encounterData, 316, 5); + updatePokedexAreaDataFromEncounterSet(hordeCommonEncounters, pokedexAreaData, areaIndex, 0x400); + EncounterSet hordeUncommonEncounters = readEncounter(encounterData, 336, 5); + updatePokedexAreaDataFromEncounterSet(hordeUncommonEncounters, pokedexAreaData, areaIndex, 0x400); + EncounterSet hordeRareEncounters = readEncounter(encounterData, 356, 5); + updatePokedexAreaDataFromEncounterSet(hordeRareEncounters, pokedexAreaData, areaIndex, 0x400); + currentMapNum++; + } + + // Now read all the stuff that's hardcoded in the Field CRO + for (int i = 0; i < Gen6Constants.fallingEncounterCount; i++) { + int areaIndex = Gen6Constants.xyMapNumToPokedexIndex[currentMapNum]; + int offset = Gen6Constants.fallingEncounterOffset + i * Gen6Constants.fieldEncounterSize; + EncounterSet fallingEncounter = readFieldEncounter(fieldCRO, offset); + updatePokedexAreaDataFromEncounterSet(fallingEncounter, pokedexAreaData, areaIndex, 0x800); + currentMapNum++; + } + for (int i = 0; i < Gen6Constants.rustlingBushEncounterCount; i++) { + int areaIndex = Gen6Constants.xyMapNumToPokedexIndex[currentMapNum]; + int offset = Gen6Constants.rustlingBushEncounterOffset + i * Gen6Constants.fieldEncounterSize; + EncounterSet rustlingBushEncounter = readFieldEncounter(fieldCRO, offset); + updatePokedexAreaDataFromEncounterSet(rustlingBushEncounter, pokedexAreaData, areaIndex, 0x800); + currentMapNum++; + } + + // Write out the newly-created area data to the GARC + GARCArchive pokedexAreaGarc = readGARC(romEntry.getFile("PokedexAreaData"), true); + pokedexAreaGarc.setFile(0, pokedexAreaData); + writeGARC(romEntry.getFile("PokedexAreaData"), pokedexAreaGarc); + } + + private void updatePokedexAreaDataORAS(GARCArchive encounterGarc) throws IOException { + byte[] pokedexAreaData = new byte[(Gen6Constants.pokemonCount + 1) * Gen6Constants.perPokemonAreaDataLengthORAS]; + int currentMapNum = 0; + for (int i = 0; i < encounterGarc.files.size() - 2; i++) { + byte[] b = encounterGarc.files.get(i).get(0); + int offset = FileFunctions.readFullInt(b, 0x10) + 0xE; + int offset2 = FileFunctions.readFullInt(b, 0x14); + int length = offset2 - offset; + if (length < 0xF6) { // No encounters in this map + continue; + } + int areaIndex = Gen6Constants.orasMapNumToPokedexIndex[currentMapNum]; + if (areaIndex == -1) { // Current encounters are not taken into account for the Pokedex + currentMapNum++; + continue; + } + byte[] encounterData = new byte[0xF6]; + System.arraycopy(b, offset, encounterData, 0, 0xF6); + + EncounterSet grassEncounters = readEncounter(encounterData, 0, 12); + updatePokedexAreaDataFromEncounterSet(grassEncounters, pokedexAreaData, areaIndex, 0x1); + EncounterSet longGrassEncounters = readEncounter(encounterData, 48, 12); + updatePokedexAreaDataFromEncounterSet(longGrassEncounters, pokedexAreaData, areaIndex, 0x2); + int foreignEncounterType = grassEncounters.encounters.size() > 0 ? 0x04 : 0x08; + EncounterSet dexNavForeignEncounters = readEncounter(encounterData, 96, 3); + updatePokedexAreaDataFromEncounterSet(dexNavForeignEncounters, pokedexAreaData, areaIndex, foreignEncounterType); + EncounterSet surfEncounters = readEncounter(encounterData, 108, 5); + updatePokedexAreaDataFromEncounterSet(surfEncounters, pokedexAreaData, areaIndex, 0x10); + EncounterSet rockSmashEncounters = readEncounter(encounterData, 128, 5); + updatePokedexAreaDataFromEncounterSet(rockSmashEncounters, pokedexAreaData, areaIndex, 0x20); + EncounterSet oldRodEncounters = readEncounter(encounterData, 148, 3); + updatePokedexAreaDataFromEncounterSet(oldRodEncounters, pokedexAreaData, areaIndex, 0x40); + EncounterSet goodRodEncounters = readEncounter(encounterData, 160, 3); + updatePokedexAreaDataFromEncounterSet(goodRodEncounters, pokedexAreaData, areaIndex, 0x80); + EncounterSet superRodEncounters = readEncounter(encounterData, 172, 3); + updatePokedexAreaDataFromEncounterSet(superRodEncounters, pokedexAreaData, areaIndex, 0x100); + EncounterSet hordeCommonEncounters = readEncounter(encounterData, 184, 5); + updatePokedexAreaDataFromEncounterSet(hordeCommonEncounters, pokedexAreaData, areaIndex, 0x200); + EncounterSet hordeUncommonEncounters = readEncounter(encounterData, 204, 5); + updatePokedexAreaDataFromEncounterSet(hordeUncommonEncounters, pokedexAreaData, areaIndex, 0x200); + EncounterSet hordeRareEncounters = readEncounter(encounterData, 224, 5); + updatePokedexAreaDataFromEncounterSet(hordeRareEncounters, pokedexAreaData, areaIndex, 0x200); + currentMapNum++; + } + + GARCArchive pokedexAreaGarc = readGARC(romEntry.getFile("PokedexAreaData"), true); + pokedexAreaGarc.setFile(0, pokedexAreaData); + writeGARC(romEntry.getFile("PokedexAreaData"), pokedexAreaGarc); + } + + private void updatePokedexAreaDataFromEncounterSet(EncounterSet es, byte[] pokedexAreaData, int areaIndex, int encounterType) { + for (Encounter enc : es.encounters) { + Pokemon pkmn = enc.pokemon; + int perPokemonAreaDataLength = romEntry.romType == Gen6Constants.Type_XY ? + Gen6Constants.perPokemonAreaDataLengthXY : Gen6Constants.perPokemonAreaDataLengthORAS; + int offset = pkmn.getBaseNumber() * perPokemonAreaDataLength + areaIndex * 4; + int value = FileFunctions.readFullInt(pokedexAreaData, offset); + value |= encounterType; + FileFunctions.writeFullInt(pokedexAreaData, offset, value); + } + } + + private void writeEncounter(byte[] data, int offset, List encounters) { + for (int i = 0; i < encounters.size(); i++) { + Encounter encounter = encounters.get(i); + int speciesAndFormeData = (encounter.formeNumber << 11) + encounter.pokemon.getBaseNumber(); + writeWord(data, offset + i * 4, speciesAndFormeData); + data[offset + 2 + i * 4] = (byte) encounter.level; + data[offset + 3 + i * 4] = (byte) encounter.maxLevel; + } + } + + private void writeFieldEncounter(byte[] data, int offset, List encounters) { + for (int i = 0; i < encounters.size(); i++) { + Encounter encounter = encounters.get(i); + writeWord(data, offset + 4 + i * 8, encounter.pokemon.getBaseNumber()); + data[offset + 8 + i * 8] = (byte) encounter.level; + } + } + + private void loadWildMapNames() { + try { + wildMapNames = new HashMap<>(); + GARCArchive encounterGarc = this.readGARC(romEntry.getFile("WildPokemon"), false); + int zoneDataOffset = romEntry.getInt("MapTableFileOffset"); + byte[] zoneData = encounterGarc.files.get(zoneDataOffset).get(0); + List allMapNames = getStrings(false, romEntry.getInt("MapNamesTextOffset")); + for (int map = 0; map < zoneDataOffset; map++) { + int indexNum = (map * 56) + 0x1C; + int nameIndex1 = zoneData[indexNum] & 0xFF; + int nameIndex2 = 0x100 * ((int) (zoneData[indexNum + 1]) & 1); + String mapName = allMapNames.get(nameIndex1 + nameIndex2); + wildMapNames.put(map, mapName); + } + loadedWildMapNames = true; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getTrainers() { + List allTrainers = new ArrayList<>(); + boolean isORAS = romEntry.romType == Gen6Constants.Type_ORAS; + try { + GARCArchive trainers = this.readGARC(romEntry.getFile("TrainerData"),true); + GARCArchive trpokes = this.readGARC(romEntry.getFile("TrainerPokemon"),true); + int trainernum = trainers.files.size(); + List tclasses = this.getTrainerClassNames(); + List tnames = this.getTrainerNames(); + Map tnamesMap = new TreeMap<>(); + for (int i = 0; i < tnames.size(); i++) { + tnamesMap.put(i,tnames.get(i)); + } + for (int i = 1; i < trainernum; i++) { + // Trainer entries are 20 bytes in X/Y, 24 bytes in ORAS + // Team flags; 1 byte; 0x01 = custom moves, 0x02 = held item + // [ORAS only] 1 byte unused + // Class; 1 byte + // [ORAS only] 1 byte unknown + // [ORAS only] 2 bytes unused + // Battle Mode; 1 byte; 0=single, 1=double, 2=triple, 3=rotation, 4=??? + // Number of pokemon in team; 1 byte + // Items; 2 bytes each, 4 item slots + // AI Flags; 2 byte + // 3 bytes not used + // Victory Money; 1 byte; The money given out after defeat = + // 4 * this value * highest level poke in party + // Victory Item; 2 bytes; The item given out after defeat. + // In X/Y, these are berries, nuggets, pearls (e.g. Battle Chateau) + // In ORAS, none of these are set. + byte[] trainer = trainers.files.get(i).get(0); + byte[] trpoke = trpokes.files.get(i).get(0); + Trainer tr = new Trainer(); + tr.poketype = isORAS ? readWord(trainer,0) : trainer[0] & 0xFF; + tr.index = i; + tr.trainerclass = isORAS ? readWord(trainer,2) : trainer[1] & 0xFF; + int offset = isORAS ? 6 : 2; + int battleType = trainer[offset] & 0xFF; + int numPokes = trainer[offset+1] & 0xFF; + boolean healer = trainer[offset+13] != 0; + int pokeOffs = 0; + String trainerClass = tclasses.get(tr.trainerclass); + String trainerName = tnamesMap.getOrDefault(i - 1, "UNKNOWN"); + tr.fullDisplayName = trainerClass + " " + trainerName; + + for (int poke = 0; poke < numPokes; poke++) { + // Structure is + // ST SB LV LV SP SP FRM FRM + // (HI HI) + // (M1 M1 M2 M2 M3 M3 M4 M4) + // ST (strength) corresponds to the IVs of a trainer's pokemon. + // In ORAS, this value is like previous gens, a number 0-255 + // to represent 0 to 31 IVs. In the vanilla games, the top + // leaders/champions have 29. + // In X/Y, the bottom 5 bits are the IVs. It is unknown what + // the top 3 bits correspond to, perhaps EV spread? + // The second byte, SB = 0 0 Ab Ab 0 0 Fm Ml + // Ab Ab = ability number, 0 for random + // Fm = 1 for forced female + // Ml = 1 for forced male + // There's also a trainer flag to force gender, but + // this allows fixed teams with mixed genders. + + int level = readWord(trpoke, pokeOffs + 2); + int species = readWord(trpoke, pokeOffs + 4); + int formnum = readWord(trpoke, pokeOffs + 6); + TrainerPokemon tpk = new TrainerPokemon(); + tpk.level = level; + tpk.pokemon = pokes[species]; + tpk.strength = trpoke[pokeOffs]; + if (isORAS) { + tpk.IVs = (tpk.strength * 31 / 255); + } else { + tpk.IVs = tpk.strength & 0x1F; + } + int abilityAndFlag = trpoke[pokeOffs + 1]; + tpk.abilitySlot = (abilityAndFlag >>> 4) & 0xF; + tpk.forcedGenderFlag = (abilityAndFlag & 0xF); + tpk.forme = formnum; + tpk.formeSuffix = Gen6Constants.getFormeSuffixByBaseForme(species,formnum); + pokeOffs += 8; + if (tr.pokemonHaveItems()) { + tpk.heldItem = readWord(trpoke, pokeOffs); + pokeOffs += 2; + tpk.hasMegaStone = Gen6Constants.isMegaStone(tpk.heldItem); + } + if (tr.pokemonHaveCustomMoves()) { + for (int move = 0; move < 4; move++) { + tpk.moves[move] = readWord(trpoke, pokeOffs + (move*2)); + } + pokeOffs += 8; + } + tr.pokemon.add(tpk); + } + allTrainers.add(tr); + } + if (romEntry.romType == Gen6Constants.Type_XY) { + Gen6Constants.tagTrainersXY(allTrainers); + Gen6Constants.setMultiBattleStatusXY(allTrainers); + } else { + Gen6Constants.tagTrainersORAS(allTrainers); + Gen6Constants.setMultiBattleStatusORAS(allTrainers); + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + return allTrainers; + } + + @Override + public List getMainPlaythroughTrainers() { + return new ArrayList<>(); + } + + @Override + public List getEliteFourTrainers(boolean isChallengeMode) { + return Arrays.stream(romEntry.arrayEntries.get("EliteFourIndices")).boxed().collect(Collectors.toList()); + } + + @Override + public List getEvolutionItems() { + return Gen6Constants.evolutionItems; + } + + @Override + public void setTrainers(List trainerData, boolean doubleBattleMode) { + Iterator allTrainers = trainerData.iterator(); + boolean isORAS = romEntry.romType == Gen6Constants.Type_ORAS; + try { + GARCArchive trainers = this.readGARC(romEntry.getFile("TrainerData"),true); + GARCArchive trpokes = this.readGARC(romEntry.getFile("TrainerPokemon"),true); + // Get current movesets in case we need to reset them for certain + // trainer mons. + Map> movesets = this.getMovesLearnt(); + int trainernum = trainers.files.size(); + for (int i = 1; i < trainernum; i++) { + byte[] trainer = trainers.files.get(i).get(0); + Trainer tr = allTrainers.next(); + // preserve original poketype for held item & moves + int offset = 0; + if (isORAS) { + writeWord(trainer,0,tr.poketype); + offset = 4; + } else { + trainer[0] = (byte) tr.poketype; + } + int numPokes = tr.pokemon.size(); + trainer[offset+3] = (byte) numPokes; + + if (doubleBattleMode) { + if (!tr.skipImportant()) { + if (trainer[offset+2] == 0) { + trainer[offset+2] = 1; + trainer[offset+12] |= 0x80; // Flag that needs to be set for trainers not to attack their own pokes + } + } + } + + int bytesNeeded = 8 * numPokes; + if (tr.pokemonHaveCustomMoves()) { + bytesNeeded += 8 * numPokes; + } + if (tr.pokemonHaveItems()) { + bytesNeeded += 2 * numPokes; + } + byte[] trpoke = new byte[bytesNeeded]; + int pokeOffs = 0; + Iterator tpokes = tr.pokemon.iterator(); + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon tp = tpokes.next(); + byte abilityAndFlag = (byte)((tp.abilitySlot << 4) | tp.forcedGenderFlag); + trpoke[pokeOffs] = (byte) tp.strength; + trpoke[pokeOffs + 1] = abilityAndFlag; + writeWord(trpoke, pokeOffs + 2, tp.level); + writeWord(trpoke, pokeOffs + 4, tp.pokemon.number); + writeWord(trpoke, pokeOffs + 6, tp.forme); + pokeOffs += 8; + if (tr.pokemonHaveItems()) { + writeWord(trpoke, pokeOffs, tp.heldItem); + pokeOffs += 2; + } + if (tr.pokemonHaveCustomMoves()) { + if (tp.resetMoves) { + int[] pokeMoves = RomFunctions.getMovesAtLevel(getAltFormeOfPokemon(tp.pokemon, tp.forme).number, movesets, tp.level); + for (int m = 0; m < 4; m++) { + writeWord(trpoke, pokeOffs + m * 2, pokeMoves[m]); + } + } else { + writeWord(trpoke, pokeOffs, tp.moves[0]); + writeWord(trpoke, pokeOffs + 2, tp.moves[1]); + writeWord(trpoke, pokeOffs + 4, tp.moves[2]); + writeWord(trpoke, pokeOffs + 6, tp.moves[3]); + } + pokeOffs += 8; + } + } + trpokes.setFile(i,trpoke); + } + this.writeGARC(romEntry.getFile("TrainerData"), trainers); + this.writeGARC(romEntry.getFile("TrainerPokemon"), trpokes); + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + } + + @Override + public Map> getMovesLearnt() { + Map> movesets = new TreeMap<>(); + try { + GARCArchive movesLearnt = this.readGARC(romEntry.getFile("PokemonMovesets"),true); + int formeCount = Gen6Constants.getFormeCount(romEntry.romType); +// int formeOffset = Gen5Constants.getFormeMovesetOffset(romEntry.romType); + for (int i = 1; i <= Gen6Constants.pokemonCount + formeCount; i++) { + Pokemon pkmn = pokes[i]; + byte[] movedata; +// if (i > Gen6Constants.pokemonCount) { +// movedata = movesLearnt.files.get(i + formeOffset); +// } else { +// movedata = movesLearnt.files.get(i); +// } + movedata = movesLearnt.files.get(i).get(0); + int moveDataLoc = 0; + List learnt = new ArrayList<>(); + while (readWord(movedata, moveDataLoc) != 0xFFFF || readWord(movedata, moveDataLoc + 2) != 0xFFFF) { + int move = readWord(movedata, moveDataLoc); + int level = readWord(movedata, moveDataLoc + 2); + MoveLearnt ml = new MoveLearnt(); + ml.level = level; + ml.move = move; + learnt.add(ml); + moveDataLoc += 4; + } + movesets.put(pkmn.number, learnt); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return movesets; + } + + @Override + public void setMovesLearnt(Map> movesets) { + try { + GARCArchive movesLearnt = readGARC(romEntry.getFile("PokemonMovesets"),true); + int formeCount = Gen6Constants.getFormeCount(romEntry.romType); +// int formeOffset = Gen6Constants.getFormeMovesetOffset(romEntry.romType); + for (int i = 1; i <= Gen6Constants.pokemonCount + formeCount; i++) { + Pokemon pkmn = pokes[i]; + List learnt = movesets.get(pkmn.number); + int sizeNeeded = learnt.size() * 4 + 4; + byte[] moveset = new byte[sizeNeeded]; + int j = 0; + for (; j < learnt.size(); j++) { + MoveLearnt ml = learnt.get(j); + writeWord(moveset, j * 4, ml.move); + writeWord(moveset, j * 4 + 2, ml.level); + } + writeWord(moveset, j * 4, 0xFFFF); + writeWord(moveset, j * 4 + 2, 0xFFFF); +// if (i > Gen5Constants.pokemonCount) { +// movesLearnt.files.set(i + formeOffset, moveset); +// } else { +// movesLearnt.files.set(i, moveset); +// } + movesLearnt.setFile(i, moveset); + } + // Save + this.writeGARC(romEntry.getFile("PokemonMovesets"), movesLearnt); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + @Override + public Map> getEggMoves() { + Map> eggMoves = new TreeMap<>(); + try { + GARCArchive eggMovesGarc = this.readGARC(romEntry.getFile("EggMoves"),true); + for (int i = 1; i <= Gen6Constants.pokemonCount; i++) { + Pokemon pkmn = pokes[i]; + byte[] movedata = eggMovesGarc.files.get(i).get(0); + int numberOfEggMoves = readWord(movedata, 0); + List moves = new ArrayList<>(); + for (int j = 0; j < numberOfEggMoves; j++) { + int move = readWord(movedata, 2 + (j * 2)); + moves.add(move); + } + eggMoves.put(pkmn.number, moves); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return eggMoves; + } + + @Override + public void setEggMoves(Map> eggMoves) { + try { + GARCArchive eggMovesGarc = this.readGARC(romEntry.getFile("EggMoves"), true); + for (int i = 1; i <= Gen6Constants.pokemonCount; i++) { + Pokemon pkmn = pokes[i]; + byte[] movedata = eggMovesGarc.files.get(i).get(0); + List moves = eggMoves.get(pkmn.number); + for (int j = 0; j < moves.size(); j++) { + writeWord(movedata, 2 + (j * 2), moves.get(j)); + } + } + // Save + this.writeGARC(romEntry.getFile("EggMoves"), eggMovesGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public boolean canChangeStaticPokemon() { + return romEntry.staticPokemonSupport; + } + + @Override + public boolean hasStaticAltFormes() { + return true; + } + + @Override + public List getStaticPokemon() { + List statics = new ArrayList<>(); + try { + byte[] staticCRO = readFile(romEntry.getFile("StaticPokemon")); + + // Static Pokemon + int count = Gen6Constants.getStaticPokemonCount(romEntry.romType); + int size = Gen6Constants.staticPokemonSize; + int offset = romEntry.getInt("StaticPokemonOffset"); + for (int i = 0; i < count; i++) { + StaticEncounter se = new StaticEncounter(); + int species = FileFunctions.read2ByteInt(staticCRO,offset+i*size); + Pokemon pokemon = pokes[species]; + int forme = staticCRO[offset+i*size + 2]; + if (forme > pokemon.cosmeticForms && forme != 30 && forme != 31) { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(species, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + pokemon = pokes[speciesWithForme]; + } + se.pkmn = pokemon; + se.forme = forme; + se.level = staticCRO[offset+i*size + 3]; + short heldItem = (short)FileFunctions.read2ByteInt(staticCRO,offset+i*size + 4); + if (heldItem < 0) { + heldItem = 0; + } + se.heldItem = heldItem; + statics.add(se); + } + + List skipStarters = + Arrays.stream(romEntry.arrayEntries.get("StarterIndices")).boxed().collect(Collectors.toList()); + + // Gift Pokemon + count = Gen6Constants.getGiftPokemonCount(romEntry.romType); + size = Gen6Constants.getGiftPokemonSize(romEntry.romType); + offset = romEntry.getInt("GiftPokemonOffset"); + for (int i = 0; i < count; i++) { + if (skipStarters.contains(i)) continue; + StaticEncounter se = new StaticEncounter(); + int species = FileFunctions.read2ByteInt(staticCRO,offset+i*size); + Pokemon pokemon = pokes[species]; + int forme = staticCRO[offset+i*size + 4]; + if (forme > pokemon.cosmeticForms && forme != 30 && forme != 31) { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(species, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + pokemon = pokes[speciesWithForme]; + } + se.pkmn = pokemon; + se.forme = forme; + se.level = staticCRO[offset+i*size + 5]; + int heldItem = FileFunctions.readFullInt(staticCRO,offset+i*size + 12); + if (heldItem < 0) { + heldItem = 0; + } + se.heldItem = heldItem; + if (romEntry.romType == Gen6Constants.Type_ORAS) { + int metLocation = FileFunctions.read2ByteInt(staticCRO, offset + i * size + 18); + if (metLocation == 0xEA64) { + se.isEgg = true; + } + } + statics.add(se); + } + + // X/Y Trash Can Pokemon + if (romEntry.romType == Gen6Constants.Type_XY) { + int tableBaseOffset = find(code, Gen6Constants.xyTrashEncountersTablePrefix); + if (tableBaseOffset > 0) { + tableBaseOffset += Gen6Constants.xyTrashEncountersTablePrefix.length() / 2; // because it was a prefix + statics.addAll(readTrashCanEncounterSet(tableBaseOffset, Gen6Constants.pokemonVillageGarbadorOffset, Gen6Constants.pokemonVillageGarbadorCount, true)); + statics.addAll(readTrashCanEncounterSet(tableBaseOffset, Gen6Constants.pokemonVillageBanetteOffset, Gen6Constants.pokemonVillageBanetteCount, true)); + statics.addAll(readTrashCanEncounterSet(tableBaseOffset, Gen6Constants.lostHotelGarbadorOffset, Gen6Constants.lostHotelGarbadorCount, true)); + statics.addAll(readTrashCanEncounterSet(tableBaseOffset, Gen6Constants.lostHotelTrubbishOffset, Gen6Constants.lostHotelTrubbishCount, true)); + statics.addAll(readTrashCanEncounterSet(tableBaseOffset, Gen6Constants.lostHotelRotomOffset, Gen6Constants.lostHotelRotomCount, false)); + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + consolidateLinkedEncounters(statics); + return statics; + } + + private void consolidateLinkedEncounters(List statics) { + List encountersToRemove = new ArrayList<>(); + for (Map.Entry entry : romEntry.linkedStaticOffsets.entrySet()) { + StaticEncounter baseEncounter = statics.get(entry.getKey()); + StaticEncounter linkedEncounter = statics.get(entry.getValue()); + baseEncounter.linkedEncounters.add(linkedEncounter); + encountersToRemove.add(linkedEncounter); + } + for (StaticEncounter encounter : encountersToRemove) { + statics.remove(encounter); + } + } + + private List readTrashCanEncounterSet(int tableBaseOffset, int offsetWithinTable, int count, + boolean consolidateSameSpeciesEncounters) { + List statics = new ArrayList<>(); + Map encounterSet = new HashMap<>(); + int offset = tableBaseOffset + (offsetWithinTable * Gen6Constants.xyTrashEncounterDataLength); + for (int i = offsetWithinTable; i < offsetWithinTable + count; i++) { + StaticEncounter se = readTrashCanEncounter(offset); + if (consolidateSameSpeciesEncounters && encounterSet.containsKey(se.pkmn)) { + StaticEncounter mainEncounter = encounterSet.get(se.pkmn); + mainEncounter.linkedEncounters.add(se); + } else { + statics.add(se); + encounterSet.put(se.pkmn, se); + } + offset += Gen6Constants.xyTrashEncounterDataLength; + } + return statics; + } + + private StaticEncounter readTrashCanEncounter(int offset) { + int species = FileFunctions.readFullInt(code, offset); + int forme = FileFunctions.readFullInt(code, offset + 4); + int level = FileFunctions.readFullInt(code, offset + 8); + StaticEncounter se = new StaticEncounter(); + Pokemon pokemon = pokes[species]; + if (forme > pokemon.cosmeticForms && forme != 30 && forme != 31) { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(species, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + pokemon = pokes[speciesWithForme]; + } + se.pkmn = pokemon; + se.forme = forme; + se.level = level; + return se; + } + + @Override + public boolean setStaticPokemon(List staticPokemon) { + // Static Pokemon + try { + byte[] staticCRO = readFile(romEntry.getFile("StaticPokemon")); + + unlinkStaticEncounters(staticPokemon); + Iterator staticIter = staticPokemon.iterator(); + + int staticCount = Gen6Constants.getStaticPokemonCount(romEntry.romType); + int size = Gen6Constants.staticPokemonSize; + int offset = romEntry.getInt("StaticPokemonOffset"); + for (int i = 0; i < staticCount; i++) { + StaticEncounter se = staticIter.next(); + writeWord(staticCRO,offset+i*size,se.pkmn.number); + staticCRO[offset+i*size + 2] = (byte)se.forme; + staticCRO[offset+i*size + 3] = (byte)se.level; + if (se.heldItem == 0) { + writeWord(staticCRO,offset+i*size + 4,-1); + } else { + writeWord(staticCRO,offset+i*size + 4,se.heldItem); + } + } + + List skipStarters = + Arrays.stream(romEntry.arrayEntries.get("StarterIndices")).boxed().collect(Collectors.toList()); + + // Gift Pokemon + int giftCount = Gen6Constants.getGiftPokemonCount(romEntry.romType); + size = Gen6Constants.getGiftPokemonSize(romEntry.romType); + offset = romEntry.getInt("GiftPokemonOffset"); + for (int i = 0; i < giftCount; i++) { + if (skipStarters.contains(i)) continue; + StaticEncounter se = staticIter.next(); + writeWord(staticCRO,offset+i*size,se.pkmn.number); + staticCRO[offset+i*size + 4] = (byte)se.forme; + staticCRO[offset+i*size + 5] = (byte)se.level; + if (se.heldItem == 0) { + FileFunctions.writeFullInt(staticCRO,offset+i*size + 12,-1); + } else { + FileFunctions.writeFullInt(staticCRO,offset+i*size + 12,se.heldItem); + } + } + writeFile(romEntry.getFile("StaticPokemon"),staticCRO); + + // X/Y Trash Can Pokemon + if (romEntry.romType == Gen6Constants.Type_XY) { + offset = find(code, Gen6Constants.xyTrashEncountersTablePrefix); + if (offset > 0) { + offset += Gen6Constants.xyTrashEncountersTablePrefix.length() / 2; // because it was a prefix + int currentCount = 0; + while (currentCount != Gen6Constants.xyTrashCanEncounterCount) { + StaticEncounter se = staticIter.next(); + FileFunctions.writeFullInt(code, offset, se.pkmn.getBaseNumber()); + FileFunctions.writeFullInt(code, offset + 4, se.forme); + FileFunctions.writeFullInt(code, offset + 8, se.level); + offset += Gen6Constants.xyTrashEncounterDataLength; + currentCount++; + for (int i = 0; i < se.linkedEncounters.size(); i++) { + StaticEncounter linkedEncounter = se.linkedEncounters.get(i); + FileFunctions.writeFullInt(code, offset, linkedEncounter.pkmn.getBaseNumber()); + FileFunctions.writeFullInt(code, offset + 4, linkedEncounter.forme); + FileFunctions.writeFullInt(code, offset + 8, linkedEncounter.level); + offset += Gen6Constants.xyTrashEncounterDataLength; + currentCount++; + } + } + } + } + + if (romEntry.romType == Gen6Constants.Type_XY) { + int[] boxLegendaryOffsets = romEntry.arrayEntries.get("BoxLegendaryOffsets"); + StaticEncounter boxLegendaryEncounter = staticPokemon.get(boxLegendaryOffsets[0]); + fixBoxLegendariesXY(boxLegendaryEncounter.pkmn.number); + setRoamersXY(staticPokemon); + } else { + StaticEncounter rayquazaEncounter = staticPokemon.get(romEntry.getInt("RayquazaEncounterNumber")); + fixRayquazaORAS(rayquazaEncounter.pkmn.number); + } + + return true; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void unlinkStaticEncounters(List statics) { + List offsetsToInsert = new ArrayList<>(); + for (Map.Entry entry : romEntry.linkedStaticOffsets.entrySet()) { + offsetsToInsert.add(entry.getValue()); + } + Collections.sort(offsetsToInsert); + for (Integer offsetToInsert : offsetsToInsert) { + statics.add(offsetToInsert, new StaticEncounter()); + } + for (Map.Entry entry : romEntry.linkedStaticOffsets.entrySet()) { + StaticEncounter baseEncounter = statics.get(entry.getKey()); + statics.set(entry.getValue(), baseEncounter.linkedEncounters.get(0)); + } + } + + private void fixBoxLegendariesXY(int boxLegendarySpecies) throws IOException { + // We need to edit the script file or otherwise the text will still say "Xerneas" or "Yveltal" + GARCArchive encounterGarc = readGARC(romEntry.getFile("WildPokemon"), false); + byte[] boxLegendaryRoomData = encounterGarc.getFile(Gen6Constants.boxLegendaryEncounterFileXY); + AMX localScript = new AMX(boxLegendaryRoomData, 1); + byte[] data = localScript.decData; + int[] boxLegendaryScriptOffsets = romEntry.arrayEntries.get("BoxLegendaryScriptOffsets"); + for (int i = 0; i < boxLegendaryScriptOffsets.length; i++) { + FileFunctions.write2ByteInt(data, boxLegendaryScriptOffsets[i], boxLegendarySpecies); + } + byte[] modifiedScript = localScript.getBytes(); + System.arraycopy(modifiedScript, 0, boxLegendaryRoomData, Gen6Constants.boxLegendaryLocalScriptOffsetXY, modifiedScript.length); + encounterGarc.setFile(Gen6Constants.boxLegendaryEncounterFileXY, boxLegendaryRoomData); + writeGARC(romEntry.getFile("WildPokemon"), encounterGarc); + + // We also need to edit DllField.cro so that the hardcoded checks for + // Xerneas's/Yveltal's ID will instead be checks for our randomized species ID. + byte[] staticCRO = readFile(romEntry.getFile("StaticPokemon")); + int functionOffset = find(staticCRO, Gen6Constants.boxLegendaryFunctionPrefixXY); + if (functionOffset > 0) { + functionOffset += Gen6Constants.boxLegendaryFunctionPrefixXY.length() / 2; // because it was a prefix + + // At multiple points in the function, the game calls pml::pokepara::CoreParam::GetMonNo + // and compares the result to r8; every single one of these comparisons is followed by a + // nop. However, the way in which the species ID is loaded into r8 differs depending on + // the game. We'd prefer to write the same assembly for both games, and there's a trick + // we can abuse to do so. Since the species ID is never used outside of this comparison, + // we can feel free to mutate it however we please. The below code allows us to write any + // arbitrary species ID and make the proper comparison like this: + // sub r0, r0, (speciesLower x 0x100) + // subs r0, r0, speciesUpper + int speciesUpper = boxLegendarySpecies & 0x00FF; + int speciesLower = (boxLegendarySpecies & 0xFF00) >> 8; + for (int i = 0; i < Gen6Constants.boxLegendaryCodeOffsetsXY.length; i++) { + int codeOffset = functionOffset + Gen6Constants.boxLegendaryCodeOffsetsXY[i]; + staticCRO[codeOffset] = (byte) speciesLower; + staticCRO[codeOffset + 1] = 0x0C; + staticCRO[codeOffset + 2] = 0x40; + staticCRO[codeOffset + 3] = (byte) 0xE2; + staticCRO[codeOffset + 4] = (byte) speciesUpper; + staticCRO[codeOffset + 5] = 0x00; + staticCRO[codeOffset + 6] = 0x50; + staticCRO[codeOffset + 7] = (byte) 0xE2; + } + } + writeFile(romEntry.getFile("StaticPokemon"), staticCRO); + } + + private void setRoamersXY(List staticPokemon) throws IOException { + int[] roamingLegendaryOffsets = romEntry.arrayEntries.get("RoamingLegendaryOffsets"); + StaticEncounter[] roamers = new StaticEncounter[roamingLegendaryOffsets.length]; + for (int i = 0; i < roamers.length; i++) { + roamers[i] = staticPokemon.get(roamingLegendaryOffsets[i]); + } + int roamerSpeciesOffset = find(code, Gen6Constants.xyRoamerSpeciesLocator); + int freeSpaceOffset = find(code, Gen6Constants.xyRoamerFreeSpacePostfix); + if (roamerSpeciesOffset > 0 && freeSpaceOffset > 0) { + // In order to make this code work with all versions of XY, we had to find the *end* of our free space. + // The beginning is five instructions back. + freeSpaceOffset -= 20; + + // The unmodified code looks like this: + // nop + // bl FUN_0041b710 + // nop + // nop + // b LAB_003b7d1c + // We want to move both branches to the top so that we have 12 bytes of space to work with. + // Start by moving "bl FUN_0041b710" up one instruction, making sure to adjust the branch accordingly. + code[freeSpaceOffset] = (byte)(code[freeSpaceOffset + 4] + 1); + code[freeSpaceOffset + 1] = code[freeSpaceOffset + 5]; + code[freeSpaceOffset + 2] = code[freeSpaceOffset + 6]; + code[freeSpaceOffset + 3] = code[freeSpaceOffset + 7]; + + // Now move "b LAB_003b7d1c" up three instructions, again adjusting the branch accordingly. + code[freeSpaceOffset + 4] = (byte)(code[freeSpaceOffset + 16] + 3); + code[freeSpaceOffset + 5] = code[freeSpaceOffset + 17]; + code[freeSpaceOffset + 6] = code[freeSpaceOffset + 18]; + code[freeSpaceOffset + 7] = code[freeSpaceOffset + 19]; + + // In the free space now opened up, write the three roamer species. + for (int i = 0; i < roamers.length; i++) { + int offset = freeSpaceOffset + 8 + (i * 4); + int species = roamers[i].pkmn.getBaseNumber(); + FileFunctions.writeFullInt(code, offset, species); + } + + // To load the species ID, the game currently does "moveq r4, #0x90" for Articuno and similar + // things for Zapdos and Moltres. Instead, just pc-relative load what we wrote before. The fact + // that we change the conditional moveq to the unconditional pc-relative load only matters for + // the case where the player's starter index is *not* 0, 1, or 2, but that can't happen outside + // of save editing. + for (int i = 0; i < roamers.length; i++) { + int offset = roamerSpeciesOffset + (i * 12); + code[offset] = (byte)(0xAC - (8 * i)); + code[offset + 1] = 0x41; + code[offset + 2] = (byte) 0x9F; + code[offset + 3] = (byte) 0xE5; + } + } + + // The level of the roamer is set by a separate function in DllField. + byte[] fieldCRO = readFile(romEntry.getFile("Field")); + int levelOffset = find(fieldCRO, Gen6Constants.xyRoamerLevelPrefix); + if (levelOffset > 0) { + levelOffset += Gen6Constants.xyRoamerLevelPrefix.length() / 2; // because it was a prefix + fieldCRO[levelOffset] = (byte) roamers[0].level; + } + writeFile(romEntry.getFile("Field"), fieldCRO); + + // We also need to change the Sea Spirit's Den script in order for it to spawn + // the correct static version of the roamer. + try { + GARCArchive encounterGarc = readGARC(romEntry.getFile("WildPokemon"), false); + byte[] seaSpiritsDenAreaFile = encounterGarc.getFile(Gen6Constants.seaSpiritsDenEncounterFileXY); + AMX seaSpiritsDenAreaScript = new AMX(seaSpiritsDenAreaFile, 1); + for (int i = 0; i < roamers.length; i++) { + int offset = Gen6Constants.seaSpiritsDenScriptOffsetsXY[i]; + int species = roamers[i].pkmn.getBaseNumber(); + FileFunctions.write2ByteInt(seaSpiritsDenAreaScript.decData, offset, species); + } + byte[] modifiedScript = seaSpiritsDenAreaScript.getBytes(); + System.arraycopy(modifiedScript, 0, seaSpiritsDenAreaFile, Gen6Constants.seaSpiritsDenLocalScriptOffsetXY, modifiedScript.length); + encounterGarc.setFile(Gen6Constants.seaSpiritsDenEncounterFileXY, seaSpiritsDenAreaFile); + writeGARC(romEntry.getFile("WildPokemon"), encounterGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void fixRayquazaORAS(int rayquazaEncounterSpecies) throws IOException { + // We need to edit the script file or otherwise the text will still say "Rayquaza" + int rayquazaScriptFile = romEntry.getInt("RayquazaEncounterScriptNumber"); + GARCArchive scriptGarc = readGARC(romEntry.getFile("Scripts"), true); + AMX rayquazaAMX = new AMX(scriptGarc.files.get(rayquazaScriptFile).get(0)); + byte[] data = rayquazaAMX.decData; + for (int i = 0; i < Gen6Constants.rayquazaScriptOffsetsORAS.length; i++) { + FileFunctions.write2ByteInt(data, Gen6Constants.rayquazaScriptOffsetsORAS[i], rayquazaEncounterSpecies); + } + scriptGarc.setFile(rayquazaScriptFile, rayquazaAMX.getBytes()); + writeGARC(romEntry.getFile("Scripts"), scriptGarc); + + // We also need to edit DllField.cro so that the hardcoded checks for Rayquaza's species + // ID will instead be checks for our randomized species ID. + byte[] staticCRO = readFile(romEntry.getFile("StaticPokemon")); + int functionOffset = find(staticCRO, Gen6Constants.rayquazaFunctionPrefixORAS); + if (functionOffset > 0) { + functionOffset += Gen6Constants.rayquazaFunctionPrefixORAS.length() / 2; // because it was a prefix + + // Every Rayquaza check consists of "cmp r0, #0x180" followed by a nop. Replace + // all three checks with a sub and subs instructions so that we can write any + // random species ID. + int speciesUpper = rayquazaEncounterSpecies & 0x00FF; + int speciesLower = (rayquazaEncounterSpecies & 0xFF00) >> 8; + for (int i = 0; i < Gen6Constants.rayquazaCodeOffsetsORAS.length; i++) { + int codeOffset = functionOffset + Gen6Constants.rayquazaCodeOffsetsORAS[i]; + staticCRO[codeOffset] = (byte) speciesLower; + staticCRO[codeOffset + 1] = 0x0C; + staticCRO[codeOffset + 2] = 0x40; + staticCRO[codeOffset + 3] = (byte) 0xE2; + staticCRO[codeOffset + 4] = (byte) speciesUpper; + staticCRO[codeOffset + 5] = 0x00; + staticCRO[codeOffset + 6] = 0x50; + staticCRO[codeOffset + 7] = (byte) 0xE2; + } + } + writeFile(romEntry.getFile("StaticPokemon"), staticCRO); + } + + @Override + public int miscTweaksAvailable() { + int available = 0; + available |= MiscTweak.FASTEST_TEXT.getValue(); + available |= MiscTweak.BAN_LUCKY_EGG.getValue(); + available |= MiscTweak.RETAIN_ALT_FORMES.getValue(); + available |= MiscTweak.NATIONAL_DEX_AT_START.getValue(); + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestText(); + } else if (tweak == MiscTweak.BAN_LUCKY_EGG) { + allowedItems.banSingles(Items.luckyEgg); + nonBadItems.banSingles(Items.luckyEgg); + } else if (tweak == MiscTweak.RETAIN_ALT_FORMES) { + try { + patchFormeReversion(); + } catch (IOException e) { + e.printStackTrace(); + } + } else if (tweak == MiscTweak.NATIONAL_DEX_AT_START) { + patchForNationalDex(); + } + } + + @Override + public boolean isEffectivenessUpdated() { + return false; + } + + private void applyFastestText() { + int offset = find(code, Gen6Constants.fastestTextPrefixes[0]); + if (offset > 0) { + offset += Gen6Constants.fastestTextPrefixes[0].length() / 2; // because it was a prefix + code[offset] = 0x03; + code[offset + 1] = 0x40; + code[offset + 2] = (byte) 0xA0; + code[offset + 3] = (byte) 0xE3; + } + offset = find(code, Gen6Constants.fastestTextPrefixes[1]); + if (offset > 0) { + offset += Gen6Constants.fastestTextPrefixes[1].length() / 2; // because it was a prefix + code[offset] = 0x03; + code[offset + 1] = 0x50; + code[offset + 2] = (byte) 0xA0; + code[offset + 3] = (byte) 0xE3; + } + } + + private void patchForNationalDex() { + int offset = find(code, Gen6Constants.nationalDexFunctionLocator); + if (offset > 0) { + // In Savedata::ZukanData::GetZenkokuZukanFlag, we load a flag into r0 and + // then AND it with 0x1 to get a boolean that determines if the player has + // the National Dex. The below code patches this piece of code so that + // instead of loading the flag, we simply "mov r0, #0x1". + code[offset] = 0x01; + code[offset + 1] = 0x00; + code[offset + 2] = (byte) 0xA0; + code[offset + 3] = (byte) 0xE3; + } + + if (romEntry.romType == Gen6Constants.Type_XY) { + offset = find(code, Gen6Constants.xyGetDexFlagFunctionLocator); + if (offset > 0) { + // In addition to the code listed above, XY also use a function that I'm + // calling Savedata::ZukanData::GetDexFlag(int) to determine what Pokedexes + // the player owns. It can be called with 0 (Central), 1 (Coastal), 2 (Mountain), + // or 3 (National). Since the player *always* has the Central Dex, the code has + // a short-circuit for it that looks like this: + // cmp r5, #0x0 + // moveq r0, #0x1 + // beq returnFromFunction + // The below code nops out that comparison and makes the move and branch instructions + // non-conditional; no matter what's on the save file, the player will have all dexes. + FileFunctions.writeFullInt(code, offset, 0); + code[offset + 7] = (byte) 0xE3; + code[offset + 11] = (byte) 0xEA; + } + } else { + // DllSangoZukan.cro will refuse to let you open either the Hoenn or National Pokedex if you have + // caught 0 Pokemon in the Hoenn Pokedex; it is unknown *how* it does this, though. Instead, let's + // just hack up the function that determines how many Pokemon in the Hoenn Pokedex you've caught so + // it returns 1 if you haven't caught anything. + offset = find(code, Gen6Constants.orasGetHoennDexCaughtFunctionPrefix); + if (offset > 0) { + offset += Gen6Constants.orasGetHoennDexCaughtFunctionPrefix.length() / 2; // because it was a prefix + + // At the start of the function, there's a check that the Zukan block on the save data is valid; + // this is obviously generated by either a macro or inlined function, since literally every function + // relating to the Pokedex has this too. First, it checks if the checksum is correct then does a beq + // to branch to the main body of the function; let's replace this with an unconditional branch. + code[offset + 31] = (byte) 0xEA; + + // Now, in the space where the function would normally handle the call to the assert function + // to crash the game if the checksum is invalid, we can write the following code: + // mov r0, r7 + // cmp r0, #0x0 + // moveq r0, #0x1 + // ldmia sp!,{r4 r5 r6 r7 r8 r9 r10 r11 r12 pc} + FileFunctions.writeFullIntBigEndian(code, offset + 32, 0x0700A0E1); + FileFunctions.writeFullIntBigEndian(code, offset + 36, 0x000050E3); + FileFunctions.writeFullIntBigEndian(code, offset + 40, 0x0100A003); + FileFunctions.writeFullIntBigEndian(code, offset + 44, 0xF09FBDE8); + + // At the end of the function, the game normally does "mov r0, r7" and then returns, where r7 + // contains the number of Pokemon caught in the Hoenn Pokedex. Instead, branch to the code we + // wrote above. + FileFunctions.writeFullIntBigEndian(code, offset + 208, 0xD2FFFFEA); + } + } + } + + @Override + public void enableGuaranteedPokemonCatching() { + try { + byte[] battleCRO = readFile(romEntry.getFile("Battle")); + int offset = find(battleCRO, Gen6Constants.perfectOddsBranchLocator); + if (offset > 0) { + // The game checks to see if your odds are greater then or equal to 255 using the following + // code. Note that they compare to 0xFF000 instead of 0xFF; it looks like all catching code + // probabilities are shifted like this? + // cmp r6, #0xFF000 + // blt oddsLessThanOrEqualTo254 + // The below code just nops the branch out so it always acts like our odds are 255, and + // Pokemon are automatically caught no matter what. + battleCRO[offset] = 0x00; + battleCRO[offset + 1] = 0x00; + battleCRO[offset + 2] = 0x00; + battleCRO[offset + 3] = 0x00; + writeFile(romEntry.getFile("Battle"), battleCRO); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getTMMoves() { + String tmDataPrefix = Gen6Constants.tmDataPrefix; + int offset = find(code, tmDataPrefix); + if (offset != 0) { + offset += Gen6Constants.tmDataPrefix.length() / 2; // because it was a prefix + List tms = new ArrayList<>(); + for (int i = 0; i < Gen6Constants.tmBlockOneCount; i++) { + tms.add(readWord(code, offset + i * 2)); + } + offset += (Gen6Constants.getTMBlockTwoStartingOffset(romEntry.romType) * 2); + for (int i = 0; i < (Gen6Constants.tmCount - Gen6Constants.tmBlockOneCount); i++) { + tms.add(readWord(code, offset + i * 2)); + } + return tms; + } else { + return null; + } + } + + @Override + public List getHMMoves() { + String tmDataPrefix = Gen6Constants.tmDataPrefix; + int offset = find(code, tmDataPrefix); + if (offset != 0) { + offset += Gen6Constants.tmDataPrefix.length() / 2; // because it was a prefix + offset += Gen6Constants.tmBlockOneCount * 2; // TM data + List hms = new ArrayList<>(); + for (int i = 0; i < Gen6Constants.hmBlockOneCount; i++) { + hms.add(readWord(code, offset + i * 2)); + } + if (romEntry.romType == Gen6Constants.Type_ORAS) { + hms.add(readWord(code, offset + Gen6Constants.rockSmashOffsetORAS)); + hms.add(readWord(code, offset + Gen6Constants.diveOffsetORAS)); + } + return hms; + } else { + return null; + } + } + + @Override + public void setTMMoves(List moveIndexes) { + String tmDataPrefix = Gen6Constants.tmDataPrefix; + int offset = find(code, tmDataPrefix); + if (offset > 0) { + offset += Gen6Constants.tmDataPrefix.length() / 2; // because it was a prefix + for (int i = 0; i < Gen6Constants.tmBlockOneCount; i++) { + writeWord(code, offset + i * 2, moveIndexes.get(i)); + } + offset += (Gen6Constants.getTMBlockTwoStartingOffset(romEntry.romType) * 2); + for (int i = 0; i < (Gen6Constants.tmCount - Gen6Constants.tmBlockOneCount); i++) { + writeWord(code, offset + i * 2, moveIndexes.get(i + Gen6Constants.tmBlockOneCount)); + } + + // Update TM item descriptions + List itemDescriptions = getStrings(false, romEntry.getInt("ItemDescriptionsTextOffset")); + List moveDescriptions = getStrings(false, romEntry.getInt("MoveDescriptionsTextOffset")); + // TM01 is item 328 and so on + for (int i = 0; i < Gen6Constants.tmBlockOneCount; i++) { + itemDescriptions.set(i + Gen6Constants.tmBlockOneOffset, moveDescriptions.get(moveIndexes.get(i))); + } + // TM93-95 are 618-620 + for (int i = 0; i < Gen6Constants.tmBlockTwoCount; i++) { + itemDescriptions.set(i + Gen6Constants.tmBlockTwoOffset, + moveDescriptions.get(moveIndexes.get(i + Gen6Constants.tmBlockOneCount))); + } + // TM96-100 are 690 and so on + for (int i = 0; i < Gen6Constants.tmBlockThreeCount; i++) { + itemDescriptions.set(i + Gen6Constants.tmBlockThreeOffset, + moveDescriptions.get(moveIndexes.get(i + Gen6Constants.tmBlockOneCount + Gen6Constants.tmBlockTwoCount))); + } + // Save the new item descriptions + setStrings(false, romEntry.getInt("ItemDescriptionsTextOffset"), itemDescriptions); + // Palettes + String palettePrefix = Gen6Constants.itemPalettesPrefix; + int offsPals = find(code, palettePrefix); + if (offsPals > 0) { + offsPals += Gen6Constants.itemPalettesPrefix.length() / 2; // because it was a prefix + // Write pals + for (int i = 0; i < Gen6Constants.tmBlockOneCount; i++) { + int itmNum = Gen6Constants.tmBlockOneOffset + i; + Move m = this.moves[moveIndexes.get(i)]; + int pal = this.typeTMPaletteNumber(m.type, false); + writeWord(code, offsPals + itmNum * 4, pal); + } + for (int i = 0; i < (Gen6Constants.tmBlockTwoCount); i++) { + int itmNum = Gen6Constants.tmBlockTwoOffset + i; + Move m = this.moves[moveIndexes.get(i + Gen6Constants.tmBlockOneCount)]; + int pal = this.typeTMPaletteNumber(m.type, false); + writeWord(code, offsPals + itmNum * 4, pal); + } + for (int i = 0; i < (Gen6Constants.tmBlockThreeCount); i++) { + int itmNum = Gen6Constants.tmBlockThreeOffset + i; + Move m = this.moves[moveIndexes.get(i + Gen6Constants.tmBlockOneCount + Gen6Constants.tmBlockTwoCount)]; + int pal = this.typeTMPaletteNumber(m.type, false); + writeWord(code, offsPals + itmNum * 4, pal); + } + } + } + } + + private int find(byte[] data, String hexString) { + if (hexString.length() % 2 != 0) { + return -3; // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + List found = RomFunctions.search(data, searchFor); + if (found.size() == 0) { + return -1; // not found + } else if (found.size() > 1) { + return -2; // not unique + } else { + return found.get(0); + } + } + + @Override + public int getTMCount() { + return Gen6Constants.tmCount; + } + + @Override + public int getHMCount() { + return Gen6Constants.getHMCount(romEntry.romType); + } + + @Override + public Map getTMHMCompatibility() { + Map compat = new TreeMap<>(); + int formeCount = Gen6Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= Gen6Constants.pokemonCount + formeCount; i++) { + byte[] data; + data = pokeGarc.files.get(i).get(0); + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen6Constants.tmCount + Gen6Constants.getHMCount(romEntry.romType) + 1]; + for (int j = 0; j < 14; j++) { + readByteIntoFlags(data, flags, j * 8 + 1, Gen6Constants.bsTMHMCompatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map compatData) { + for (Map.Entry compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + byte[] data = pokeGarc.files.get(pkmn.number).get(0); + for (int j = 0; j < 14; j++) { + data[Gen6Constants.bsTMHMCompatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public boolean hasMoveTutors() { + return romEntry.romType == Gen6Constants.Type_ORAS; + } + + @Override + public List getMoveTutorMoves() { + List mtMoves = new ArrayList<>(); + + int mtOffset = getMoveTutorMovesOffset(); + if (mtOffset > 0) { + int val = 0; + while (val != 0xFFFF) { + val = FileFunctions.read2ByteInt(code,mtOffset); + mtOffset += 2; + if (val == 0x26E || val == 0xFFFF) continue; + mtMoves.add(val); + } + } + + return mtMoves; + } + + @Override + public void setMoveTutorMoves(List moves) { + + int mtOffset = find(code, Gen6Constants.tutorsShopPrefix); + if (mtOffset > 0) { + mtOffset += Gen6Constants.tutorsShopPrefix.length() / 2; // because it was a prefix + for (int i = 0; i < Gen6Constants.tutorMoveCount; i++) { + FileFunctions.write2ByteInt(code,mtOffset + i*8, moves.get(i)); + } + } + + mtOffset = getMoveTutorMovesOffset(); + if (mtOffset > 0) { + for (int move: moves) { + int val = FileFunctions.read2ByteInt(code,mtOffset); + if (val == 0x26E) mtOffset += 2; + FileFunctions.write2ByteInt(code,mtOffset,move); + mtOffset += 2; + } + } + } + + private int getMoveTutorMovesOffset() { + int offset = moveTutorMovesOffset; + if (offset == 0) { + offset = find(code, Gen6Constants.tutorsLocator); + moveTutorMovesOffset = offset; + } + return offset; + } + + @Override + public Map getMoveTutorCompatibility() { + Map compat = new TreeMap<>(); + int[] sizes = Gen6Constants.tutorSize; + int formeCount = Gen6Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= Gen6Constants.pokemonCount + formeCount; i++) { + byte[] data; + data = pokeGarc.files.get(i).get(0); + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Arrays.stream(sizes).sum() + 1]; + int offset = 0; + for (int mt = 0; mt < 4; mt++) { + for (int j = 0; j < 4; j++) { + readByteIntoFlags(data, flags, offset + j * 8 + 1, Gen6Constants.bsMTCompatOffset + mt * 4 + j); + } + offset += sizes[mt]; + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setMoveTutorCompatibility(Map compatData) { + if (!hasMoveTutors()) return; + int[] sizes = Gen6Constants.tutorSize; + int formeCount = Gen6Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= Gen6Constants.pokemonCount + formeCount; i++) { + byte[] data; + data = pokeGarc.files.get(i).get(0); + Pokemon pkmn = pokes[i]; + boolean[] flags = compatData.get(pkmn); + int offset = 0; + for (int mt = 0; mt < 4; mt++) { + boolean[] mtflags = new boolean[sizes[mt] + 1]; + System.arraycopy(flags, offset + 1, mtflags, 1, sizes[mt]); + for (int j = 0; j < 4; j++) { + data[Gen6Constants.bsMTCompatOffset + mt * 4 + j] = getByteFromFlags(mtflags, j * 8 + 1); + } + offset += sizes[mt]; + } + } + } + + @Override + public String getROMName() { + return "Pokemon " + romEntry.name; + } + + @Override + public String getROMCode() { + return romEntry.romCode; + } + + @Override + public String getSupportLevel() { + return "Complete"; + } + + @Override + public boolean hasTimeBasedEncounters() { + return false; + } + + @Override + public List getMovesBannedFromLevelup() { + return Gen6Constants.bannedMoves; + } + + @Override + public boolean hasWildAltFormes() { + return true; + } + + @Override + public List bannedForStaticPokemon() { + return Gen6Constants.actuallyCosmeticForms + .stream() + .filter(index -> index < Gen6Constants.pokemonCount + Gen6Constants.getFormeCount(romEntry.romType)) + .map(index -> pokes[index]) + .collect(Collectors.toList()); + } + + @Override + public boolean forceSwapStaticMegaEvos() { + return romEntry.romType == Gen6Constants.Type_XY; + } + + @Override + public boolean hasMainGameLegendaries() { + return true; + } + + @Override + public List getMainGameLegendaries() { + return Arrays.stream(romEntry.arrayEntries.get("MainGameLegendaries")).boxed().collect(Collectors.toList()); + } + + @Override + public List getSpecialMusicStatics() { + return new ArrayList<>(); + } + + @Override + public void applyCorrectStaticMusic(Map specialMusicStaticChanges) { + + } + + @Override + public boolean hasStaticMusicFix() { + return false; + } + + @Override + public List getTotemPokemon() { + return new ArrayList<>(); + } + + @Override + public void setTotemPokemon(List totemPokemon) { + + } + + @Override + public void removeImpossibleEvolutions(Settings settings) { + boolean changeMoveEvos = !(settings.getMovesetsMod() == Settings.MovesetsMod.UNCHANGED); + + Map> movesets = this.getMovesLearnt(); + Set extraEvolutions = new HashSet<>(); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + extraEvolutions.clear(); + for (Evolution evo : pkmn.evolutionsFrom) { + if (changeMoveEvos && evo.type == EvolutionType.LEVEL_WITH_MOVE) { + // read move + int move = evo.extraInfo; + int levelLearntAt = 1; + for (MoveLearnt ml : movesets.get(evo.from.number)) { + if (ml.move == move) { + levelLearntAt = ml.level; + break; + } + } + if (levelLearntAt == 1) { + // override for piloswine + levelLearntAt = 45; + } + // change to pure level evo + evo.type = EvolutionType.LEVEL; + evo.extraInfo = levelLearntAt; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // Pure Trade + if (evo.type == EvolutionType.TRADE) { + // Replace w/ level 37 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // Trade w/ Item + if (evo.type == EvolutionType.TRADE_ITEM) { + // Get the current item & evolution + int item = evo.extraInfo; + if (evo.from.number == Species.slowpoke) { + // Slowpoke is awkward - he already has a level evo + // So we can't do Level up w/ Held Item for him + // Put Water Stone instead + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.waterStone; + addEvoUpdateStone(impossibleEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + addEvoUpdateHeldItem(impossibleEvolutionUpdates, evo, itemNames.get(item)); + // Replace, for this entry, w/ + // Level up w/ Held Item at Day + evo.type = EvolutionType.LEVEL_ITEM_DAY; + // now add an extra evo for + // Level up w/ Held Item at Night + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_NIGHT, item); + extraEvolutions.add(extraEntry); + } + } + if (evo.type == EvolutionType.TRADE_SPECIAL) { + // This is the karrablast <-> shelmet trade + // Replace it with Level up w/ Other Species in Party + // (22) + // Based on what species we're currently dealing with + evo.type = EvolutionType.LEVEL_WITH_OTHER; + evo.extraInfo = (evo.from.number == Species.karrablast ? Species.shelmet : Species.karrablast); + addEvoUpdateParty(impossibleEvolutionUpdates, evo, pokes[evo.extraInfo].fullName()); + } + // TBD: Pancham, Sliggoo? Sylveon? + } + + pkmn.evolutionsFrom.addAll(extraEvolutions); + for (Evolution ev : extraEvolutions) { + ev.to.evolutionsTo.add(ev); + } + } + } + + } + + @Override + public void makeEvolutionsEasier(Settings settings) { + boolean wildsRandomized = !settings.getWildPokemonMod().equals(Settings.WildPokemonMod.UNCHANGED); + + // Reduce the amount of happiness required to evolve. + int offset = find(code, Gen6Constants.friendshipValueForEvoLocator); + if (offset > 0) { + // Amount of required happiness for HAPPINESS evolutions. + if (code[offset] == (byte)220) { + code[offset] = (byte)160; + } + // Amount of required happiness for HAPPINESS_DAY evolutions. + if (code[offset + 12] == (byte)220) { + code[offset + 12] = (byte)160; + } + // Amount of required happiness for HAPPINESS_NIGHT evolutions. + if (code[offset + 36] == (byte)220) { + code[offset + 36] = (byte)160; + } + } + + if (wildsRandomized) { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + for (Evolution evo : pkmn.evolutionsFrom) { + if (evo.type == EvolutionType.LEVEL_WITH_OTHER) { + // Replace w/ level 35 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 35; + addEvoUpdateCondensed(easierEvolutionUpdates, evo, false); + } + } + } + } + } + } + + @Override + public void removeTimeBasedEvolutions() { + Set extraEvolutions = new HashSet<>(); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + extraEvolutions.clear(); + for (Evolution evo : pkmn.evolutionsFrom) { + if (evo.type == EvolutionType.HAPPINESS_DAY) { + if (evo.from.number == Species.eevee) { + // We can't set Eevee to evolve into Espeon with happiness at night because that's how + // Umbreon works in the original game. Instead, make Eevee: == sun stone => Espeon + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.sunStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + // Add an extra evo for Happiness at Night + addEvoUpdateHappiness(timeBasedEvolutionUpdates, evo); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.HAPPINESS_NIGHT, 0); + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.HAPPINESS_NIGHT) { + if (evo.from.number == Species.eevee) { + // We can't set Eevee to evolve into Umbreon with happiness at day because that's how + // Espeon works in the original game. Instead, make Eevee: == moon stone => Umbreon + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.moonStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + // Add an extra evo for Happiness at Day + addEvoUpdateHappiness(timeBasedEvolutionUpdates, evo); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.HAPPINESS_DAY, 0); + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.LEVEL_ITEM_DAY) { + int item = evo.extraInfo; + // Make sure we don't already have an evo for the same item at night (e.g., when using Change Impossible Evos) + if (evo.from.evolutionsFrom.stream().noneMatch(e -> e.type == EvolutionType.LEVEL_ITEM_NIGHT && e.extraInfo == item)) { + // Add an extra evo for Level w/ Item During Night + addEvoUpdateHeldItem(timeBasedEvolutionUpdates, evo, itemNames.get(item)); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_NIGHT, item); + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.LEVEL_ITEM_NIGHT) { + int item = evo.extraInfo; + // Make sure we don't already have an evo for the same item at day (e.g., when using Change Impossible Evos) + if (evo.from.evolutionsFrom.stream().noneMatch(e -> e.type == EvolutionType.LEVEL_ITEM_DAY && e.extraInfo == item)) { + // Add an extra evo for Level w/ Item During Day + addEvoUpdateHeldItem(timeBasedEvolutionUpdates, evo, itemNames.get(item)); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_DAY, item); + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.LEVEL_DAY || evo.type == EvolutionType.LEVEL_NIGHT) { + addEvoUpdateLevel(timeBasedEvolutionUpdates, evo); + evo.type = EvolutionType.LEVEL; + } + } + pkmn.evolutionsFrom.addAll(extraEvolutions); + for (Evolution ev : extraEvolutions) { + ev.to.evolutionsTo.add(ev); + } + } + } + + } + + @Override + public boolean hasShopRandomization() { + return true; + } + + @Override + public boolean canChangeTrainerText() { + return true; + } + + @Override + public List getTrainerNames() { + List tnames = getStrings(false, romEntry.getInt("TrainerNamesTextOffset")); + tnames.remove(0); // blank one + + return tnames; + } + + @Override + public int maxTrainerNameLength() { + return 10; + } + + @Override + public void setTrainerNames(List trainerNames) { + List tnames = getStrings(false, romEntry.getInt("TrainerNamesTextOffset")); + List newTNames = new ArrayList<>(trainerNames); + newTNames.add(0, tnames.get(0)); // the 0-entry, preserve it + setStrings(false, romEntry.getInt("TrainerNamesTextOffset"), newTNames); + try { + writeStringsForAllLanguages(newTNames, romEntry.getInt("TrainerNamesTextOffset")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void writeStringsForAllLanguages(List strings, int index) throws IOException { + List nonEnglishLanguages = Arrays.asList("JaKana", "JaKanji", "Fr", "It", "De", "Es", "Ko"); + for (String nonEnglishLanguage : nonEnglishLanguages) { + String key = "TextStrings" + nonEnglishLanguage; + GARCArchive stringsGarcForLanguage = readGARC(romEntry.getFile(key),true); + setStrings(stringsGarcForLanguage, index, strings); + writeGARC(romEntry.getFile(key), stringsGarcForLanguage); + } + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.MAX_LENGTH; + } + + @Override + public List getTCNameLengthsByTrainer() { + return new ArrayList<>(); + } + + @Override + public List getTrainerClassNames() { + return getStrings(false, romEntry.getInt("TrainerClassesTextOffset")); + } + + @Override + public void setTrainerClassNames(List trainerClassNames) { + setStrings(false, romEntry.getInt("TrainerClassesTextOffset"), trainerClassNames); + try { + writeStringsForAllLanguages(trainerClassNames, romEntry.getInt("TrainerClassesTextOffset")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public int maxTrainerClassNameLength() { + return 15; // "Pokémon Breeder" is possible, so, + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return false; + } + + @Override + public List getDoublesTrainerClasses() { + int[] doublesClasses = romEntry.arrayEntries.get("DoublesTrainerClasses"); + List doubles = new ArrayList<>(); + for (int tClass : doublesClasses) { + doubles.add(tClass); + } + return doubles; + } + + @Override + public String getDefaultExtension() { + return "cxi"; + } + + @Override + public int abilitiesPerPokemon() { + return 3; + } + + @Override + public int highestAbilityIndex() { + return Gen6Constants.getHighestAbilityIndex(romEntry.romType); + } + + @Override + public int internalStringLength(String string) { + return string.length(); + } + + @Override + public void randomizeIntroPokemon() { + + if (romEntry.romType == Gen6Constants.Type_XY) { + + // Pick a random Pokemon, including formes + + Pokemon introPokemon = randomPokemonInclFormes(); + while (introPokemon.actuallyCosmetic) { + introPokemon = randomPokemonInclFormes(); + } + int introPokemonNum = introPokemon.number; + int introPokemonForme = 0; + boolean checkCosmetics = true; + if (introPokemon.formeNumber > 0) { + introPokemonForme = introPokemon.formeNumber; + introPokemonNum = introPokemon.baseForme.number; + checkCosmetics = false; + } + if (checkCosmetics && introPokemon.cosmeticForms > 0) { + introPokemonForme = introPokemon.getCosmeticFormNumber(this.random.nextInt(introPokemon.cosmeticForms)); + } else if (!checkCosmetics && introPokemon.cosmeticForms > 0) { + introPokemonForme += introPokemon.getCosmeticFormNumber(this.random.nextInt(introPokemon.cosmeticForms)); + } + + // Find the value for the Pokemon's cry + + int baseAddr = find(code, Gen6Constants.criesTablePrefixXY); + baseAddr += Gen6Constants.criesTablePrefixXY.length() / 2; + + int pkNumKey = introPokemonNum; + + if (introPokemonForme != 0) { + int extraOffset = readLong(code, baseAddr + (pkNumKey * 0x14)); + pkNumKey = extraOffset + (introPokemonForme - 1); + } + + int initialCry = readLong(code, baseAddr + (pkNumKey * 0x14) + 0x4); + int repeatedCry = readLong(code, baseAddr + (pkNumKey * 0x14) + 0x10); + + // Write to DLLIntro.cro + try { + byte[] introCRO = readFile(romEntry.getFile("Intro")); + + // Replace the Pokemon model that's loaded, and set its forme + + int croModelOffset = find(introCRO, Gen6Constants.introPokemonModelOffsetXY); + croModelOffset += Gen6Constants.introPokemonModelOffsetXY.length() / 2; + + writeWord(introCRO, croModelOffset, introPokemonNum); + introCRO[croModelOffset + 2] = (byte)introPokemonForme; + + // Shiny chance + if (this.random.nextInt(256) == 0) { + introCRO[croModelOffset + 4] = 1; + } + + // Replace the initial cry when the Pokemon exits the ball + // First, re-point two branches + + int croInitialCryOffset1 = find(introCRO, Gen6Constants.introInitialCryOffset1XY); + croInitialCryOffset1 += Gen6Constants.introInitialCryOffset1XY.length() / 2; + + introCRO[croInitialCryOffset1] = 0x5E; + + int croInitialCryOffset2 = find(introCRO, Gen6Constants.introInitialCryOffset2XY); + croInitialCryOffset2 += Gen6Constants.introInitialCryOffset2XY.length() / 2; + + introCRO[croInitialCryOffset2] = 0x2F; + + // Then change the parameters that are loaded for a function call, and also change the function call + // itself to a function that uses the "cry value" instead of Pokemon ID + forme + emotion (same function + // that is used for the repeated cries) + + int croInitialCryOffset3 = find(introCRO, Gen6Constants.introInitialCryOffset3XY); + croInitialCryOffset3 += Gen6Constants.introInitialCryOffset3XY.length() / 2; + + writeLong(introCRO, croInitialCryOffset3, 0xE1A02000); // cpy r2,r0 + writeLong(introCRO, croInitialCryOffset3 + 0x4, 0xE59F100C); // ldr r1,=#CRY_VALUE + writeLong(introCRO, croInitialCryOffset3 + 0x8, 0xE58D0000); // str r0,[sp] + writeLong(introCRO, croInitialCryOffset3 + 0xC, 0xEBFFFDE9); // bl FUN_006a51d4 + writeLong(introCRO, croInitialCryOffset3 + 0x10, readLong(introCRO, croInitialCryOffset3 + 0x14)); // Move these two instructions up four bytes + writeLong(introCRO, croInitialCryOffset3 + 0x14, readLong(introCRO, croInitialCryOffset3 + 0x18)); + writeLong(introCRO, croInitialCryOffset3 + 0x18, initialCry); // CRY_VALUE pool + + // Replace the repeated cry that the Pokemon does while standing around + // Just replace a pool value + int croRepeatedCryOffset = find(introCRO, Gen6Constants.introRepeatedCryOffsetXY); + croRepeatedCryOffset += Gen6Constants.introRepeatedCryOffsetXY.length() / 2; + writeLong(introCRO, croRepeatedCryOffset, repeatedCry); + + writeFile(romEntry.getFile("Intro"), introCRO); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public ItemList getAllowedItems() { + return allowedItems; + } + + @Override + public ItemList getNonBadItems() { + return nonBadItems; + } + + @Override + public List getUniqueNoSellItems() { + return Gen6Constants.uniqueNoSellItems; + } + + @Override + public List getRegularShopItems() { + return Gen6Constants.regularShopItems; + } + + @Override + public List getOPShopItems() { + return Gen6Constants.opShopItems; + } + + @Override + public String[] getItemNames() { + return itemNames.toArray(new String[0]); + } + + @Override + public String abilityName(int number) { + return abilityNames.get(number); + } + + @Override + public Map> getAbilityVariations() { + return Gen5Constants.abilityVariations; + } + + @Override + public List getUselessAbilities() { + return new ArrayList<>(Gen6Constants.uselessAbilities); + } + + @Override + public int getAbilityForTrainerPokemon(TrainerPokemon tp) { + // Before randomizing Trainer Pokemon, one possible value for abilitySlot is 0, + // which represents "Either Ability 1 or 2". During randomization, we make sure to + // to set abilitySlot to some non-zero value, but if you call this method without + // randomization, then you'll hit this case. + if (tp.abilitySlot < 1 || tp.abilitySlot > 3) { + return 0; + } + + List abilityList = Arrays.asList(tp.pokemon.ability1, tp.pokemon.ability2, tp.pokemon.ability3); + return abilityList.get(tp.abilitySlot - 1); + } + + @Override + public boolean hasMegaEvolutions() { + return true; + } + + private int tmFromIndex(int index) { + + if (index >= Gen6Constants.tmBlockOneOffset + && index < Gen6Constants.tmBlockOneOffset + Gen6Constants.tmBlockOneCount) { + return index - (Gen6Constants.tmBlockOneOffset - 1); + } else if (index >= Gen6Constants.tmBlockTwoOffset + && index < Gen6Constants.tmBlockTwoOffset + Gen6Constants.tmBlockTwoCount) { + return (index + Gen6Constants.tmBlockOneCount) - (Gen6Constants.tmBlockTwoOffset - 1); + } else { + return (index + Gen6Constants.tmBlockOneCount + Gen6Constants.tmBlockTwoCount) - (Gen6Constants.tmBlockThreeOffset - 1); + } + } + + private int indexFromTM(int tm) { + if (tm >= 1 && tm <= Gen6Constants.tmBlockOneCount) { + return tm + (Gen6Constants.tmBlockOneOffset - 1); + } else if (tm > Gen6Constants.tmBlockOneCount && tm <= Gen6Constants.tmBlockOneCount + Gen6Constants.tmBlockTwoCount) { + return tm + (Gen6Constants.tmBlockTwoOffset - 1 - Gen6Constants.tmBlockOneCount); + } else { + return tm + (Gen6Constants.tmBlockThreeOffset - 1 - (Gen6Constants.tmBlockOneCount + Gen6Constants.tmBlockTwoCount)); + } + } + + @Override + public List getCurrentFieldTMs() { + List fieldItems = this.getFieldItems(); + List fieldTMs = new ArrayList<>(); + + ItemList allowedItems = Gen6Constants.getAllowedItems(romEntry.romType); + for (int item : fieldItems) { + if (allowedItems.isTM(item)) { + fieldTMs.add(tmFromIndex(item)); + } + } + + return fieldTMs; + } + + @Override + public void setFieldTMs(List fieldTMs) { + List fieldItems = this.getFieldItems(); + int fiLength = fieldItems.size(); + Iterator iterTMs = fieldTMs.iterator(); + + ItemList allowedItems = Gen6Constants.getAllowedItems(romEntry.romType); + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (allowedItems.isTM(oldItem)) { + int newItem = indexFromTM(iterTMs.next()); + fieldItems.set(i, newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List getRegularFieldItems() { + List fieldItems = this.getFieldItems(); + List fieldRegItems = new ArrayList<>(); + + ItemList allowedItems = Gen6Constants.getAllowedItems(romEntry.romType); + for (int item : fieldItems) { + if (allowedItems.isAllowed(item) && !(allowedItems.isTM(item))) { + fieldRegItems.add(item); + } + } + + return fieldRegItems; + } + + @Override + public void setRegularFieldItems(List items) { + List fieldItems = this.getFieldItems(); + int fiLength = fieldItems.size(); + Iterator iterNewItems = items.iterator(); + + ItemList allowedItems = Gen6Constants.getAllowedItems(romEntry.romType); + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (!(allowedItems.isTM(oldItem)) && allowedItems.isAllowed(oldItem) && oldItem != Items.masterBall) { + int newItem = iterNewItems.next(); + fieldItems.set(i, newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List getRequiredFieldTMs() { + return Gen6Constants.getRequiredFieldTMs(romEntry.romType); + } + + public List getFieldItems() { + List fieldItems = new ArrayList<>(); + try { + // normal items + int normalItemsFile = romEntry.getInt("FieldItemsScriptNumber"); + int normalItemsOffset = romEntry.getInt("FieldItemsOffset"); + GARCArchive scriptGarc = readGARC(romEntry.getFile("Scripts"),true); + AMX normalItemAMX = new AMX(scriptGarc.files.get(normalItemsFile).get(0)); + byte[] data = normalItemAMX.decData; + for (int i = normalItemsOffset; i < data.length; i += 12) { + int item = FileFunctions.read2ByteInt(data,i); + fieldItems.add(item); + } + + // hidden items - separate handling for XY and ORAS + if (romEntry.romType == Gen6Constants.Type_XY) { + int hiddenItemsFile = romEntry.getInt("HiddenItemsScriptNumber"); + int hiddenItemsOffset = romEntry.getInt("HiddenItemsOffset"); + AMX hiddenItemAMX = new AMX(scriptGarc.files.get(hiddenItemsFile).get(0)); + data = hiddenItemAMX.decData; + for (int i = hiddenItemsOffset; i < data.length; i += 12) { + int item = FileFunctions.read2ByteInt(data,i); + fieldItems.add(item); + } + } else { + String hiddenItemsPrefix = Gen6Constants.hiddenItemsPrefixORAS; + int offsHidden = find(code,hiddenItemsPrefix); + if (offsHidden > 0) { + offsHidden += hiddenItemsPrefix.length() / 2; + for (int i = 0; i < Gen6Constants.hiddenItemCountORAS; i++) { + int item = FileFunctions.read2ByteInt(code, offsHidden + (i * 0xE) + 2); + fieldItems.add(item); + } + } + } + + // In ORAS, it's possible to encounter the sparkling Mega Stone items on the field + // before you finish the game. Thus, we want to randomize them as well. + if (romEntry.romType == Gen6Constants.Type_ORAS) { + List fieldMegaStones = this.getFieldMegaStonesORAS(scriptGarc); + fieldItems.addAll(fieldMegaStones); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + return fieldItems; + } + + private List getFieldMegaStonesORAS(GARCArchive scriptGarc) throws IOException { + List fieldMegaStones = new ArrayList<>(); + int megaStoneItemScriptFile = romEntry.getInt("MegaStoneItemScriptNumber"); + byte[] megaStoneItemEventBytes = scriptGarc.getFile(megaStoneItemScriptFile); + AMX megaStoneItemEvent = new AMX(megaStoneItemEventBytes); + for (int i = 0; i < Gen6Constants.megastoneTableLengthORAS; i++) { + int offset = Gen6Constants.megastoneTableStartingOffsetORAS + (i * Gen6Constants.megastoneTableEntrySizeORAS); + int item = FileFunctions.read2ByteInt(megaStoneItemEvent.decData, offset); + fieldMegaStones.add(item); + } + return fieldMegaStones; + } + + public void setFieldItems(List items) { + try { + Iterator iterItems = items.iterator(); + // normal items + int normalItemsFile = romEntry.getInt("FieldItemsScriptNumber"); + int normalItemsOffset = romEntry.getInt("FieldItemsOffset"); + GARCArchive scriptGarc = readGARC(romEntry.getFile("Scripts"),true); + AMX normalItemAMX = new AMX(scriptGarc.files.get(normalItemsFile).get(0)); + byte[] data = normalItemAMX.decData; + for (int i = normalItemsOffset; i < data.length; i += 12) { + int item = iterItems.next(); + FileFunctions.write2ByteInt(data,i,item); + } + scriptGarc.setFile(normalItemsFile,normalItemAMX.getBytes()); + + // hidden items - separate handling for XY and ORAS + if (romEntry.romType == Gen6Constants.Type_XY) { + int hiddenItemsFile = romEntry.getInt("HiddenItemsScriptNumber"); + int hiddenItemsOffset = romEntry.getInt("HiddenItemsOffset"); + AMX hiddenItemAMX = new AMX(scriptGarc.files.get(hiddenItemsFile).get(0)); + data = hiddenItemAMX.decData; + for (int i = hiddenItemsOffset; i < data.length; i += 12) { + int item = iterItems.next(); + FileFunctions.write2ByteInt(data,i,item); + } + scriptGarc.setFile(hiddenItemsFile,hiddenItemAMX.getBytes()); + } else { + String hiddenItemsPrefix = Gen6Constants.hiddenItemsPrefixORAS; + int offsHidden = find(code,hiddenItemsPrefix); + if (offsHidden > 0) { + offsHidden += hiddenItemsPrefix.length() / 2; + for (int i = 0; i < Gen6Constants.hiddenItemCountORAS; i++) { + int item = iterItems.next(); + FileFunctions.write2ByteInt(code,offsHidden + (i * 0xE) + 2, item); + } + } + } + + // Sparkling Mega Stone items for ORAS only + if (romEntry.romType == Gen6Constants.Type_ORAS) { + List fieldMegaStones = this.getFieldMegaStonesORAS(scriptGarc); + Map megaStoneMap = new HashMap<>(); + int megaStoneItemScriptFile = romEntry.getInt("MegaStoneItemScriptNumber"); + byte[] megaStoneItemEventBytes = scriptGarc.getFile(megaStoneItemScriptFile); + AMX megaStoneItemEvent = new AMX(megaStoneItemEventBytes); + for (int i = 0; i < Gen6Constants.megastoneTableLengthORAS; i++) { + int offset = Gen6Constants.megastoneTableStartingOffsetORAS + (i * Gen6Constants.megastoneTableEntrySizeORAS); + int oldItem = fieldMegaStones.get(i); + int newItem = iterItems.next(); + if (megaStoneMap.containsKey(oldItem)) { + // There are some duplicate entries for certain Mega Stones, and we're not quite sure why. + // Set them to the same item for sanity's sake. + int replacementItem = megaStoneMap.get(oldItem); + FileFunctions.write2ByteInt(megaStoneItemEvent.decData, offset, replacementItem); + } else { + FileFunctions.write2ByteInt(megaStoneItemEvent.decData, offset, newItem); + megaStoneMap.put(oldItem, newItem); + } + } + scriptGarc.setFile(megaStoneItemScriptFile, megaStoneItemEvent.getBytes()); + } + + writeGARC(romEntry.getFile("Scripts"),scriptGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getIngameTrades() { + List trades = new ArrayList<>(); + + int count = romEntry.getInt("IngameTradeCount"); + String prefix = Gen6Constants.getIngameTradesPrefix(romEntry.romType); + List tradeStrings = getStrings(false, romEntry.getInt("IngameTradesTextOffset")); + int textOffset = romEntry.getInt("IngameTradesTextExtraOffset"); + int offset = find(code,prefix); + if (offset > 0) { + offset += prefix.length() / 2; + for (int i = 0; i < count; i++) { + IngameTrade trade = new IngameTrade(); + trade.nickname = tradeStrings.get(textOffset + i); + trade.givenPokemon = pokes[FileFunctions.read2ByteInt(code,offset)]; + trade.ivs = new int[6]; + for (int iv = 0; iv < 6; iv++) { + trade.ivs[iv] = code[offset + 5 + iv]; + } + trade.otId = FileFunctions.read2ByteInt(code,offset + 0xE); + trade.item = FileFunctions.read2ByteInt(code,offset + 0x10); + trade.otName = tradeStrings.get(textOffset + count + i); + trade.requestedPokemon = pokes[FileFunctions.read2ByteInt(code,offset + 0x20)]; + trades.add(trade); + offset += Gen6Constants.ingameTradeSize; + } + } + return trades; + } + + @Override + public void setIngameTrades(List trades) { + List oldTrades = this.getIngameTrades(); + int[] hardcodedTradeOffsets = romEntry.arrayEntries.get("HardcodedTradeOffsets"); + int[] hardcodedTradeTexts = romEntry.arrayEntries.get("HardcodedTradeTexts"); + int count = romEntry.getInt("IngameTradeCount"); + String prefix = Gen6Constants.getIngameTradesPrefix(romEntry.romType); + List tradeStrings = getStrings(false, romEntry.getInt("IngameTradesTextOffset")); + int textOffset = romEntry.getInt("IngameTradesTextExtraOffset"); + int offset = find(code,prefix); + if (offset > 0) { + offset += prefix.length() / 2; + for (int i = 0; i < count; i++) { + IngameTrade trade = trades.get(i); + tradeStrings.set(textOffset + i, trade.nickname); + FileFunctions.write2ByteInt(code,offset,trade.givenPokemon.number); + for (int iv = 0; iv < 6; iv++) { + code[offset + 5 + iv] = (byte)trade.ivs[iv]; + } + FileFunctions.write2ByteInt(code,offset + 0xE,trade.otId); + FileFunctions.write2ByteInt(code,offset + 0x10,trade.item); + tradeStrings.set(textOffset + count + i, trade.otName); + FileFunctions.write2ByteInt(code,offset + 0x20, + trade.requestedPokemon == null ? 0 : trade.requestedPokemon.number); + offset += Gen6Constants.ingameTradeSize; + + // In XY, there are some trades that use hardcoded strings. Go and forcibly update + // the story text so that the trainer says what they want to trade. + if (romEntry.romType == Gen6Constants.Type_XY && Gen6Constants.xyHardcodedTradeOffsets.contains(i)) { + int hardcodedTradeIndex = Gen6Constants.xyHardcodedTradeOffsets.indexOf(i); + updateHardcodedTradeText(oldTrades.get(i), trade, Gen6Constants.xyHardcodedTradeTexts.get(hardcodedTradeIndex)); + } + } + this.setStrings(false, romEntry.getInt("IngameTradesTextOffset"), tradeStrings); + } + } + + // NOTE: This method is kind of stupid, in that it doesn't try to reflow the text to better fit; it just + // blindly replaces the Pokemon's name. However, it seems to work well enough for what we need. + private void updateHardcodedTradeText(IngameTrade oldTrade, IngameTrade newTrade, int hardcodedTradeTextFile) { + List hardcodedTradeStrings = getStrings(true, hardcodedTradeTextFile); + Pokemon oldRequested = oldTrade.requestedPokemon; + String oldRequestedName = oldRequested != null ? oldRequested.name : null; + String oldGivenName = oldTrade.givenPokemon.name; + Pokemon newRequested = newTrade.requestedPokemon; + String newRequestedName = newRequested != null ? newRequested.name : null; + String newGivenName = newTrade.givenPokemon.name; + for (int i = 0; i < hardcodedTradeStrings.size(); i++) { + String hardcodedTradeString = hardcodedTradeStrings.get(i); + if (oldRequestedName != null && newRequestedName != null && hardcodedTradeString.contains(oldRequestedName)) { + hardcodedTradeString = hardcodedTradeString.replace(oldRequestedName, newRequestedName); + } + if (hardcodedTradeString.contains(oldGivenName)) { + hardcodedTradeString = hardcodedTradeString.replace(oldGivenName, newGivenName); + } + hardcodedTradeStrings.set(i, hardcodedTradeString); + } + this.setStrings(true, hardcodedTradeTextFile, hardcodedTradeStrings); + } + + @Override + public boolean hasDVs() { + return false; + } + + @Override + public int generationOfPokemon() { + return 6; + } + + @Override + public void removeEvosForPokemonPool() { + // slightly more complicated than gen2/3 + // we have to update a "baby table" too + List pokemonIncluded = this.mainPokemonListInclFormes; + Set keepEvos = new HashSet<>(); + for (Pokemon pk : pokes) { + if (pk != null) { + keepEvos.clear(); + for (Evolution evol : pk.evolutionsFrom) { + if (pokemonIncluded.contains(evol.from) && pokemonIncluded.contains(evol.to)) { + keepEvos.add(evol); + } else { + evol.to.evolutionsTo.remove(evol); + } + } + pk.evolutionsFrom.retainAll(keepEvos); + } + } + + try { + // baby pokemon + GARCArchive babyGarc = readGARC(romEntry.getFile("BabyPokemon"), true); + byte[] masterFile = babyGarc.getFile(Gen6Constants.pokemonCount + 1); + for (int i = 1; i <= Gen6Constants.pokemonCount; i++) { + byte[] babyFile = babyGarc.getFile(i); + Pokemon baby = pokes[i]; + while (baby.evolutionsTo.size() > 0) { + // Grab the first "to evolution" even if there are multiple + baby = baby.evolutionsTo.get(0).from; + } + writeWord(babyFile, 0, baby.number); + writeWord(masterFile, i * 2, baby.number); + babyGarc.setFile(i, babyFile); + } + babyGarc.setFile(Gen6Constants.pokemonCount + 1, masterFile); + writeGARC(romEntry.getFile("BabyPokemon"), babyGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public boolean supportsFourStartingMoves() { + return true; + } + + @Override + public List getFieldMoves() { + if (romEntry.romType == Gen6Constants.Type_XY) { + return Gen6Constants.fieldMovesXY; + } else { + return Gen6Constants.fieldMovesORAS; + } + } + + @Override + public List getEarlyRequiredHMMoves() { + return new ArrayList<>(); + } + + @Override + public Map getShopItems() { + int[] tmShops = romEntry.arrayEntries.get("TMShops"); + int[] regularShops = romEntry.arrayEntries.get("RegularShops"); + int[] shopItemSizes = romEntry.arrayEntries.get("ShopItemSizes"); + int shopCount = romEntry.getInt("ShopCount"); + Map shopItemsMap = new TreeMap<>(); + + int offset = getShopItemsOffset(); + if (offset <= 0) { + return shopItemsMap; + } + for (int i = 0; i < shopCount; i++) { + boolean badShop = false; + for (int tmShop: tmShops) { + if (i == tmShop) { + badShop = true; + offset += (shopItemSizes[i] * 2); + break; + } + } + for (int regularShop: regularShops) { + if (badShop) break; + if (i == regularShop) { + badShop = true; + offset += (shopItemSizes[i] * 2); + break; + } + } + if (!badShop) { + List items = new ArrayList<>(); + for (int j = 0; j < shopItemSizes[i]; j++) { + items.add(FileFunctions.read2ByteInt(code,offset)); + offset += 2; + } + Shop shop = new Shop(); + shop.items = items; + shop.name = shopNames.get(i); + shop.isMainGame = Gen6Constants.getMainGameShops(romEntry.romType).contains(i); + shopItemsMap.put(i, shop); + } + } + return shopItemsMap; + } + + @Override + public void setShopItems(Map shopItems) { + int[] shopItemSizes = romEntry.arrayEntries.get("ShopItemSizes"); + int[] tmShops = romEntry.arrayEntries.get("TMShops"); + int[] regularShops = romEntry.arrayEntries.get("RegularShops"); + int shopCount = romEntry.getInt("ShopCount"); + + int offset = getShopItemsOffset(); + if (offset <= 0) { + return; + } + for (int i = 0; i < shopCount; i++) { + boolean badShop = false; + for (int tmShop: tmShops) { + if (badShop) break; + if (i == tmShop) { + badShop = true; + offset += (shopItemSizes[i] * 2); + break; + } + } + for (int regularShop: regularShops) { + if (badShop) break; + if (i == regularShop) { + badShop = true; + offset += (shopItemSizes[i] * 2); + break; + } + } + if (!badShop) { + List shopContents = shopItems.get(i).items; + Iterator iterItems = shopContents.iterator(); + for (int j = 0; j < shopItemSizes[i]; j++) { + Integer item = iterItems.next(); + FileFunctions.write2ByteInt(code,offset,item); + offset += 2; + } + } + } + } + + private int getShopItemsOffset() { + int offset = shopItemsOffset; + if (offset == 0) { + String locator = Gen6Constants.getShopItemsLocator(romEntry.romType); + offset = find(code, locator); + shopItemsOffset = offset; + } + return offset; + } + + @Override + public void setShopPrices() { + try { + GARCArchive itemPriceGarc = this.readGARC(romEntry.getFile("ItemData"),true); + for (int i = 1; i < itemPriceGarc.files.size(); i++) { + writeWord(itemPriceGarc.files.get(i).get(0),0,Gen6Constants.balancedItemPrices.get(i)); + } + writeGARC(romEntry.getFile("ItemData"),itemPriceGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getPickupItems() { + List pickupItems = new ArrayList<>(); + + // If we haven't found the pickup table for this ROM already, find it. + if (pickupItemsTableOffset == 0) { + int offset = find(code, Gen6Constants.pickupTableLocator); + if (offset > 0) { + pickupItemsTableOffset = offset; + } + } + + // Assuming we've found the pickup table, extract the items out of it. + if (pickupItemsTableOffset > 0) { + for (int i = 0; i < Gen6Constants.numberOfPickupItems; i++) { + int itemOffset = pickupItemsTableOffset + (2 * i); + int item = FileFunctions.read2ByteInt(code, itemOffset); + PickupItem pickupItem = new PickupItem(item); + pickupItems.add(pickupItem); + } + } + + // Assuming we got the items from the last step, fill out the probabilities. + if (pickupItems.size() > 0) { + for (int levelRange = 0; levelRange < 10; levelRange++) { + int startingCommonItemOffset = levelRange; + int startingRareItemOffset = 18 + levelRange; + pickupItems.get(startingCommonItemOffset).probabilities[levelRange] = 30; + for (int i = 1; i < 7; i++) { + pickupItems.get(startingCommonItemOffset + i).probabilities[levelRange] = 10; + } + pickupItems.get(startingCommonItemOffset + 7).probabilities[levelRange] = 4; + pickupItems.get(startingCommonItemOffset + 8).probabilities[levelRange] = 4; + pickupItems.get(startingRareItemOffset).probabilities[levelRange] = 1; + pickupItems.get(startingRareItemOffset + 1).probabilities[levelRange] = 1; + } + } + return pickupItems; + } + + @Override + public void setPickupItems(List pickupItems) { + if (pickupItemsTableOffset > 0) { + for (int i = 0; i < Gen6Constants.numberOfPickupItems; i++) { + int itemOffset = pickupItemsTableOffset + (2 * i); + int item = pickupItems.get(i).item; + FileFunctions.write2ByteInt(code, itemOffset, item); + } + } + } + + private void computeCRC32sForRom() throws IOException { + this.actualFileCRC32s = new HashMap<>(); + this.actualCodeCRC32 = FileFunctions.getCRC32(code); + for (String fileKey : romEntry.files.keySet()) { + byte[] file = readFile(romEntry.getFile(fileKey)); + long crc32 = FileFunctions.getCRC32(file); + this.actualFileCRC32s.put(fileKey, crc32); + } + } + + @Override + public boolean isRomValid() { + int index = this.hasGameUpdateLoaded() ? 1 : 0; + if (romEntry.expectedCodeCRC32s[index] != actualCodeCRC32) { + return false; + } + + for (String fileKey : romEntry.files.keySet()) { + long expectedCRC32 = romEntry.files.get(fileKey).expectedCRC32s[index]; + long actualCRC32 = actualFileCRC32s.get(fileKey); + if (expectedCRC32 != actualCRC32) { + System.out.println(actualCRC32); + return false; + } + } + + return true; + } + + @Override + public BufferedImage getMascotImage() { + try { + GARCArchive pokespritesGARC = this.readGARC(romEntry.getFile("PokemonGraphics"),false); + int pkIndex = this.random.nextInt(pokespritesGARC.files.size()-2)+1; + + byte[] icon = pokespritesGARC.files.get(pkIndex).get(0); + int paletteCount = readWord(icon,2); + byte[] rawPalette = Arrays.copyOfRange(icon,4,4+paletteCount*2); + int[] palette = new int[paletteCount]; + for (int i = 0; i < paletteCount; i++) { + palette[i] = GFXFunctions.conv3DS16BitColorToARGB(readWord(rawPalette, i * 2)); + } + + int width = 64; + int height = 32; + // Get the picture and uncompress it. + byte[] uncompressedPic = Arrays.copyOfRange(icon,4+paletteCount*2,4+paletteCount*2+width*height); + + int bpp = paletteCount <= 0x10 ? 4 : 8; + // Output to 64x144 tiled image to prepare for unscrambling + BufferedImage bim = GFXFunctions.drawTiledZOrderImage(uncompressedPic, palette, 0, width, height, bpp); + + // Unscramble the above onto a 96x96 canvas + BufferedImage finalImage = new BufferedImage(40, 30, BufferedImage.TYPE_INT_ARGB); + Graphics g = finalImage.getGraphics(); + g.drawImage(bim, 0, 0, 64, 64, 0, 0, 64, 64, null); + g.drawImage(bim, 64, 0, 96, 8, 0, 64, 32, 72, null); + g.drawImage(bim, 64, 8, 96, 16, 32, 64, 64, 72, null); + g.drawImage(bim, 64, 16, 96, 24, 0, 72, 32, 80, null); + g.drawImage(bim, 64, 24, 96, 32, 32, 72, 64, 80, null); + g.drawImage(bim, 64, 32, 96, 40, 0, 80, 32, 88, null); + g.drawImage(bim, 64, 40, 96, 48, 32, 80, 64, 88, null); + g.drawImage(bim, 64, 48, 96, 56, 0, 88, 32, 96, null); + g.drawImage(bim, 64, 56, 96, 64, 32, 88, 64, 96, null); + g.drawImage(bim, 0, 64, 64, 96, 0, 96, 64, 128, null); + g.drawImage(bim, 64, 64, 96, 72, 0, 128, 32, 136, null); + g.drawImage(bim, 64, 72, 96, 80, 32, 128, 64, 136, null); + g.drawImage(bim, 64, 80, 96, 88, 0, 136, 32, 144, null); + g.drawImage(bim, 64, 88, 96, 96, 32, 136, 64, 144, null); + + // Phew, all done. + return finalImage; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getAllHeldItems() { + return Gen6Constants.allHeldItems; + } + + @Override + public List getAllConsumableHeldItems() { + return Gen6Constants.consumableHeldItems; + } + + @Override + public List getSensibleHeldItemsFor(TrainerPokemon tp, boolean consumableOnly, List moves, int[] pokeMoves) { + List items = new ArrayList<>(); + items.addAll(Gen6Constants.generalPurposeConsumableItems); + int frequencyBoostCount = 6; // Make some very good items more common, but not too common + if (!consumableOnly) { + frequencyBoostCount = 8; // bigger to account for larger item pool. + items.addAll(Gen6Constants.generalPurposeItems); + } + int numDamagingMoves = 0; + for (int moveIdx : pokeMoves) { + Move move = moves.get(moveIdx); + if (move == null) { + continue; + } + if (move.category == MoveCategory.PHYSICAL) { + numDamagingMoves++; + items.add(Items.liechiBerry); + items.add(Gen6Constants.consumableTypeBoostingItems.get(move.type)); + if (!consumableOnly) { + items.addAll(Gen6Constants.typeBoostingItems.get(move.type)); + items.add(Items.choiceBand); + items.add(Items.muscleBand); + } + } + if (move.category == MoveCategory.SPECIAL) { + numDamagingMoves++; + items.add(Items.petayaBerry); + items.add(Gen6Constants.consumableTypeBoostingItems.get(move.type)); + if (!consumableOnly) { + items.addAll(Gen6Constants.typeBoostingItems.get(move.type)); + items.add(Items.wiseGlasses); + items.add(Items.choiceSpecs); + } + } + if (!consumableOnly && Gen6Constants.moveBoostingItems.containsKey(moveIdx)) { + items.addAll(Gen6Constants.moveBoostingItems.get(moveIdx)); + } + } + if (numDamagingMoves >= 2) { + items.add(Items.assaultVest); + } + Map byType = Effectiveness.against(tp.pokemon.primaryType, tp.pokemon.secondaryType, 6); + for(Map.Entry entry : byType.entrySet()) { + Integer berry = Gen6Constants.weaknessReducingBerries.get(entry.getKey()); + if (entry.getValue() == Effectiveness.DOUBLE) { + items.add(berry); + } else if (entry.getValue() == Effectiveness.QUADRUPLE) { + for (int i = 0; i < frequencyBoostCount; i++) { + items.add(berry); + } + } + } + if (byType.get(Type.NORMAL) == Effectiveness.NEUTRAL) { + items.add(Items.chilanBerry); + } + + int ability = this.getAbilityForTrainerPokemon(tp); + if (ability == Abilities.levitate) { + items.removeAll(Arrays.asList(Items.shucaBerry)); + } else if (byType.get(Type.GROUND) == Effectiveness.DOUBLE || byType.get(Type.GROUND) == Effectiveness.QUADRUPLE) { + items.add(Items.airBalloon); + } + + if (!consumableOnly) { + if (Gen6Constants.abilityBoostingItems.containsKey(ability)) { + items.addAll(Gen6Constants.abilityBoostingItems.get(ability)); + } + if (tp.pokemon.primaryType == Type.POISON || tp.pokemon.secondaryType == Type.POISON) { + items.add(Items.blackSludge); + } + List speciesItems = Gen6Constants.speciesBoostingItems.get(tp.pokemon.number); + if (speciesItems != null) { + for (int i = 0; i < frequencyBoostCount; i++) { + items.addAll(speciesItems); + } + } + if (!tp.pokemon.evolutionsFrom.isEmpty() && tp.level >= 20) { + // eviolite can be too good for early game, so we gate it behind a minimum level. + // We go with the same level as the option for "No early wonder guard". + items.add(Items.eviolite); + } + } + return items; + } +} diff --git a/src/com/pkrandom/romhandlers/Gen7RomHandler.java b/src/com/pkrandom/romhandlers/Gen7RomHandler.java new file mode 100644 index 0000000..bfadd86 --- /dev/null +++ b/src/com/pkrandom/romhandlers/Gen7RomHandler.java @@ -0,0 +1,3821 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- Gen7RomHandler.java - randomizer handler for Su/Mo/US/UM. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import com.pkrandom.FileFunctions; +import com.pkrandom.MiscTweak; +import com.pkrandom.RomFunctions; +import com.pkrandom.Settings; +import com.pkrandom.constants.*; +import com.pkrandom.ctr.AMX; +import com.pkrandom.ctr.BFLIM; +import com.pkrandom.ctr.GARCArchive; +import com.pkrandom.ctr.Mini; +import com.pkrandom.exceptions.RandomizerIOException; +import com.pkrandom.pokemon.*; +import pptxt.N3DSTxtHandler; + +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.*; +import java.util.stream.Collectors; + +public class Gen7RomHandler extends Abstract3DSRomHandler { + + public static class Factory extends RomHandler.Factory { + + @Override + public Gen7RomHandler create(Random random, PrintStream logStream) { + return new Gen7RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + return detect3DSRomInner(getProductCodeFromFile(filename), getTitleIdFromFile(filename)); + } + } + + public Gen7RomHandler(Random random) { + super(random, null); + } + + public Gen7RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + private static class OffsetWithinEntry { + private int entry; + private int offset; + } + + private static class RomFileEntry { + public String path; + public long[] expectedCRC32s; + } + + private static class RomEntry { + private String name; + private String romCode; + private String titleId; + private String acronym; + private int romType; + private long[] expectedCodeCRC32s = new long[2]; + private Map files = new HashMap<>(); + private Map linkedStaticOffsets = new HashMap<>(); + private Map strings = new HashMap<>(); + private Map numbers = new HashMap<>(); + private Map arrayEntries = new HashMap<>(); + private Map offsetArrayEntries = new HashMap<>(); + + private int getInt(String key) { + if (!numbers.containsKey(key)) { + numbers.put(key, 0); + } + return numbers.get(key); + } + + private String getString(String key) { + if (!strings.containsKey(key)) { + strings.put(key, ""); + } + return strings.get(key); + } + + private String getFile(String key) { + if (!files.containsKey(key)) { + files.put(key, new RomFileEntry()); + } + return files.get(key).path; + } + } + + private static List roms; + + static { + loadROMInfo(); + } + + private static void loadROMInfo() { + roms = new ArrayList<>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen7_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + if (r[0].equals("Game")) { + current.romCode = r[1]; + } else if (r[0].equals("Type")) { + if (r[1].equalsIgnoreCase("USUM")) { + current.romType = Gen7Constants.Type_USUM; + } else { + current.romType = Gen7Constants.Type_SM; + } + } else if (r[0].equals("TitleId")) { + current.titleId = r[1]; + } else if (r[0].equals("Acronym")) { + current.acronym = r[1]; + } else if (r[0].startsWith("File<")) { + String key = r[0].split("<")[1].split(">")[0]; + String[] values = r[1].substring(1, r[1].length() - 1).split(","); + String path = values[0]; + String crcString = values[1].trim() + ", " + values[2].trim(); + String[] crcs = crcString.substring(1, crcString.length() - 1).split(","); + RomFileEntry entry = new RomFileEntry(); + entry.path = path.trim(); + entry.expectedCRC32s = new long[2]; + entry.expectedCRC32s[0] = parseRILong("0x" + crcs[0].trim()); + entry.expectedCRC32s[1] = parseRILong("0x" + crcs[1].trim()); + current.files.put(key, entry); + } else if (r[0].equals("CodeCRC32")) { + String[] values = r[1].substring(1, r[1].length() - 1).split(","); + current.expectedCodeCRC32s[0] = parseRILong("0x" + values[0].trim()); + current.expectedCodeCRC32s[1] = parseRILong("0x" + values[1].trim()); + } else if (r[0].equals("LinkedStaticEncounterOffsets")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + for (int i = 0; i < offsets.length; i++) { + String[] parts = offsets[i].split(":"); + current.linkedStaticOffsets.put(Integer.parseInt(parts[0].trim()), Integer.parseInt(parts[1].trim())); + } + } else if (r[0].endsWith("Offset") || r[0].endsWith("Count") || r[0].endsWith("Number")) { + int offs = parseRIInt(r[1]); + current.numbers.put(r[0], offs); + } else if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.arrayEntries.put(r[0], offs); + } + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.romCode)) { + // copy from here + current.linkedStaticOffsets.putAll(otherEntry.linkedStaticOffsets); + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.numbers.putAll(otherEntry.numbers); + current.strings.putAll(otherEntry.strings); + current.offsetArrayEntries.putAll(otherEntry.offsetArrayEntries); + current.files.putAll(otherEntry.files); + } + } + } else { + current.strings.put(r[0],r[1]); + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + System.err.println("File not found!"); + } + } + + private static int parseRIInt(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Integer.parseInt(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + private static long parseRILong(String off) { + int radix = 10; + off = off.trim().toLowerCase(); + if (off.startsWith("0x") || off.startsWith("&h")) { + radix = 16; + off = off.substring(2); + } + try { + return Long.parseLong(off, radix); + } catch (NumberFormatException ex) { + System.err.println("invalid base " + radix + "number " + off); + return 0; + } + } + + // This ROM + private Pokemon[] pokes; + private Map formeMappings = new TreeMap<>(); + private Map> absolutePokeNumByBaseForme; + private Map dummyAbsolutePokeNums; + private List pokemonList; + private List pokemonListInclFormes; + private List megaEvolutions; + private List areaDataList; + private Move[] moves; + private RomEntry romEntry; + private byte[] code; + private List itemNames; + private List shopNames; + private List abilityNames; + private ItemList allowedItems, nonBadItems; + private long actualCodeCRC32; + private Map actualFileCRC32s; + + private GARCArchive pokeGarc, moveGarc, encounterGarc, stringsGarc, storyTextGarc; + + @Override + protected boolean detect3DSRom(String productCode, String titleId) { + return detect3DSRomInner(productCode, titleId); + } + + private static boolean detect3DSRomInner(String productCode, String titleId) { + return entryFor(productCode, titleId) != null; + } + + private static RomEntry entryFor(String productCode, String titleId) { + if (productCode == null || titleId == null) { + return null; + } + + for (RomEntry re : roms) { + if (productCode.equals(re.romCode) && titleId.equals(re.titleId)) { + return re; + } + } + return null; + } + + @Override + protected void loadedROM(String productCode, String titleId) { + this.romEntry = entryFor(productCode, titleId); + + try { + code = readCode(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + try { + stringsGarc = readGARC(romEntry.getFile("TextStrings"), true); + storyTextGarc = readGARC(romEntry.getFile("StoryText"), true); + areaDataList = getAreaData(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + loadPokemonStats(); + loadMoves(); + + pokemonListInclFormes = Arrays.asList(pokes); + pokemonList = Arrays.asList(Arrays.copyOfRange(pokes,0,Gen7Constants.getPokemonCount(romEntry.romType) + 1)); + + itemNames = getStrings(false,romEntry.getInt("ItemNamesTextOffset")); + abilityNames = getStrings(false,romEntry.getInt("AbilityNamesTextOffset")); + shopNames = Gen7Constants.getShopNames(romEntry.romType); + + allowedItems = Gen7Constants.getAllowedItems(romEntry.romType).copy(); + nonBadItems = Gen7Constants.nonBadItems.copy(); + + if (romEntry.romType == Gen7Constants.Type_SM) { + isSM = true; + } + + try { + computeCRC32sForRom(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private List getStrings(boolean isStoryText, int index) { + GARCArchive baseGARC = isStoryText ? storyTextGarc : stringsGarc; + return getStrings(baseGARC, index); + } + + private List getStrings(GARCArchive textGARC, int index) { + byte[] rawFile = textGARC.files.get(index).get(0); + return new ArrayList<>(N3DSTxtHandler.readTexts(rawFile,true,romEntry.romType)); + } + + private void setStrings(boolean isStoryText, int index, List strings) { + GARCArchive baseGARC = isStoryText ? storyTextGarc : stringsGarc; + setStrings(baseGARC, index, strings); + } + + private void setStrings(GARCArchive textGARC, int index, List strings) { + byte[] oldRawFile = textGARC.files.get(index).get(0); + try { + byte[] newRawFile = N3DSTxtHandler.saveEntry(oldRawFile, strings, romEntry.romType); + textGARC.setFile(index, newRawFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void loadPokemonStats() { + try { + pokeGarc = this.readGARC(romEntry.getFile("PokemonStats"),true); + String[] pokeNames = readPokemonNames(); + int pokemonCount = Gen7Constants.getPokemonCount(romEntry.romType); + int formeCount = Gen7Constants.getFormeCount(romEntry.romType); + pokes = new Pokemon[pokemonCount + formeCount + 1]; + for (int i = 1; i <= pokemonCount; i++) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i],pokeGarc.files.get(i).get(0),formeMappings); + pokes[i].name = pokeNames[i]; + } + + absolutePokeNumByBaseForme = new HashMap<>(); + dummyAbsolutePokeNums = new HashMap<>(); + dummyAbsolutePokeNums.put(255,0); + + int i = pokemonCount + 1; + int formNum = 1; + int prevSpecies = 0; + Map currentMap = new HashMap<>(); + for (int k: formeMappings.keySet()) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i], pokeGarc.files.get(k).get(0),formeMappings); + FormeInfo fi = formeMappings.get(k); + int realBaseForme = pokes[fi.baseForme].baseForme == null ? fi.baseForme : pokes[fi.baseForme].baseForme.number; + pokes[i].name = pokeNames[realBaseForme]; + pokes[i].baseForme = pokes[fi.baseForme]; + pokes[i].formeNumber = fi.formeNumber; + if (pokes[i].actuallyCosmetic) { + pokes[i].formeSuffix = pokes[i].baseForme.formeSuffix; + } else { + pokes[i].formeSuffix = Gen7Constants.getFormeSuffixByBaseForme(fi.baseForme,fi.formeNumber); + } + if (realBaseForme == prevSpecies) { + formNum++; + currentMap.put(formNum,i); + } else { + if (prevSpecies != 0) { + absolutePokeNumByBaseForme.put(prevSpecies,currentMap); + } + prevSpecies = realBaseForme; + formNum = 1; + currentMap = new HashMap<>(); + currentMap.put(formNum,i); + } + i++; + } + if (prevSpecies != 0) { + absolutePokeNumByBaseForme.put(prevSpecies,currentMap); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + populateEvolutions(); + populateMegaEvolutions(); + } + + private void loadBasicPokeStats(Pokemon pkmn, byte[] stats, Map altFormes) { + pkmn.hp = stats[Gen7Constants.bsHPOffset] & 0xFF; + pkmn.attack = stats[Gen7Constants.bsAttackOffset] & 0xFF; + pkmn.defense = stats[Gen7Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = stats[Gen7Constants.bsSpeedOffset] & 0xFF; + pkmn.spatk = stats[Gen7Constants.bsSpAtkOffset] & 0xFF; + pkmn.spdef = stats[Gen7Constants.bsSpDefOffset] & 0xFF; + // Type + pkmn.primaryType = Gen7Constants.typeTable[stats[Gen7Constants.bsPrimaryTypeOffset] & 0xFF]; + pkmn.secondaryType = Gen7Constants.typeTable[stats[Gen7Constants.bsSecondaryTypeOffset] & 0xFF]; + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + pkmn.catchRate = stats[Gen7Constants.bsCatchRateOffset] & 0xFF; + pkmn.growthCurve = ExpCurve.fromByte(stats[Gen7Constants.bsGrowthCurveOffset]); + + pkmn.ability1 = stats[Gen7Constants.bsAbility1Offset] & 0xFF; + pkmn.ability2 = stats[Gen7Constants.bsAbility2Offset] & 0xFF; + pkmn.ability3 = stats[Gen7Constants.bsAbility3Offset] & 0xFF; + if (pkmn.ability1 == pkmn.ability2) { + pkmn.ability2 = 0; + } + + pkmn.callRate = stats[Gen7Constants.bsCallRateOffset] & 0xFF; + + // Held Items? + int item1 = FileFunctions.read2ByteInt(stats, Gen7Constants.bsCommonHeldItemOffset); + int item2 = FileFunctions.read2ByteInt(stats, Gen7Constants.bsRareHeldItemOffset); + + if (item1 == item2) { + // guaranteed + pkmn.guaranteedHeldItem = item1; + pkmn.commonHeldItem = 0; + pkmn.rareHeldItem = 0; + pkmn.darkGrassHeldItem = -1; + } else { + pkmn.guaranteedHeldItem = 0; + pkmn.commonHeldItem = item1; + pkmn.rareHeldItem = item2; + pkmn.darkGrassHeldItem = -1; + } + + int formeCount = stats[Gen7Constants.bsFormeCountOffset] & 0xFF; + if (formeCount > 1) { + if (!altFormes.keySet().contains(pkmn.number)) { + int firstFormeOffset = FileFunctions.read2ByteInt(stats, Gen7Constants.bsFormeOffset); + if (firstFormeOffset != 0) { + int j = 0; + int jMax = 0; + int theAltForme = 0; + Set altFormesWithCosmeticForms = Gen7Constants.getAltFormesWithCosmeticForms(romEntry.romType).keySet(); + for (int i = 1; i < formeCount; i++) { + if (j == 0 || j > jMax) { + altFormes.put(firstFormeOffset + i - 1,new FormeInfo(pkmn.number,i,FileFunctions.read2ByteInt(stats,Gen7Constants.bsFormeSpriteOffset))); // Assumes that formes are in memory in the same order as their numbers + if (Gen7Constants.getActuallyCosmeticForms(romEntry.romType).contains(firstFormeOffset+i-1)) { + if (!Gen7Constants.getIgnoreForms(romEntry.romType).contains(firstFormeOffset+i-1)) { // Skip ignored forms (identical or confusing cosmetic forms) + pkmn.cosmeticForms += 1; + pkmn.realCosmeticFormNumbers.add(i); + } + } + } else { + altFormes.put(firstFormeOffset + i - 1,new FormeInfo(theAltForme,j,FileFunctions.read2ByteInt(stats,Gen7Constants.bsFormeSpriteOffset))); + j++; + } + if (altFormesWithCosmeticForms.contains(firstFormeOffset + i - 1)) { + j = 1; + jMax = Gen7Constants.getAltFormesWithCosmeticForms(romEntry.romType).get(firstFormeOffset + i - 1); + theAltForme = firstFormeOffset + i - 1; + } + } + } else { + if (pkmn.number != Species.arceus && pkmn.number != Species.genesect && pkmn.number != Species.xerneas && pkmn.number != Species.silvally) { + // Reason for exclusions: + // Arceus/Genesect/Silvally: to avoid confusion + // Xerneas: Should be handled automatically? + pkmn.cosmeticForms = formeCount; + } + } + } else { + if (!Gen7Constants.getIgnoreForms(romEntry.romType).contains(pkmn.number)) { + pkmn.cosmeticForms = Gen7Constants.getAltFormesWithCosmeticForms(romEntry.romType).getOrDefault(pkmn.number,0); + } + if (Gen7Constants.getActuallyCosmeticForms(romEntry.romType).contains(pkmn.number)) { + pkmn.actuallyCosmetic = true; + } + } + } + + // The above code will add all alternate cosmetic forms to realCosmeticFormNumbers as necessary, but it will + // NOT add the base form. For example, if we are currently looking at Mimikyu, it will add Totem Mimikyu to + // the list of realCosmeticFormNumbers, but it will not add normal-sized Mimikyu. Without any corrections, + // this will make base Mimikyu impossible to randomly select. The simplest way to fix this is to just add + // the base form to the realCosmeticFormNumbers here if that list was populated above. + if (pkmn.realCosmeticFormNumbers.size() > 0) { + pkmn.realCosmeticFormNumbers.add(0); + pkmn.cosmeticForms += 1; + } + } + + private String[] readPokemonNames() { + int pokemonCount = Gen7Constants.getPokemonCount(romEntry.romType); + String[] pokeNames = new String[pokemonCount + 1]; + List nameList = getStrings(false, romEntry.getInt("PokemonNamesTextOffset")); + for (int i = 1; i <= pokemonCount; i++) { + pokeNames[i] = nameList.get(i); + } + return pokeNames; + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + // Read GARC + try { + GARCArchive evoGARC = readGARC(romEntry.getFile("PokemonEvolutions"),true); + for (int i = 1; i <= Gen7Constants.getPokemonCount(romEntry.romType) + Gen7Constants.getFormeCount(romEntry.romType); i++) { + Pokemon pk = pokes[i]; + byte[] evoEntry = evoGARC.files.get(i).get(0); + boolean skipNext = false; + for (int evo = 0; evo < 8; evo++) { + int method = readWord(evoEntry, evo * 8); + int species = readWord(evoEntry, evo * 8 + 4); + if (method >= 1 && method <= Gen7Constants.evolutionMethodCount && species >= 1) { + EvolutionType et = EvolutionType.fromIndex(7, method); + if (et.skipSplitEvo()) continue; // Remove Feebas "split" evolution + if (skipNext) { + skipNext = false; + continue; + } + if (et == EvolutionType.LEVEL_GAME) { + skipNext = true; + } + + int extraInfo = readWord(evoEntry, evo * 8 + 2); + int forme = evoEntry[evo * 8 + 6]; + int level = evoEntry[evo * 8 + 7]; + Evolution evol = new Evolution(pk, getPokemonForEncounter(species,forme), true, et, extraInfo); + evol.forme = forme; + evol.level = level; + if (et.usesLevel()) { + evol.extraInfo = level; + } + switch (et) { + case LEVEL_GAME: + evol.type = EvolutionType.LEVEL; + evol.to = pokes[romEntry.getInt("CosmoemEvolutionNumber")]; + break; + case LEVEL_DAY_GAME: + evol.type = EvolutionType.LEVEL_DAY; + break; + case LEVEL_NIGHT_GAME: + evol.type = EvolutionType.LEVEL_NIGHT; + break; + default: + break; + } + if (pk.baseForme != null && pk.baseForme.number == Species.rockruff && pk.formeNumber > 0) { + evol.from = pk.baseForme; + pk.baseForme.evolutionsFrom.add(evol); + pokes[absolutePokeNumByBaseForme.get(species).get(evol.forme)].evolutionsTo.add(evol); + } + if (!pk.evolutionsFrom.contains(evol)) { + pk.evolutionsFrom.add(evol); + if (!pk.actuallyCosmetic) { + if (evol.forme > 0) { + // The forme number for the evolution might represent an actual alt forme, or it + // might simply represent a cosmetic forme. If it represents an actual alt forme, + // we'll need to figure out what the absolute species ID for that alt forme is + // and update its evolutions. If it instead represents a cosmetic forme, then the + // absolutePokeNumByBaseFormeMap will be null, since there's no secondary species + // entry for this forme. + Map absolutePokeNumByBaseFormeMap = absolutePokeNumByBaseForme.get(species); + if (absolutePokeNumByBaseFormeMap != null) { + species = absolutePokeNumByBaseFormeMap.get(evol.forme); + } + } + pokes[species].evolutionsTo.add(evol); + } + } + } + } + + // Nincada's Shedinja evo is hardcoded into the game's executable, + // so if the Pokemon is Nincada, then let's and put it as one of its evolutions + if (pk.number == Species.nincada) { + Pokemon shedinja = pokes[Species.shedinja]; + Evolution evol = new Evolution(pk, shedinja, false, EvolutionType.LEVEL_IS_EXTRA, 20); + evol.forme = -1; + evol.level = 20; + pk.evolutionsFrom.add(evol); + shedinja.evolutionsTo.add(evol); + } + + // Split evos shouldn't carry stats unless the evo is Nincada's + // In that case, we should have Ninjask carry stats + if (pk.evolutionsFrom.size() > 1) { + for (Evolution e : pk.evolutionsFrom) { + if (e.type != EvolutionType.LEVEL_CREATE_EXTRA) { + e.carryStats = false; + } + } + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void populateMegaEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.megaEvolutionsFrom.clear(); + pkmn.megaEvolutionsTo.clear(); + } + } + + // Read GARC + try { + megaEvolutions = new ArrayList<>(); + GARCArchive megaEvoGARC = readGARC(romEntry.getFile("MegaEvolutions"),true); + for (int i = 1; i <= Gen7Constants.getPokemonCount(romEntry.romType); i++) { + Pokemon pk = pokes[i]; + byte[] megaEvoEntry = megaEvoGARC.files.get(i).get(0); + for (int evo = 0; evo < 2; evo++) { + int formNum = readWord(megaEvoEntry, evo * 8); + int method = readWord(megaEvoEntry, evo * 8 + 2); + if (method >= 1) { + int argument = readWord(megaEvoEntry, evo * 8 + 4); + int megaSpecies = absolutePokeNumByBaseForme + .getOrDefault(pk.number,dummyAbsolutePokeNums) + .getOrDefault(formNum,0); + MegaEvolution megaEvo = new MegaEvolution(pk, pokes[megaSpecies], method, argument); + if (!pk.megaEvolutionsFrom.contains(megaEvo)) { + pk.megaEvolutionsFrom.add(megaEvo); + pokes[megaSpecies].megaEvolutionsTo.add(megaEvo); + } + megaEvolutions.add(megaEvo); + } + } + // split evos don't carry stats + if (pk.megaEvolutionsFrom.size() > 1) { + for (MegaEvolution e : pk.megaEvolutionsFrom) { + e.carryStats = false; + } + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void loadMoves() { + try { + moveGarc = this.readGARC(romEntry.getFile("MoveData"),true); + int moveCount = Gen7Constants.getMoveCount(romEntry.romType); + moves = new Move[moveCount + 1]; + List moveNames = getStrings(false, romEntry.getInt("MoveNamesTextOffset")); + byte[][] movesData = Mini.UnpackMini(moveGarc.files.get(0).get(0), "WD"); + for (int i = 1; i <= moveCount; i++) { + byte[] moveData = movesData[i]; + moves[i] = new Move(); + moves[i].name = moveNames.get(i); + moves[i].number = i; + moves[i].internalId = i; + moves[i].effectIndex = readWord(moveData, 16); + moves[i].hitratio = (moveData[4] & 0xFF); + moves[i].power = moveData[3] & 0xFF; + moves[i].pp = moveData[5] & 0xFF; + moves[i].type = Gen7Constants.typeTable[moveData[0] & 0xFF]; + moves[i].flinchPercentChance = moveData[15] & 0xFF; + moves[i].target = moveData[20] & 0xFF; + moves[i].category = Gen7Constants.moveCategoryIndices[moveData[2] & 0xFF]; + moves[i].priority = moveData[6]; + + int critStages = moveData[14] & 0xFF; + if (critStages == 6) { + moves[i].criticalChance = CriticalChance.GUARANTEED; + } else if (critStages > 0) { + moves[i].criticalChance = CriticalChance.INCREASED; + } + + int internalStatusType = readWord(moveData, 8); + int flags = FileFunctions.readFullInt(moveData, 36); + moves[i].makesContact = (flags & 0x001) != 0; + moves[i].isChargeMove = (flags & 0x002) != 0; + moves[i].isRechargeMove = (flags & 0x004) != 0; + moves[i].isPunchMove = (flags & 0x080) != 0; + moves[i].isSoundMove = (flags & 0x100) != 0; + moves[i].isTrapMove = internalStatusType == 8; + switch (moves[i].effectIndex) { + case Gen7Constants.noDamageTargetTrappingEffect: + case Gen7Constants.noDamageFieldTrappingEffect: + case Gen7Constants.damageAdjacentFoesTrappingEffect: + case Gen7Constants.damageTargetTrappingEffect: + moves[i].isTrapMove = true; + break; + } + + int qualities = moveData[1]; + int recoilOrAbsorbPercent = moveData[18]; + if (qualities == Gen7Constants.damageAbsorbQuality) { + moves[i].absorbPercent = recoilOrAbsorbPercent; + } else { + moves[i].recoilPercent = -recoilOrAbsorbPercent; + } + + if (i == Moves.swift) { + perfectAccuracy = (int)moves[i].hitratio; + } + + if (GlobalConstants.normalMultihitMoves.contains(i)) { + moves[i].hitCount = 19 / 6.0; + } else if (GlobalConstants.doubleHitMoves.contains(i)) { + moves[i].hitCount = 2; + } else if (i == Moves.tripleKick) { + moves[i].hitCount = 2.71; // this assumes the first hit lands + } + + switch (qualities) { + case Gen7Constants.noDamageStatChangeQuality: + case Gen7Constants.noDamageStatusAndStatChangeQuality: + // All Allies or Self + if (moves[i].target == 6 || moves[i].target == 7) { + moves[i].statChangeMoveType = StatChangeMoveType.NO_DAMAGE_USER; + } else if (moves[i].target == 2) { + moves[i].statChangeMoveType = StatChangeMoveType.NO_DAMAGE_ALLY; + } else if (moves[i].target == 8) { + moves[i].statChangeMoveType = StatChangeMoveType.NO_DAMAGE_ALL; + } else { + moves[i].statChangeMoveType = StatChangeMoveType.NO_DAMAGE_TARGET; + } + break; + case Gen7Constants.damageTargetDebuffQuality: + moves[i].statChangeMoveType = StatChangeMoveType.DAMAGE_TARGET; + break; + case Gen7Constants.damageUserBuffQuality: + moves[i].statChangeMoveType = StatChangeMoveType.DAMAGE_USER; + break; + default: + moves[i].statChangeMoveType = StatChangeMoveType.NONE_OR_UNKNOWN; + break; + } + + for (int statChange = 0; statChange < 3; statChange++) { + moves[i].statChanges[statChange].type = StatChangeType.values()[moveData[21 + statChange]]; + moves[i].statChanges[statChange].stages = moveData[24 + statChange]; + moves[i].statChanges[statChange].percentChance = moveData[27 + statChange]; + } + + // Exclude status types that aren't in the StatusType enum. + if (internalStatusType < 7) { + moves[i].statusType = StatusType.values()[internalStatusType]; + if (moves[i].statusType == StatusType.POISON && (i == Moves.toxic || i == Moves.poisonFang)) { + moves[i].statusType = StatusType.TOXIC_POISON; + } + moves[i].statusPercentChance = moveData[10] & 0xFF; + switch (qualities) { + case Gen7Constants.noDamageStatusQuality: + case Gen7Constants.noDamageStatusAndStatChangeQuality: + moves[i].statusMoveType = StatusMoveType.NO_DAMAGE; + break; + case Gen7Constants.damageStatusQuality: + moves[i].statusMoveType = StatusMoveType.DAMAGE; + break; + } + } + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + protected void savingROM() { + savePokemonStats(); + saveMoves(); + try { + writeCode(code); + writeGARC(romEntry.getFile("WildPokemon"), encounterGarc); + writeGARC(romEntry.getFile("TextStrings"), stringsGarc); + writeGARC(romEntry.getFile("StoryText"), storyTextGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void savePokemonStats() { + int k = Gen7Constants.bsSize; + int pokemonCount = Gen7Constants.getPokemonCount(romEntry.romType); + int formeCount = Gen7Constants.getFormeCount(romEntry.romType); + byte[] duplicateData = pokeGarc.files.get(pokemonCount + formeCount + 1).get(0); + for (int i = 1; i <= pokemonCount + formeCount; i++) { + byte[] pokeData = pokeGarc.files.get(i).get(0); + saveBasicPokeStats(pokes[i], pokeData); + for (byte pokeDataByte : pokeData) { + duplicateData[k] = pokeDataByte; + k++; + } + } + + try { + this.writeGARC(romEntry.getFile("PokemonStats"),pokeGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + writeEvolutions(); + } + + private void saveBasicPokeStats(Pokemon pkmn, byte[] stats) { + stats[Gen7Constants.bsHPOffset] = (byte) pkmn.hp; + stats[Gen7Constants.bsAttackOffset] = (byte) pkmn.attack; + stats[Gen7Constants.bsDefenseOffset] = (byte) pkmn.defense; + stats[Gen7Constants.bsSpeedOffset] = (byte) pkmn.speed; + stats[Gen7Constants.bsSpAtkOffset] = (byte) pkmn.spatk; + stats[Gen7Constants.bsSpDefOffset] = (byte) pkmn.spdef; + stats[Gen7Constants.bsPrimaryTypeOffset] = Gen7Constants.typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + stats[Gen7Constants.bsSecondaryTypeOffset] = stats[Gen7Constants.bsPrimaryTypeOffset]; + } else { + stats[Gen7Constants.bsSecondaryTypeOffset] = Gen7Constants.typeToByte(pkmn.secondaryType); + } + stats[Gen7Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + stats[Gen7Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + + stats[Gen7Constants.bsAbility1Offset] = (byte) pkmn.ability1; + stats[Gen7Constants.bsAbility2Offset] = pkmn.ability2 != 0 ? (byte) pkmn.ability2 : (byte) pkmn.ability1; + stats[Gen7Constants.bsAbility3Offset] = (byte) pkmn.ability3; + + stats[Gen7Constants.bsCallRateOffset] = (byte) pkmn.callRate; + + // Held items + if (pkmn.guaranteedHeldItem > 0) { + FileFunctions.write2ByteInt(stats, Gen7Constants.bsCommonHeldItemOffset, pkmn.guaranteedHeldItem); + FileFunctions.write2ByteInt(stats, Gen7Constants.bsRareHeldItemOffset, pkmn.guaranteedHeldItem); + FileFunctions.write2ByteInt(stats, Gen7Constants.bsDarkGrassHeldItemOffset, 0); + } else { + FileFunctions.write2ByteInt(stats, Gen7Constants.bsCommonHeldItemOffset, pkmn.commonHeldItem); + FileFunctions.write2ByteInt(stats, Gen7Constants.bsRareHeldItemOffset, pkmn.rareHeldItem); + FileFunctions.write2ByteInt(stats, Gen7Constants.bsDarkGrassHeldItemOffset, 0); + } + + if (pkmn.fullName().equals("Meowstic")) { + stats[Gen7Constants.bsGenderOffset] = 0; + } else if (pkmn.fullName().equals("Meowstic-F")) { + stats[Gen7Constants.bsGenderOffset] = (byte)0xFE; + } + } + + private void writeEvolutions() { + try { + GARCArchive evoGARC = readGARC(romEntry.getFile("PokemonEvolutions"),true); + for (int i = 1; i <= Gen7Constants.getPokemonCount(romEntry.romType) + Gen7Constants.getFormeCount(romEntry.romType); i++) { + byte[] evoEntry = evoGARC.files.get(i).get(0); + Pokemon pk = pokes[i]; + if (pk.number == Species.nincada) { + writeShedinjaEvolution(); + } + int evosWritten = 0; + for (Evolution evo : pk.evolutionsFrom) { + Pokemon toPK = evo.to; + writeWord(evoEntry, evosWritten * 8, evo.type.toIndex(7)); + writeWord(evoEntry, evosWritten * 8 + 2, evo.type.usesLevel() ? 0 : evo.extraInfo); + writeWord(evoEntry, evosWritten * 8 + 4, toPK.getBaseNumber()); + evoEntry[evosWritten * 8 + 6] = (byte)evo.forme; + evoEntry[evosWritten * 8 + 7] = evo.type.usesLevel() ? (byte)evo.extraInfo : (byte)evo.level; + evosWritten++; + if (evosWritten == 8) { + break; + } + } + while (evosWritten < 8) { + writeWord(evoEntry, evosWritten * 8, 0); + writeWord(evoEntry, evosWritten * 8 + 2, 0); + writeWord(evoEntry, evosWritten * 8 + 4, 0); + writeWord(evoEntry, evosWritten * 8 + 6, 0); + evosWritten++; + } + } + writeGARC(romEntry.getFile("PokemonEvolutions"), evoGARC); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void writeShedinjaEvolution() { + Pokemon nincada = pokes[Species.nincada]; + + // When the "Limit Pokemon" setting is enabled and Gen 3 is disabled, or when + // "Random Every Level" evolutions are selected, we end up clearing out Nincada's + // vanilla evolutions. In that case, there's no point in even worrying about + // Shedinja, so just return. + if (nincada.evolutionsFrom.size() < 2) { + return; + } + Pokemon primaryEvolution = nincada.evolutionsFrom.get(0).to; + Pokemon extraEvolution = nincada.evolutionsFrom.get(1).to; + + // In the game's executable, there's a hardcoded check to see if the Pokemon + // that just evolved is now a Ninjask after evolving; if it is, then we start + // going down the path of creating a Shedinja. To accomplish this check, they + // hardcoded Ninjask's species ID as a constant. We replace this constant + // with the species ID of Nincada's new primary evolution; that way, evolving + // Nincada will still produce an "extra" Pokemon like in older generations. + int offset = find(code, Gen7Constants.ninjaskSpeciesPrefix); + if (offset > 0) { + offset += Gen7Constants.ninjaskSpeciesPrefix.length() / 2; // because it was a prefix + FileFunctions.writeFullInt(code, offset, primaryEvolution.getBaseNumber()); + } + + // In the game's executable, there's a hardcoded value to indicate what "extra" + // Pokemon to create. It produces a Shedinja using the following instruction: + // mov r1, #0x124, where 0x124 = 292 in decimal, which is Shedinja's species ID. + // We can't just blindly replace it, though, because certain constants (for example, + // 0x125) cannot be moved without using the movw instruction. This works fine in + // Citra, but crashes on real hardware. Instead, we have to annoyingly shift up a + // big chunk of code to fill in a nop; we can then do a pc-relative load to a + // constant in the new free space. + offset = find(code, Gen7Constants.shedinjaPrefix); + if (offset > 0) { + offset += Gen7Constants.shedinjaPrefix.length() / 2; // because it was a prefix + + // Shift up everything below the last nop to make some room at the bottom of the function. + for (int i = 84; i < 120; i++) { + code[offset + i] = code[offset + i + 4]; + } + + // For every bl that we shifted up, patch them so they're now pointing to the same place they + // were before (without this, they will be pointing to 0x4 before where they're supposed to). + List blOffsetsToPatch = Arrays.asList(84, 96, 108); + for (int blOffsetToPatch : blOffsetsToPatch) { + code[offset + blOffsetToPatch] += 1; + } + + // Write Nincada's new extra evolution in the new free space. + writeLong(code, offset + 120, extraEvolution.getBaseNumber()); + + // Second parameter of pml::pokepara::CoreParam::ChangeMonsNo is the + // new forme number + code[offset] = (byte) extraEvolution.formeNumber; + + // First parameter of pml::pokepara::CoreParam::ChangeMonsNo is the + // new species number. Write a pc-relative load to what we wrote before. + code[offset + 4] = (byte) 0x6C; + code[offset + 5] = 0x10; + code[offset + 6] = (byte) 0x9F; + code[offset + 7] = (byte) 0xE5; + } + + // Now that we've handled the hardcoded Shedinja evolution, delete it so that + // we do *not* handle it in WriteEvolutions + nincada.evolutionsFrom.remove(1); + extraEvolution.evolutionsTo.remove(0); + } + + private void saveMoves() { + int moveCount = Gen7Constants.getMoveCount(romEntry.romType); + byte[][] movesData = Mini.UnpackMini(moveGarc.files.get(0).get(0), "WD"); + for (int i = 1; i <= moveCount; i++) { + byte[] moveData = movesData[i]; + moveData[2] = Gen7Constants.moveCategoryToByte(moves[i].category); + moveData[3] = (byte) moves[i].power; + moveData[0] = Gen7Constants.typeToByte(moves[i].type); + int hitratio = (int) Math.round(moves[i].hitratio); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 101) { + hitratio = 100; + } + moveData[4] = (byte) hitratio; + moveData[5] = (byte) moves[i].pp; + } + try { + moveGarc.setFile(0, Mini.PackMini(movesData, "WD")); + this.writeGARC(romEntry.getFile("MoveData"), moveGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void patchFormeReversion() throws IOException { + // Upon loading a save, all Mega Pokemon, all Primal Reversions, + // all Greninja-A, all Zygarde-C, and all Necrozma-U in the player's + // party are set back to their base forme. This patches .code such + // that this reversion does not happen. + String saveLoadFormeReversionPrefix = Gen7Constants.getSaveLoadFormeReversionPrefix(romEntry.romType); + int offset = find(code, saveLoadFormeReversionPrefix); + if (offset > 0) { + offset += saveLoadFormeReversionPrefix.length() / 2; // because it was a prefix + + // The actual offset of the code we want to patch is 8 bytes from the end of + // the prefix. We have to do this because these 8 bytes differ between the + // base game and all game updates, so we cannot use them as part of our prefix. + offset += 8; + + // Stubs the call to the function that checks for Primal Reversions and + // Mega Pokemon + code[offset] = 0x00; + code[offset + 1] = 0x00; + code[offset + 2] = 0x00; + code[offset + 3] = 0x00; + + if (romEntry.romType == Gen7Constants.Type_USUM) { + // In Sun/Moon, Greninja-A and Zygarde-C are treated as Mega Pokemon + // and handled by the function above. In USUM, they are handled by a + // different function, along with Necrozma-U. This stubs the call + // to that function. + code[offset + 8] = 0x00; + code[offset + 9] = 0x00; + code[offset + 10] = 0x00; + code[offset + 11] = 0x00; + } + } + + // Additionally, upon completing a battle, Kyogre-P, Groudon-P, + // and Wishiwashi-S are forcibly returned to their base forme. + // Minior is also forcibly set to the "correct" Core forme. + // This patches the Battle CRO to prevent this from happening. + byte[] battleCRO = readFile(romEntry.getFile("Battle")); + offset = find(battleCRO, Gen7Constants.afterBattleFormeReversionPrefix); + if (offset > 0) { + offset += Gen7Constants.afterBattleFormeReversionPrefix.length() / 2; // because it was a prefix + + // Stubs the call to pml::pokepara::CoreParam::ChangeFormNo for Kyogre + battleCRO[offset] = 0x00; + battleCRO[offset + 1] = 0x00; + battleCRO[offset + 2] = 0x00; + battleCRO[offset + 3] = 0x00; + + // Stubs the call to pml::pokepara::CoreParam::ChangeFormNo for Groudon + battleCRO[offset + 60] = 0x00; + battleCRO[offset + 61] = 0x00; + battleCRO[offset + 62] = 0x00; + battleCRO[offset + 63] = 0x00; + + // Stubs the call to pml::pokepara::CoreParam::ChangeFormNo for Wishiwashi + battleCRO[offset + 92] = 0x00; + battleCRO[offset + 93] = 0x00; + battleCRO[offset + 94] = 0x00; + battleCRO[offset + 95] = 0x00; + + // Stubs the call to pml::pokepara::CoreParam::ChangeFormNo for Minior + battleCRO[offset + 148] = 0x00; + battleCRO[offset + 149] = 0x00; + battleCRO[offset + 150] = 0x00; + battleCRO[offset + 151] = 0x00; + + writeFile(romEntry.getFile("Battle"), battleCRO); + } + } + + @Override + protected String getGameAcronym() { + return romEntry.acronym; + } + + @Override + protected boolean isGameUpdateSupported(int version) { + return version == romEntry.numbers.get("FullyUpdatedVersionNumber"); + } + + @Override + protected String getGameVersion() { + List titleScreenText = getStrings(false, romEntry.getInt("TitleScreenTextOffset")); + if (titleScreenText.size() > romEntry.getInt("UpdateStringOffset")) { + return titleScreenText.get(romEntry.getInt("UpdateStringOffset")); + } + // This shouldn't be seen by users, but is correct assuming we accidentally show it to them. + return "Unpatched"; + } + + @Override + public List getPokemon() { + return pokemonList; + } + + @Override + public List getPokemonInclFormes() { + return pokemonListInclFormes; + } + + @Override + public List getAltFormes() { + int formeCount = Gen7Constants.getFormeCount(romEntry.romType); + int pokemonCount = Gen7Constants.getPokemonCount(romEntry.romType); + return pokemonListInclFormes.subList(pokemonCount + 1, pokemonCount + formeCount + 1); + } + + @Override + public List getMegaEvolutions() { + return megaEvolutions; + } + + @Override + public Pokemon getAltFormeOfPokemon(Pokemon pk, int forme) { + int pokeNum = absolutePokeNumByBaseForme.getOrDefault(pk.number,dummyAbsolutePokeNums).getOrDefault(forme,0); + return pokeNum != 0 ? !pokes[pokeNum].actuallyCosmetic ? pokes[pokeNum] : pokes[pokeNum].baseForme : pk; + } + + @Override + public List getIrregularFormes() { + return Gen7Constants.getIrregularFormes(romEntry.romType).stream().map(i -> pokes[i]).collect(Collectors.toList()); + } + + @Override + public boolean hasFunctionalFormes() { + return true; + } + + @Override + public List getStarters() { + List starters = new ArrayList<>(); + try { + GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true); + byte[] giftsFile = staticGarc.files.get(0).get(0); + for (int i = 0; i < 3; i++) { + int offset = i * 0x14; + StaticEncounter se = new StaticEncounter(); + int species = FileFunctions.read2ByteInt(giftsFile, offset); + Pokemon pokemon = pokes[species]; + int forme = giftsFile[offset + 2]; + if (forme > pokemon.cosmeticForms && forme != 30 && forme != 31) { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(species, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + pokemon = pokes[speciesWithForme]; + } + se.pkmn = pokemon; + se.forme = forme; + se.level = giftsFile[offset + 3]; + starters.add(se); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return starters.stream().map(pk -> pk.pkmn).collect(Collectors.toList()); + } + + @Override + public boolean setStarters(List newStarters) { + try { + GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true); + byte[] giftsFile = staticGarc.files.get(0).get(0); + for (int i = 0; i < 3; i++) { + int offset = i * 0x14; + Pokemon starter = newStarters.get(i); + int forme = 0; + boolean checkCosmetics = true; + if (starter.formeNumber > 0) { + forme = starter.formeNumber; + starter = starter.baseForme; + checkCosmetics = false; + } + if (checkCosmetics && starter.cosmeticForms > 0) { + forme = starter.getCosmeticFormNumber(this.random.nextInt(starter.cosmeticForms)); + } else if (!checkCosmetics && starter.cosmeticForms > 0) { + forme += starter.getCosmeticFormNumber(this.random.nextInt(starter.cosmeticForms)); + } + writeWord(giftsFile, offset, starter.number); + giftsFile[offset + 2] = (byte) forme; + } + writeGARC(romEntry.getFile("StaticPokemon"), staticGarc); + setStarterText(newStarters); + return true; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + // TODO: We should be editing the script file so that the game reads in our new + // starters; this way, strings that depend on the starter defined in the script + // would work without any modification. Instead, we're just manually editing all + // strings here, and if a string originally referred to the starter in the script, + // we just hardcode the starter's name if we can get away with it. + private void setStarterText(List newStarters) { + int starterTextIndex = romEntry.getInt("StarterTextOffset"); + List starterText = getStrings(true, starterTextIndex); + if (romEntry.romType == Gen7Constants.Type_USUM) { + String rowletDescriptor = newStarters.get(0).name + starterText.get(1).substring(6); + String littenDescriptor = newStarters.get(1).name + starterText.get(2).substring(6); + String popplioDescriptor = newStarters.get(2).name + starterText.get(3).substring(7); + starterText.set(1, rowletDescriptor); + starterText.set(2, littenDescriptor); + starterText.set(3, popplioDescriptor); + for (int i = 0; i < 3; i++) { + int confirmationOffset = i + 7; + int optionOffset = i + 14; + Pokemon starter = newStarters.get(i); + String confirmationText = String.format("So, you wanna go with the %s-type Pokémon\\n%s?[VAR 0114(0005)]", + starter.primaryType.camelCase(), starter.name); + String optionText = starter.name; + starterText.set(confirmationOffset, confirmationText); + starterText.set(optionOffset, optionText); + } + } else { + String rowletDescriptor = newStarters.get(0).name + starterText.get(11).substring(6); + String littenDescriptor = newStarters.get(1).name + starterText.get(12).substring(6); + String popplioDescriptor = newStarters.get(2).name + starterText.get(13).substring(7); + starterText.set(11, rowletDescriptor); + starterText.set(12, littenDescriptor); + starterText.set(13, popplioDescriptor); + for (int i = 0; i < 3; i++) { + int optionOffset = i + 1; + int confirmationOffset = i + 4; + int flavorOffset = i + 35; + Pokemon starter = newStarters.get(i); + String optionText = String.format("The %s-type %s", starter.primaryType.camelCase(), starter.name); + String confirmationText = String.format("Will you choose the %s-type Pokémon\\n%s?[VAR 0114(0008)]", + starter.primaryType.camelCase(), starter.name); + String flavorSubstring = starterText.get(flavorOffset).substring(starterText.get(flavorOffset).indexOf("\\n")); + String flavorText = String.format("The %s-type %s", starter.primaryType.camelCase(), starter.name) + flavorSubstring; + starterText.set(optionOffset, optionText); + starterText.set(confirmationOffset, confirmationText); + starterText.set(flavorOffset, flavorText); + } + } + setStrings(true, starterTextIndex, starterText); + } + + @Override + public boolean hasStarterAltFormes() { + return true; + } + + @Override + public int starterCount() { + return 3; + } + + @Override + public Map getUpdatedPokemonStats(int generation) { + Map map = GlobalConstants.getStatChanges(generation); + int aegislashBlade = Species.SMFormes.aegislashB; + if (romEntry.romType == Gen7Constants.Type_USUM) { + aegislashBlade = Species.USUMFormes.aegislashB; + } + switch(generation) { + case 8: + map.put(aegislashBlade, new StatChange(Stat.ATK.val | Stat.SPATK.val, 140, 140)); + break; + } + return map; + } + + @Override + public boolean supportsStarterHeldItems() { + return true; + } + + @Override + public List getStarterHeldItems() { + List starterHeldItems = new ArrayList<>(); + try { + GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true); + byte[] giftsFile = staticGarc.files.get(0).get(0); + for (int i = 0; i < 3; i++) { + int offset = i * 0x14; + int item = FileFunctions.read2ByteInt(giftsFile, offset + 8); + starterHeldItems.add(item); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return starterHeldItems; + } + + @Override + public void setStarterHeldItems(List items) { + try { + GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true); + byte[] giftsFile = staticGarc.files.get(0).get(0); + for (int i = 0; i < 3; i++) { + int offset = i * 0x14; + int item = items.get(i); + FileFunctions.write2ByteInt(giftsFile, offset + 8, item); + } + writeGARC(romEntry.getFile("StaticPokemon"), staticGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getMoves() { + return Arrays.asList(moves); + } + + @Override + public List getEncounters(boolean useTimeOfDay) { + List encounters = new ArrayList<>(); + for (AreaData areaData : areaDataList) { + if (!areaData.hasTables) { + continue; + } + for (int i = 0; i < areaData.encounterTables.size(); i++) { + byte[] encounterTable = areaData.encounterTables.get(i); + byte[] dayTable = new byte[0x164]; + System.arraycopy(encounterTable, 0, dayTable, 0, 0x164); + EncounterSet dayEncounters = readEncounterTable(dayTable); + if (!useTimeOfDay) { + dayEncounters.displayName = areaData.name + ", Table " + (i + 1); + encounters.add(dayEncounters); + } else { + dayEncounters.displayName = areaData.name + ", Table " + (i + 1) + " (Day)"; + encounters.add(dayEncounters); + byte[] nightTable = new byte[0x164]; + System.arraycopy(encounterTable, 0x164, nightTable, 0, 0x164); + EncounterSet nightEncounters = readEncounterTable(nightTable); + nightEncounters.displayName = areaData.name + ", Table " + (i + 1) + " (Night)"; + encounters.add(nightEncounters); + } + } + } + return encounters; + } + + private EncounterSet readEncounterTable(byte[] encounterTable) { + int minLevel = encounterTable[0]; + int maxLevel = encounterTable[1]; + EncounterSet es = new EncounterSet(); + es.rate = 1; + for (int i = 0; i < 10; i++) { + int offset = 0xC + (i * 4); + int speciesAndFormeData = readWord(encounterTable, offset); + int species = speciesAndFormeData & 0x7FF; + int forme = speciesAndFormeData >> 11; + if (species != 0) { + Encounter e = new Encounter(); + e.pokemon = getPokemonForEncounter(species, forme); + e.formeNumber = forme; + e.level = minLevel; + e.maxLevel = maxLevel; + es.encounters.add(e); + + // Get all the SOS encounters for this non-SOS encounter + for (int j = 1; j < 8; j++) { + species = readWord(encounterTable, offset + (40 * j)) & 0x7FF; + forme = readWord(encounterTable, offset + (40 * j)) >> 11; + Encounter sos = new Encounter(); + sos.pokemon = getPokemonForEncounter(species, forme); + sos.formeNumber = forme; + sos.level = minLevel; + sos.maxLevel = maxLevel; + sos.isSOS = true; + sos.sosType = SOSType.GENERIC; + es.encounters.add(sos); + } + } + } + + // Get the weather SOS encounters for this area + for (int i = 0; i < 6; i++) { + int offset = 0x14C + (i * 4); + int species = readWord(encounterTable, offset) & 0x7FF; + int forme = readWord(encounterTable, offset) >> 11; + if (species != 0) { + Encounter weatherSOS = new Encounter(); + weatherSOS.pokemon = getPokemonForEncounter(species, forme); + weatherSOS.formeNumber = forme; + weatherSOS.level = minLevel; + weatherSOS.maxLevel = maxLevel; + weatherSOS.isSOS = true; + weatherSOS.sosType = getSOSTypeForIndex(i); + es.encounters.add(weatherSOS); + } + } + return es; + } + + private SOSType getSOSTypeForIndex(int index) { + if (index / 2 == 0) { + return SOSType.RAIN; + } else if (index / 2 == 1) { + return SOSType.HAIL; + } else { + return SOSType.SAND; + } + } + + private Pokemon getPokemonForEncounter(int species, int forme) { + Pokemon pokemon = pokes[species]; + + // If the forme is purely cosmetic, just use the base forme as the Pokemon + // for this encounter (the cosmetic forme will be stored in the encounter). + if (forme <= pokemon.cosmeticForms || forme == 30 || forme == 31) { + return pokemon; + } else { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(species, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + return pokes[speciesWithForme]; + } + } + + @Override + public void setEncounters(boolean useTimeOfDay, List encountersList) { + Iterator encounters = encountersList.iterator(); + for (AreaData areaData : areaDataList) { + if (!areaData.hasTables) { + continue; + } + + for (int i = 0; i < areaData.encounterTables.size(); i++) { + byte[] encounterTable = areaData.encounterTables.get(i); + if (useTimeOfDay) { + EncounterSet dayEncounters = encounters.next(); + EncounterSet nightEncounters = encounters.next(); + writeEncounterTable(encounterTable, 0, dayEncounters.encounters); + writeEncounterTable(encounterTable, 0x164, nightEncounters.encounters); + } else { + EncounterSet dayEncounters = encounters.next(); + writeEncounterTable(encounterTable, 0, dayEncounters.encounters); + writeEncounterTable(encounterTable, 0x164, dayEncounters.encounters); + } + } + } + + try { + saveAreaData(); + patchMiniorEncounterCode(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void writeEncounterTable(byte[] encounterTable, int offset, List encounters) { + Iterator encounter = encounters.iterator(); + Encounter firstEncounter = encounters.get(0); + encounterTable[offset] = (byte) firstEncounter.level; + encounterTable[offset + 1] = (byte) firstEncounter.maxLevel; + int numberOfEncounterSlots = encounters.size() / 8; + for (int i = 0; i < numberOfEncounterSlots; i++) { + int currentOffset = offset + 0xC + (i * 4); + Encounter enc = encounter.next(); + int speciesAndFormeData = (enc.formeNumber << 11) + enc.pokemon.getBaseNumber(); + writeWord(encounterTable, currentOffset, speciesAndFormeData); + + // SOS encounters for this encounter + for (int j = 1; j < 8; j++) { + Encounter sosEncounter = encounter.next(); + speciesAndFormeData = (sosEncounter.formeNumber << 11) + sosEncounter.pokemon.getBaseNumber(); + writeWord(encounterTable, currentOffset + (40 * j), speciesAndFormeData); + } + } + + // Weather SOS encounters + if (encounters.size() != numberOfEncounterSlots * 8) { + for (int i = 0; i < 6; i++) { + int currentOffset = offset + 0x14C + (i * 4); + Encounter weatherSOSEncounter = encounter.next(); + int speciesAndFormeData = (weatherSOSEncounter.formeNumber << 11) + weatherSOSEncounter.pokemon.getBaseNumber(); + writeWord(encounterTable, currentOffset, speciesAndFormeData); + } + } + } + + private List getAreaData() throws IOException { + GARCArchive worldDataGarc = readGARC(romEntry.getFile("WorldData"), false); + List worlds = new ArrayList<>(); + for (Map file : worldDataGarc.files) { + byte[] world = Mini.UnpackMini(file.get(0), "WD")[0]; + worlds.add(world); + } + GARCArchive zoneDataGarc = readGARC(romEntry.getFile("ZoneData"), false); + byte[] zoneDataBytes = zoneDataGarc.getFile(0); + byte[] worldData = zoneDataGarc.getFile(1); + List locationList = createGoodLocationList(); + ZoneData[] zoneData = getZoneData(zoneDataBytes, worldData, locationList, worlds); + encounterGarc = readGARC(romEntry.getFile("WildPokemon"), Gen7Constants.getRelevantEncounterFiles(romEntry.romType)); + int fileCount = encounterGarc.files.size(); + int numberOfAreas = fileCount / 11; + AreaData[] areaData = new AreaData[numberOfAreas]; + for (int i = 0; i < numberOfAreas; i++) { + int areaOffset = i; + areaData[i] = new AreaData(); + areaData[i].fileNumber = 9 + (11 * i); + areaData[i].zones = Arrays.stream(zoneData).filter((zone -> zone.areaIndex == areaOffset)).collect(Collectors.toList()); + areaData[i].name = getAreaNameFromZones(areaData[i].zones); + byte[] encounterData = encounterGarc.getFile(areaData[i].fileNumber); + if (encounterData.length == 0) { + areaData[i].hasTables = false; + } else { + byte[][] encounterTables = Mini.UnpackMini(encounterData, "EA"); + areaData[i].hasTables = Arrays.stream(encounterTables).anyMatch(t -> t.length > 0); + if (!areaData[i].hasTables) { + continue; + } + + for (byte[] encounterTable : encounterTables) { + byte[] trimmedEncounterTable = new byte[0x2C8]; + System.arraycopy(encounterTable, 4, trimmedEncounterTable, 0, 0x2C8); + areaData[i].encounterTables.add(trimmedEncounterTable); + } + } + } + + return Arrays.asList(areaData); + } + + private void saveAreaData() throws IOException { + for (AreaData areaData : areaDataList) { + if (areaData.hasTables) { + byte[] encounterData = encounterGarc.getFile(areaData.fileNumber); + byte[][] encounterTables = Mini.UnpackMini(encounterData, "EA"); + for (int i = 0; i < encounterTables.length; i++) { + byte[] originalEncounterTable = encounterTables[i]; + byte[] newEncounterTable = areaData.encounterTables.get(i); + System.arraycopy(newEncounterTable, 0, originalEncounterTable, 4, newEncounterTable.length); + } + byte[] newEncounterData = Mini.PackMini(encounterTables, "EA"); + encounterGarc.setFile(areaData.fileNumber, newEncounterData); + } + } + } + + private List createGoodLocationList() { + List locationList = getStrings(false, romEntry.getInt("MapNamesTextOffset")); + List goodLocationList = new ArrayList<>(locationList); + for (int i = 0; i < locationList.size(); i += 2) { + // The location list contains both areas and subareas. If a subarea is associated with an area, it will + // appear directly after it. This code combines these subarea and area names. + String subarea = locationList.get(i + 1); + if (!subarea.isEmpty() && subarea.charAt(0) != '[') { + String updatedLocation = goodLocationList.get(i) + " (" + subarea + ")"; + goodLocationList.set(i, updatedLocation); + } + + // Some areas appear in the location list multiple times and don't have any subarea name to distinguish + // them. This code distinguishes them by appending the number of times they've appeared previously to + // the area name. + if (i > 0) { + List goodLocationUpToCurrent = goodLocationList.stream().limit(i - 1).collect(Collectors.toList()); + if (!goodLocationList.get(i).isEmpty() && goodLocationUpToCurrent.contains(goodLocationList.get(i))) { + int numberOfUsages = Collections.frequency(goodLocationUpToCurrent, goodLocationList.get(i)); + String updatedLocation = goodLocationList.get(i) + " (" + (numberOfUsages + 1) + ")"; + goodLocationList.set(i, updatedLocation); + } + } + } + return goodLocationList; + } + + private ZoneData[] getZoneData(byte[] zoneDataBytes, byte[] worldData, List locationList, List worlds) { + ZoneData[] zoneData = new ZoneData[zoneDataBytes.length / ZoneData.size]; + for (int i = 0; i < zoneData.length; i++) { + zoneData[i] = new ZoneData(zoneDataBytes, i); + zoneData[i].worldIndex = FileFunctions.read2ByteInt(worldData, i * 0x2); + zoneData[i].locationName = locationList.get(zoneData[i].parentMap); + + byte[] world = worlds.get(zoneData[i].worldIndex); + int mappingOffset = FileFunctions.readFullInt(world, 0x8); + for (int offset = mappingOffset; offset < world.length; offset += 4) { + int potentialZoneIndex = FileFunctions.read2ByteInt(world, offset); + if (potentialZoneIndex == i) { + zoneData[i].areaIndex = FileFunctions.read2ByteInt(world, offset + 0x2); + break; + } + } + } + return zoneData; + } + + private String getAreaNameFromZones(List zoneData) { + Set uniqueZoneNames = new HashSet<>(); + for (ZoneData zone : zoneData) { + uniqueZoneNames.add(zone.locationName); + } + return String.join(" / ", uniqueZoneNames); + } + + private void patchMiniorEncounterCode() { + int offset = find(code, Gen7Constants.miniorWildEncounterPatchPrefix); + if (offset > 0) { + offset += Gen7Constants.miniorWildEncounterPatchPrefix.length() / 2; + + // When deciding the *actual* forme for a wild encounter (versus the forme stored + // in the encounter data), the game has a hardcoded check for Minior's species ID. + // If the species is Minior, then it branches to code that randomly selects a forme + // for one of Minior's seven Meteor forms. As a consequence, you can't directly + // spawn Minior's Core forms; the forme number will just be replaced. The below + // code nops out the beq instruction so that Minior-C can be spawned directly. + code[offset] = 0x00; + code[offset + 1] = 0x00; + code[offset + 2] = 0x00; + code[offset + 3] = 0x00; + } + } + + @Override + public List getTrainers() { + List allTrainers = new ArrayList<>(); + try { + GARCArchive trainers = this.readGARC(romEntry.getFile("TrainerData"),true); + GARCArchive trpokes = this.readGARC(romEntry.getFile("TrainerPokemon"),true); + int trainernum = trainers.files.size(); + List tclasses = this.getTrainerClassNames(); + List tnames = this.getTrainerNames(); + Map tnamesMap = new TreeMap<>(); + for (int i = 0; i < tnames.size(); i++) { + tnamesMap.put(i,tnames.get(i)); + } + for (int i = 1; i < trainernum; i++) { + byte[] trainer = trainers.files.get(i).get(0); + byte[] trpoke = trpokes.files.get(i).get(0); + Trainer tr = new Trainer(); + tr.poketype = trainer[13] & 0xFF; + tr.index = i; + tr.trainerclass = trainer[0] & 0xFF; + int battleType = trainer[2] & 0xFF; + int numPokes = trainer[3] & 0xFF; + int trainerAILevel = trainer[12] & 0xFF; + boolean healer = trainer[15] != 0; + int pokeOffs = 0; + String trainerClass = tclasses.get(tr.trainerclass); + String trainerName = tnamesMap.getOrDefault(i - 1, "UNKNOWN"); + tr.fullDisplayName = trainerClass + " " + trainerName; + + for (int poke = 0; poke < numPokes; poke++) { + // Structure is + // IV SB LV LV SP SP FRM FRM + // (HI HI) + // (M1 M1 M2 M2 M3 M3 M4 M4) + // where SB = 0 0 Ab Ab 0 0 Fm Ml + // Ab Ab = ability number, 0 for random + // Fm = 1 for forced female + // Ml = 1 for forced male + // There's also a trainer flag to force gender, but + // this allows fixed teams with mixed genders. + + // int secondbyte = trpoke[pokeOffs + 1] & 0xFF; + int abilityAndFlag = trpoke[pokeOffs]; + int level = readWord(trpoke, pokeOffs + 14); + int species = readWord(trpoke, pokeOffs + 16); + int formnum = readWord(trpoke, pokeOffs + 18); + TrainerPokemon tpk = new TrainerPokemon(); + tpk.abilitySlot = (abilityAndFlag >>> 4) & 0xF; + tpk.forcedGenderFlag = (abilityAndFlag & 0xF); + tpk.nature = trpoke[pokeOffs + 1]; + tpk.hpEVs = trpoke[pokeOffs + 2]; + tpk.atkEVs = trpoke[pokeOffs + 3]; + tpk.defEVs = trpoke[pokeOffs + 4]; + tpk.spatkEVs = trpoke[pokeOffs + 5]; + tpk.spdefEVs = trpoke[pokeOffs + 6]; + tpk.speedEVs = trpoke[pokeOffs + 7]; + tpk.IVs = FileFunctions.readFullInt(trpoke, pokeOffs + 8); + tpk.level = level; + if (romEntry.romType == Gen7Constants.Type_USUM) { + if (i == 78) { + if (poke == 3 && tpk.level == 16 && tr.pokemon.get(0).level == 16) { + tpk.level = 14; + } + } + } + tpk.pokemon = pokes[species]; + tpk.forme = formnum; + tpk.formeSuffix = Gen7Constants.getFormeSuffixByBaseForme(species,formnum); + pokeOffs += 20; + tpk.heldItem = readWord(trpoke, pokeOffs); + tpk.hasMegaStone = Gen6Constants.isMegaStone(tpk.heldItem); + tpk.hasZCrystal = Gen7Constants.isZCrystal(tpk.heldItem); + pokeOffs += 4; + for (int move = 0; move < 4; move++) { + tpk.moves[move] = readWord(trpoke, pokeOffs + (move*2)); + } + pokeOffs += 8; + tr.pokemon.add(tpk); + } + allTrainers.add(tr); + } + if (romEntry.romType == Gen7Constants.Type_SM) { + Gen7Constants.tagTrainersSM(allTrainers); + Gen7Constants.setMultiBattleStatusSM(allTrainers); + } else { + Gen7Constants.tagTrainersUSUM(allTrainers); + Gen7Constants.setMultiBattleStatusUSUM(allTrainers); + Gen7Constants.setForcedRivalStarterPositionsUSUM(allTrainers); + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + return allTrainers; + } + + @Override + public List getMainPlaythroughTrainers() { + return new ArrayList<>(); + } + + @Override + public List getEliteFourTrainers(boolean isChallengeMode) { + return Arrays.stream(romEntry.arrayEntries.get("EliteFourIndices")).boxed().collect(Collectors.toList()); + } + + @Override + public void setTrainers(List trainerData, boolean doubleBattleMode) { + Iterator allTrainers = trainerData.iterator(); + try { + GARCArchive trainers = this.readGARC(romEntry.getFile("TrainerData"),true); + GARCArchive trpokes = this.readGARC(romEntry.getFile("TrainerPokemon"),true); + // Get current movesets in case we need to reset them for certain + // trainer mons. + Map> movesets = this.getMovesLearnt(); + int trainernum = trainers.files.size(); + for (int i = 1; i < trainernum; i++) { + byte[] trainer = trainers.files.get(i).get(0); + Trainer tr = allTrainers.next(); + int offset = 0; + trainer[13] = (byte) tr.poketype; + int numPokes = tr.pokemon.size(); + trainer[offset+3] = (byte) numPokes; + + if (doubleBattleMode) { + if (!tr.skipImportant()) { + if (trainer[offset+2] == 0) { + trainer[offset+2] = 1; + trainer[offset+12] |= 0x8; // Flag that needs to be set for trainers not to attack their own pokes + } + } + } + + int bytesNeeded = 32 * numPokes; + byte[] trpoke = new byte[bytesNeeded]; + int pokeOffs = 0; + Iterator tpokes = tr.pokemon.iterator(); + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon tp = tpokes.next(); + byte abilityAndFlag = (byte)((tp.abilitySlot << 4) | tp.forcedGenderFlag); + trpoke[pokeOffs] = abilityAndFlag; + trpoke[pokeOffs + 1] = tp.nature; + trpoke[pokeOffs + 2] = tp.hpEVs; + trpoke[pokeOffs + 3] = tp.atkEVs; + trpoke[pokeOffs + 4] = tp.defEVs; + trpoke[pokeOffs + 5] = tp.spatkEVs; + trpoke[pokeOffs + 6] = tp.spdefEVs; + trpoke[pokeOffs + 7] = tp.speedEVs; + FileFunctions.writeFullInt(trpoke, pokeOffs + 8, tp.IVs); + writeWord(trpoke, pokeOffs + 14, tp.level); + writeWord(trpoke, pokeOffs + 16, tp.pokemon.number); + writeWord(trpoke, pokeOffs + 18, tp.forme); + pokeOffs += 20; + writeWord(trpoke, pokeOffs, tp.heldItem); + pokeOffs += 4; + if (tp.resetMoves) { + int[] pokeMoves = RomFunctions.getMovesAtLevel(getAltFormeOfPokemon(tp.pokemon, tp.forme).number, movesets, tp.level); + for (int m = 0; m < 4; m++) { + writeWord(trpoke, pokeOffs + m * 2, pokeMoves[m]); + } + if (Gen7Constants.heldZCrystals.contains(tp.heldItem)) { // Choose a new Z-Crystal at random based on the types of the Pokemon's moves + int chosenMove = this.random.nextInt(Arrays.stream(pokeMoves).filter(mv -> mv != 0).toArray().length); + int newZCrystal = Gen7Constants.heldZCrystals.get((int)Gen7Constants.typeToByte(moves[pokeMoves[chosenMove]].type)); + writeWord(trpoke, pokeOffs - 4, newZCrystal); + } + } else { + writeWord(trpoke, pokeOffs, tp.moves[0]); + writeWord(trpoke, pokeOffs + 2, tp.moves[1]); + writeWord(trpoke, pokeOffs + 4, tp.moves[2]); + writeWord(trpoke, pokeOffs + 6, tp.moves[3]); + if (Gen7Constants.heldZCrystals.contains(tp.heldItem)) { // Choose a new Z-Crystal at random based on the types of the Pokemon's moves + int chosenMove = this.random.nextInt(Arrays.stream(tp.moves).filter(mv -> mv != 0).toArray().length); + int newZCrystal = Gen7Constants.heldZCrystals.get((int)Gen7Constants.typeToByte(moves[tp.moves[chosenMove]].type)); + writeWord(trpoke, pokeOffs - 4, newZCrystal); + } + } + pokeOffs += 8; + } + trpokes.setFile(i,trpoke); + } + this.writeGARC(romEntry.getFile("TrainerData"), trainers); + this.writeGARC(romEntry.getFile("TrainerPokemon"), trpokes); + + // In Sun/Moon, Beast Lusamine's Pokemon have aura boosts that are hardcoded. + if (romEntry.romType == Gen7Constants.Type_SM) { + Trainer beastLusamine = trainerData.get(Gen7Constants.beastLusamineTrainerIndex); + setBeastLusaminePokemonBuffs(beastLusamine); + } + } catch (IOException ex) { + throw new RandomizerIOException(ex); + } + } + + private void setBeastLusaminePokemonBuffs(Trainer beastLusamine) throws IOException { + byte[] battleCRO = readFile(romEntry.getFile("Battle")); + int offset = find(battleCRO, Gen7Constants.beastLusaminePokemonBoostsPrefix); + if (offset > 0) { + offset += Gen7Constants.beastLusaminePokemonBoostsPrefix.length() / 2; // because it was a prefix + + // The game only has room for five boost entries, where each boost entry is determined by species ID. + // However, Beast Lusamine might have duplicates in her party, meaning that two Pokemon can share the + // same boost entry. First, figure out all the unique Pokemon in her party. We avoid using a Set here + // in order to preserve the original ordering; we want to make sure to boost the *first* five Pokemon + List uniquePokemon = new ArrayList<>(); + for (int i = 0; i < beastLusamine.pokemon.size(); i++) { + if (!uniquePokemon.contains(beastLusamine.pokemon.get(i).pokemon)) { + uniquePokemon.add(beastLusamine.pokemon.get(i).pokemon); + } + } + int numberOfBoostEntries = Math.min(uniquePokemon.size(), 5); + for (int i = 0; i < numberOfBoostEntries; i++) { + Pokemon boostedPokemon = uniquePokemon.get(i); + int auraNumber = getAuraNumberForHighestStat(boostedPokemon); + int speciesNumber = boostedPokemon.getBaseNumber(); + FileFunctions.write2ByteInt(battleCRO, offset + (i * 0x10), speciesNumber); + battleCRO[offset + (i * 0x10) + 2] = (byte) auraNumber; + } + writeFile(romEntry.getFile("Battle"), battleCRO); + } + } + + // Finds the highest stat for the purposes of setting the aura boost on Beast Lusamine's Pokemon. + // In the case where two or more stats are tied for the highest stat, it randomly selects one. + private int getAuraNumberForHighestStat(Pokemon boostedPokemon) { + int currentBestStat = boostedPokemon.attack; + int auraNumber = 1; + boolean useDefenseAura = boostedPokemon.defense > currentBestStat || (boostedPokemon.defense == currentBestStat && random.nextBoolean()); + if (useDefenseAura) { + currentBestStat = boostedPokemon.defense; + auraNumber = 2; + } + boolean useSpAtkAura = boostedPokemon.spatk > currentBestStat || (boostedPokemon.spatk == currentBestStat && random.nextBoolean()); + if (useSpAtkAura) { + currentBestStat = boostedPokemon.spatk; + auraNumber = 3; + } + boolean useSpDefAura = boostedPokemon.spdef > currentBestStat || (boostedPokemon.spdef == currentBestStat && random.nextBoolean()); + if (useSpDefAura) { + currentBestStat = boostedPokemon.spdef; + auraNumber = 4; + } + boolean useSpeedAura = boostedPokemon.speed > currentBestStat || (boostedPokemon.speed == currentBestStat && random.nextBoolean()); + if (useSpeedAura) { + auraNumber = 5; + } + return auraNumber; + } + + @Override + public List getEvolutionItems() { + return Gen7Constants.evolutionItems; + } + + @Override + public Map> getMovesLearnt() { + Map> movesets = new TreeMap<>(); + try { + GARCArchive movesLearnt = this.readGARC(romEntry.getFile("PokemonMovesets"),true); + int formeCount = Gen7Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= Gen7Constants.getPokemonCount(romEntry.romType) + formeCount; i++) { + Pokemon pkmn = pokes[i]; + byte[] movedata; + movedata = movesLearnt.files.get(i).get(0); + int moveDataLoc = 0; + List learnt = new ArrayList<>(); + while (readWord(movedata, moveDataLoc) != 0xFFFF || readWord(movedata, moveDataLoc + 2) != 0xFFFF) { + int move = readWord(movedata, moveDataLoc); + int level = readWord(movedata, moveDataLoc + 2); + MoveLearnt ml = new MoveLearnt(); + ml.level = level; + ml.move = move; + learnt.add(ml); + moveDataLoc += 4; + } + movesets.put(pkmn.number, learnt); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return movesets; + } + + @Override + public void setMovesLearnt(Map> movesets) { + try { + GARCArchive movesLearnt = readGARC(romEntry.getFile("PokemonMovesets"),true); + int formeCount = Gen7Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= Gen7Constants.getPokemonCount(romEntry.romType) + formeCount; i++) { + Pokemon pkmn = pokes[i]; + List learnt = movesets.get(pkmn.number); + int sizeNeeded = learnt.size() * 4 + 4; + byte[] moveset = new byte[sizeNeeded]; + int j = 0; + for (; j < learnt.size(); j++) { + MoveLearnt ml = learnt.get(j); + writeWord(moveset, j * 4, ml.move); + writeWord(moveset, j * 4 + 2, ml.level); + } + writeWord(moveset, j * 4, 0xFFFF); + writeWord(moveset, j * 4 + 2, 0xFFFF); + movesLearnt.setFile(i, moveset); + } + // Save + this.writeGARC(romEntry.getFile("PokemonMovesets"), movesLearnt); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + @Override + public Map> getEggMoves() { + Map> eggMoves = new TreeMap<>(); + try { + GARCArchive eggMovesGarc = this.readGARC(romEntry.getFile("EggMoves"),true); + TreeMap altFormeEggMoveFiles = new TreeMap<>(); + for (int i = 1; i <= Gen7Constants.getPokemonCount(romEntry.romType); i++) { + Pokemon pkmn = pokes[i]; + byte[] movedata = eggMovesGarc.files.get(i).get(0); + int formeReference = readWord(movedata, 0); + if (formeReference != pkmn.number) { + altFormeEggMoveFiles.put(pkmn, formeReference); + } + int numberOfEggMoves = readWord(movedata, 2); + List moves = new ArrayList<>(); + for (int j = 0; j < numberOfEggMoves; j++) { + int move = readWord(movedata, 4 + (j * 2)); + moves.add(move); + } + eggMoves.put(pkmn.number, moves); + } + Iterator iter = altFormeEggMoveFiles.keySet().iterator(); + while (iter.hasNext()) { + Pokemon originalForme = iter.next(); + int formeNumber = 1; + int fileNumber = altFormeEggMoveFiles.get(originalForme); + Pokemon altForme = getAltFormeOfPokemon(originalForme, formeNumber); + while (!originalForme.equals(altForme)) { + byte[] movedata = eggMovesGarc.files.get(fileNumber).get(0); + int numberOfEggMoves = readWord(movedata, 2); + List moves = new ArrayList<>(); + for (int j = 0; j < numberOfEggMoves; j++) { + int move = readWord(movedata, 4 + (j * 2)); + moves.add(move); + } + eggMoves.put(altForme.number, moves); + formeNumber++; + fileNumber++; + altForme = getAltFormeOfPokemon(originalForme, formeNumber); + } + iter.remove(); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return eggMoves; + } + + @Override + public void setEggMoves(Map> eggMoves) { + try { + GARCArchive eggMovesGarc = this.readGARC(romEntry.getFile("EggMoves"), true); + TreeMap altFormeEggMoveFiles = new TreeMap<>(); + for (int i = 1; i <= Gen7Constants.getPokemonCount(romEntry.romType); i++) { + Pokemon pkmn = pokes[i]; + byte[] movedata = eggMovesGarc.files.get(i).get(0); + int formeReference = readWord(movedata, 0); + if (formeReference != pkmn.number) { + altFormeEggMoveFiles.put(pkmn, formeReference); + } + List moves = eggMoves.get(pkmn.number); + for (int j = 0; j < moves.size(); j++) { + writeWord(movedata, 4 + (j * 2), moves.get(j)); + } + } + Iterator iter = altFormeEggMoveFiles.keySet().iterator(); + while (iter.hasNext()) { + Pokemon originalForme = iter.next(); + int formeNumber = 1; + int fileNumber = altFormeEggMoveFiles.get(originalForme); + Pokemon altForme = getAltFormeOfPokemon(originalForme, formeNumber); + while (!originalForme.equals(altForme)) { + byte[] movedata = eggMovesGarc.files.get(fileNumber).get(0); + List moves = eggMoves.get(altForme.number); + for (int j = 0; j < moves.size(); j++) { + writeWord(movedata, 4 + (j * 2), moves.get(j)); + } + formeNumber++; + fileNumber++; + altForme = getAltFormeOfPokemon(originalForme, formeNumber); + } + iter.remove(); + } + // Save + this.writeGARC(romEntry.getFile("EggMoves"), eggMovesGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public boolean canChangeStaticPokemon() { + return true; + } + + @Override + public boolean hasStaticAltFormes() { + return true; + } + + @Override + public boolean hasMainGameLegendaries() { + return true; + } + + @Override + public List getMainGameLegendaries() { + return Arrays.stream(romEntry.arrayEntries.get("MainGameLegendaries")).boxed().collect(Collectors.toList()); + } + + @Override + public List getSpecialMusicStatics() { + return new ArrayList<>(); + } + + @Override + public void applyCorrectStaticMusic(Map specialMusicStaticChanges) { + + } + + @Override + public boolean hasStaticMusicFix() { + return false; + } + + @Override + public List getTotemPokemon() { + List totems = new ArrayList<>(); + try { + GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true); + List totemIndices = + Arrays.stream(romEntry.arrayEntries.get("TotemPokemonIndices")).boxed().collect(Collectors.toList()); + + // Static encounters + byte[] staticEncountersFile = staticGarc.files.get(1).get(0); + for (int i: totemIndices) { + int offset = i * 0x38; + TotemPokemon totem = new TotemPokemon(); + int species = FileFunctions.read2ByteInt(staticEncountersFile, offset); + Pokemon pokemon = pokes[species]; + int forme = staticEncountersFile[offset + 2]; + if (forme > pokemon.cosmeticForms && forme != 30 && forme != 31) { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(species, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + pokemon = pokes[speciesWithForme]; + } + totem.pkmn = pokemon; + totem.forme = forme; + totem.level = staticEncountersFile[offset + 3]; + int heldItem = FileFunctions.read2ByteInt(staticEncountersFile, offset + 4); + if (heldItem == 0xFFFF) { + heldItem = 0; + } + totem.heldItem = heldItem; + totem.aura = new Aura(staticEncountersFile[offset + 0x25]); + int allies = staticEncountersFile[offset + 0x27]; + for (int j = 0; j < allies; j++) { + int allyIndex = (staticEncountersFile[offset + 0x28 + 4*j] - 1) & 0xFF; + totem.allies.put(allyIndex,readStaticEncounter(staticEncountersFile, allyIndex * 0x38)); + } + totems.add(totem); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return totems; + } + + @Override + public void setTotemPokemon(List totemPokemon) { + try { + GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true); + List totemIndices = + Arrays.stream(romEntry.arrayEntries.get("TotemPokemonIndices")).boxed().collect(Collectors.toList()); + Iterator totemIter = totemPokemon.iterator(); + + // Static encounters + byte[] staticEncountersFile = staticGarc.files.get(1).get(0); + for (int i: totemIndices) { + int offset = i * 0x38; + TotemPokemon totem = totemIter.next(); + if (totem.pkmn.formeNumber > 0) { + totem.forme = totem.pkmn.formeNumber; + totem.pkmn = totem.pkmn.baseForme; + } + writeWord(staticEncountersFile, offset, totem.pkmn.number); + staticEncountersFile[offset + 2] = (byte) totem.forme; + staticEncountersFile[offset + 3] = (byte) totem.level; + if (totem.heldItem == 0) { + writeWord(staticEncountersFile, offset + 4, -1); + } else { + writeWord(staticEncountersFile, offset + 4, totem.heldItem); + } + if (totem.resetMoves) { + writeWord(staticEncountersFile, offset + 12, 0); + writeWord(staticEncountersFile, offset + 14, 0); + writeWord(staticEncountersFile, offset + 16, 0); + writeWord(staticEncountersFile, offset + 18, 0); + } + staticEncountersFile[offset + 0x25] = totem.aura.toByte(); + for (Integer allyIndex: totem.allies.keySet()) { + offset = allyIndex * 0x38; + StaticEncounter ally = totem.allies.get(allyIndex); + if (ally.pkmn.formeNumber > 0) { + ally.forme = ally.pkmn.formeNumber; + ally.pkmn = ally.pkmn.baseForme; + } + writeWord(staticEncountersFile, offset, ally.pkmn.number); + staticEncountersFile[offset + 2] = (byte) ally.forme; + staticEncountersFile[offset + 3] = (byte) ally.level; + if (ally.heldItem == 0) { + writeWord(staticEncountersFile, offset + 4, -1); + } else { + writeWord(staticEncountersFile, offset + 4, ally.heldItem); + } + if (ally.resetMoves) { + writeWord(staticEncountersFile, offset + 12, 0); + writeWord(staticEncountersFile, offset + 14, 0); + writeWord(staticEncountersFile, offset + 16, 0); + writeWord(staticEncountersFile, offset + 18, 0); + } + } + } + + writeGARC(romEntry.getFile("StaticPokemon"), staticGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + + } + + @Override + public List getStaticPokemon() { + List statics = new ArrayList<>(); + try { + GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true); + List skipIndices = + Arrays.stream(romEntry.arrayEntries.get("TotemPokemonIndices")).boxed().collect(Collectors.toList()); + skipIndices.addAll(Arrays.stream(romEntry.arrayEntries.get("AllyPokemonIndices")).boxed().collect(Collectors.toList())); + + // Gifts, start at 3 to skip the starters + byte[] giftsFile = staticGarc.files.get(0).get(0); + int numberOfGifts = giftsFile.length / 0x14; + for (int i = 3; i < numberOfGifts; i++) { + int offset = i * 0x14; + StaticEncounter se = new StaticEncounter(); + int species = FileFunctions.read2ByteInt(giftsFile, offset); + Pokemon pokemon = pokes[species]; + int forme = giftsFile[offset + 2]; + if (forme > pokemon.cosmeticForms && forme != 30 && forme != 31) { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(species, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + pokemon = pokes[speciesWithForme]; + } + se.pkmn = pokemon; + se.forme = forme; + se.level = giftsFile[offset + 3]; + se.heldItem = FileFunctions.read2ByteInt(giftsFile, offset + 8); + se.isEgg = giftsFile[offset + 10] == 1; + statics.add(se); + } + + // Static encounters + byte[] staticEncountersFile = staticGarc.files.get(1).get(0); + int numberOfStaticEncounters = staticEncountersFile.length / 0x38; + for (int i = 0; i < numberOfStaticEncounters; i++) { + if (skipIndices.contains(i)) continue; + int offset = i * 0x38; + StaticEncounter se = readStaticEncounter(staticEncountersFile, offset); + statics.add(se); + } + + // Zygarde created via Assembly on Route 16 is hardcoded + readAssemblyZygarde(statics); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + consolidateLinkedEncounters(statics); + return statics; + } + + private StaticEncounter readStaticEncounter(byte[] staticEncountersFile, int offset) { + StaticEncounter se = new StaticEncounter(); + int species = FileFunctions.read2ByteInt(staticEncountersFile, offset); + Pokemon pokemon = pokes[species]; + int forme = staticEncountersFile[offset + 2]; + if (forme > pokemon.cosmeticForms && forme != 30 && forme != 31) { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(species, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + pokemon = pokes[speciesWithForme]; + } + se.pkmn = pokemon; + se.forme = forme; + se.level = staticEncountersFile[offset + 3]; + int heldItem = FileFunctions.read2ByteInt(staticEncountersFile, offset + 4); + if (heldItem == 0xFFFF) { + heldItem = 0; + } + se.heldItem = heldItem; + return se; + } + + private void consolidateLinkedEncounters(List statics) { + List encountersToRemove = new ArrayList<>(); + for (Map.Entry entry : romEntry.linkedStaticOffsets.entrySet()) { + StaticEncounter baseEncounter = statics.get(entry.getKey()); + StaticEncounter linkedEncounter = statics.get(entry.getValue()); + baseEncounter.linkedEncounters.add(linkedEncounter); + encountersToRemove.add(linkedEncounter); + } + for (StaticEncounter encounter : encountersToRemove) { + statics.remove(encounter); + } + } + + private void readAssemblyZygarde(List statics) throws IOException { + GARCArchive scriptGarc = readGARC(romEntry.getFile("Scripts"), true); + int[] scriptLevelOffsets = romEntry.arrayEntries.get("ZygardeScriptLevelOffsets"); + int[] levels = new int[scriptLevelOffsets.length]; + byte[] zygardeAssemblyScriptBytes = scriptGarc.getFile(Gen7Constants.zygardeAssemblyScriptFile); + AMX zygardeAssemblyScript = new AMX(zygardeAssemblyScriptBytes); + for (int i = 0; i < scriptLevelOffsets.length; i++) { + levels[i] = zygardeAssemblyScript.decData[scriptLevelOffsets[i]]; + } + + int speciesOffset = find(code, Gen7Constants.zygardeAssemblySpeciesPrefix); + int formeOffset = find(code, Gen7Constants.zygardeAssemblyFormePrefix); + if (speciesOffset > 0 && formeOffset > 0) { + speciesOffset += Gen7Constants.zygardeAssemblySpeciesPrefix.length() / 2; // because it was a prefix + formeOffset += Gen7Constants.zygardeAssemblyFormePrefix.length() / 2; // because it was a prefix + int species = FileFunctions.read2ByteInt(code, speciesOffset); + + // The original code for this passed in the forme via a parameter, stored that onto + // the stack, then did a ldr to put that stack variable into r0 before finally + // storing that value in the right place. If we already modified this code, then we + // don't care about all of this; we just wrote a "mov r0, #forme" over the ldr instead. + // Thus, if the original ldr instruction is still there, assume we haven't touched it. + int forme = 0; + if (FileFunctions.readFullInt(code, formeOffset) == 0xE59D0040) { + // Since we haven't modified the code yet, this is Zygarde. For SM, use 10%, + // since you can get it fairly early. For USUM, use 50%, since it's only + // obtainable in the postgame. + forme = isSM ? 1 : 0; + } else { + // We have modified the code, so just read the constant forme number we wrote. + forme = code[formeOffset]; + } + + StaticEncounter lowLevelAssembly = new StaticEncounter(); + Pokemon pokemon = pokes[species]; + if (forme > pokemon.cosmeticForms && forme != 30 && forme != 31) { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(species, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + pokemon = pokes[speciesWithForme]; + } + lowLevelAssembly.pkmn = pokemon; + lowLevelAssembly.forme = forme; + lowLevelAssembly.level = levels[0]; + for (int i = 1; i < levels.length; i++) { + StaticEncounter higherLevelAssembly = new StaticEncounter(); + higherLevelAssembly.pkmn = pokemon; + higherLevelAssembly.forme = forme; + higherLevelAssembly.level = levels[i]; + lowLevelAssembly.linkedEncounters.add(higherLevelAssembly); + } + + statics.add(lowLevelAssembly); + } + } + + @Override + public boolean setStaticPokemon(List staticPokemon) { + try { + unlinkStaticEncounters(staticPokemon); + GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true); + List skipIndices = + Arrays.stream(romEntry.arrayEntries.get("TotemPokemonIndices")).boxed().collect(Collectors.toList()); + skipIndices.addAll(Arrays.stream(romEntry.arrayEntries.get("AllyPokemonIndices")).boxed().collect(Collectors.toList())); + Iterator staticIter = staticPokemon.iterator(); + + // Gifts, start at 3 to skip the starters + byte[] giftsFile = staticGarc.files.get(0).get(0); + int numberOfGifts = giftsFile.length / 0x14; + for (int i = 3; i < numberOfGifts; i++) { + int offset = i * 0x14; + StaticEncounter se = staticIter.next(); + writeWord(giftsFile, offset, se.pkmn.number); + giftsFile[offset + 2] = (byte) se.forme; + giftsFile[offset + 3] = (byte) se.level; + writeWord(giftsFile, offset + 8, se.heldItem); + } + + // Static encounters + byte[] staticEncountersFile = staticGarc.files.get(1).get(0); + int numberOfStaticEncounters = staticEncountersFile.length / 0x38; + for (int i = 0; i < numberOfStaticEncounters; i++) { + if (skipIndices.contains(i)) continue; + int offset = i * 0x38; + StaticEncounter se = staticIter.next(); + writeWord(staticEncountersFile, offset, se.pkmn.number); + staticEncountersFile[offset + 2] = (byte) se.forme; + staticEncountersFile[offset + 3] = (byte) se.level; + if (se.heldItem == 0) { + writeWord(staticEncountersFile, offset + 4, -1); + } else { + writeWord(staticEncountersFile, offset + 4, se.heldItem); + } + if (se.resetMoves) { + writeWord(staticEncountersFile, offset + 12, 0); + writeWord(staticEncountersFile, offset + 14, 0); + writeWord(staticEncountersFile, offset + 16, 0); + writeWord(staticEncountersFile, offset + 18, 0); + } + } + + // Zygarde created via Assembly on Route 16 is hardcoded + writeAssemblyZygarde(staticIter.next()); + + writeGARC(romEntry.getFile("StaticPokemon"), staticGarc); + return true; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void unlinkStaticEncounters(List statics) { + List offsetsToInsert = new ArrayList<>(); + for (Map.Entry entry : romEntry.linkedStaticOffsets.entrySet()) { + offsetsToInsert.add(entry.getValue()); + } + Collections.sort(offsetsToInsert); + for (Integer offsetToInsert : offsetsToInsert) { + statics.add(offsetToInsert, new StaticEncounter()); + } + for (Map.Entry entry : romEntry.linkedStaticOffsets.entrySet()) { + StaticEncounter baseEncounter = statics.get(entry.getKey()); + statics.set(entry.getValue(), baseEncounter.linkedEncounters.get(0)); + } + } + + private void writeAssemblyZygarde(StaticEncounter se) throws IOException { + int[] levels = new int[se.linkedEncounters.size() + 1]; + levels[0] = se.level; + for (int i = 0; i < se.linkedEncounters.size(); i++) { + levels[i + 1] = se.linkedEncounters.get(i).level; + } + + GARCArchive scriptGarc = readGARC(romEntry.getFile("Scripts"), true); + int[] scriptLevelOffsets = romEntry.arrayEntries.get("ZygardeScriptLevelOffsets"); + byte[] zygardeAssemblyScriptBytes = scriptGarc.getFile(Gen7Constants.zygardeAssemblyScriptFile); + AMX zygardeAssemblyScript = new AMX(zygardeAssemblyScriptBytes); + for (int i = 0; i < scriptLevelOffsets.length; i++) { + zygardeAssemblyScript.decData[scriptLevelOffsets[i]] = (byte) levels[i]; + } + scriptGarc.setFile(Gen7Constants.zygardeAssemblyScriptFile, zygardeAssemblyScript.getBytes()); + writeGARC(romEntry.getFile("Scripts"), scriptGarc); + + int speciesOffset = find(code, Gen7Constants.zygardeAssemblySpeciesPrefix); + int formeOffset = find(code, Gen7Constants.zygardeAssemblyFormePrefix); + if (speciesOffset > 0 && formeOffset > 0) { + speciesOffset += Gen7Constants.zygardeAssemblySpeciesPrefix.length() / 2; // because it was a prefix + formeOffset += Gen7Constants.zygardeAssemblyFormePrefix.length() / 2; // because it was a prefix + FileFunctions.write2ByteInt(code, speciesOffset, se.pkmn.getBaseNumber()); + + // Just write "mov r0, #forme" to where the game originally loaded the forme. + code[formeOffset] = (byte) se.forme; + code[formeOffset + 1] = 0x00; + code[formeOffset + 2] = (byte) 0xA0; + code[formeOffset + 3] = (byte) 0xE3; + } + } + + @Override + public int miscTweaksAvailable() { + int available = 0; + available |= MiscTweak.FASTEST_TEXT.getValue(); + available |= MiscTweak.BAN_LUCKY_EGG.getValue(); + available |= MiscTweak.SOS_BATTLES_FOR_ALL.getValue(); + available |= MiscTweak.RETAIN_ALT_FORMES.getValue(); + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestText(); + } else if (tweak == MiscTweak.BAN_LUCKY_EGG) { + allowedItems.banSingles(Items.luckyEgg); + nonBadItems.banSingles(Items.luckyEgg); + } else if (tweak == MiscTweak.SOS_BATTLES_FOR_ALL) { + positiveCallRates(); + } else if (tweak == MiscTweak.RETAIN_ALT_FORMES) { + try { + patchFormeReversion(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public boolean isEffectivenessUpdated() { + return false; + } + + private void applyFastestText() { + int offset = find(code, Gen7Constants.fastestTextPrefixes[0]); + if (offset > 0) { + offset += Gen7Constants.fastestTextPrefixes[0].length() / 2; // because it was a prefix + code[offset] = 0x03; + code[offset + 1] = 0x40; + code[offset + 2] = (byte) 0xA0; + code[offset + 3] = (byte) 0xE3; + } + offset = find(code, Gen7Constants.fastestTextPrefixes[1]); + if (offset > 0) { + offset += Gen7Constants.fastestTextPrefixes[1].length() / 2; // because it was a prefix + code[offset] = 0x03; + code[offset + 1] = 0x50; + code[offset + 2] = (byte) 0xA0; + code[offset + 3] = (byte) 0xE3; + } + } + + private void positiveCallRates() { + for (Pokemon pk: pokes) { + if (pk == null) continue; + if (pk.callRate <= 0) { + pk.callRate = 5; + } + } + } + + public void enableGuaranteedPokemonCatching() { + try { + byte[] battleCRO = readFile(romEntry.getFile("Battle")); + int offset = find(battleCRO, Gen7Constants.perfectOddsBranchLocator); + if (offset > 0) { + // The game checks to see if your odds are greater then or equal to 255 using the following + // code. Note that they compare to 0xFF000 instead of 0xFF; it looks like all catching code + // probabilities are shifted like this? + // cmp r7, #0xFF000 + // blt oddsLessThanOrEqualTo254 + // The below code just nops the branch out so it always acts like our odds are 255, and + // Pokemon are automatically caught no matter what. + battleCRO[offset] = 0x00; + battleCRO[offset + 1] = 0x00; + battleCRO[offset + 2] = 0x00; + battleCRO[offset + 3] = 0x00; + writeFile(romEntry.getFile("Battle"), battleCRO); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getTMMoves() { + String tmDataPrefix = Gen7Constants.getTmDataPrefix(romEntry.romType); + int offset = find(code, tmDataPrefix); + if (offset != 0) { + offset += tmDataPrefix.length() / 2; // because it was a prefix + List tms = new ArrayList<>(); + for (int i = 0; i < Gen7Constants.tmCount; i++) { + tms.add(readWord(code, offset + i * 2)); + } + return tms; + } else { + return null; + } + } + + @Override + public List getHMMoves() { + // Gen 7 does not have any HMs + return new ArrayList<>(); + } + + @Override + public void setTMMoves(List moveIndexes) { + String tmDataPrefix = Gen7Constants.getTmDataPrefix(romEntry.romType); + int offset = find(code, tmDataPrefix); + if (offset > 0) { + offset += tmDataPrefix.length() / 2; // because it was a prefix + for (int i = 0; i < Gen7Constants.tmCount; i++) { + writeWord(code, offset + i * 2, moveIndexes.get(i)); + } + + // Update TM item descriptions + List itemDescriptions = getStrings(false, romEntry.getInt("ItemDescriptionsTextOffset")); + List moveDescriptions = getStrings(false, romEntry.getInt("MoveDescriptionsTextOffset")); + // TM01 is item 328 and so on + for (int i = 0; i < Gen7Constants.tmBlockOneCount; i++) { + itemDescriptions.set(i + Gen7Constants.tmBlockOneOffset, moveDescriptions.get(moveIndexes.get(i))); + } + // TM93-95 are 618-620 + for (int i = 0; i < Gen7Constants.tmBlockTwoCount; i++) { + itemDescriptions.set(i + Gen7Constants.tmBlockTwoOffset, + moveDescriptions.get(moveIndexes.get(i + Gen7Constants.tmBlockOneCount))); + } + // TM96-100 are 690 and so on + for (int i = 0; i < Gen7Constants.tmBlockThreeCount; i++) { + itemDescriptions.set(i + Gen7Constants.tmBlockThreeOffset, + moveDescriptions.get(moveIndexes.get(i + Gen7Constants.tmBlockOneCount + Gen7Constants.tmBlockTwoCount))); + } + // Save the new item descriptions + setStrings(false, romEntry.getInt("ItemDescriptionsTextOffset"), itemDescriptions); + // Palettes + String palettePrefix = Gen7Constants.itemPalettesPrefix; + int offsPals = find(code, palettePrefix); + if (offsPals > 0) { + offsPals += Gen7Constants.itemPalettesPrefix.length() / 2; // because it was a prefix + // Write pals + for (int i = 0; i < Gen7Constants.tmBlockOneCount; i++) { + int itmNum = Gen7Constants.tmBlockOneOffset + i; + Move m = this.moves[moveIndexes.get(i)]; + int pal = this.typeTMPaletteNumber(m.type, true); + writeWord(code, offsPals + itmNum * 4, pal); + } + for (int i = 0; i < (Gen7Constants.tmBlockTwoCount); i++) { + int itmNum = Gen7Constants.tmBlockTwoOffset + i; + Move m = this.moves[moveIndexes.get(i + Gen7Constants.tmBlockOneCount)]; + int pal = this.typeTMPaletteNumber(m.type, true); + writeWord(code, offsPals + itmNum * 4, pal); + } + for (int i = 0; i < (Gen7Constants.tmBlockThreeCount); i++) { + int itmNum = Gen7Constants.tmBlockThreeOffset + i; + Move m = this.moves[moveIndexes.get(i + Gen7Constants.tmBlockOneCount + Gen7Constants.tmBlockTwoCount)]; + int pal = this.typeTMPaletteNumber(m.type, true); + writeWord(code, offsPals + itmNum * 4, pal); + } + } + } + } + + private int find(byte[] data, String hexString) { + if (hexString.length() % 2 != 0) { + return -3; // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + List found = RomFunctions.search(data, searchFor); + if (found.size() == 0) { + return -1; // not found + } else if (found.size() > 1) { + return -2; // not unique + } else { + return found.get(0); + } + } + + @Override + public int getTMCount() { + return Gen7Constants.tmCount; + } + + @Override + public int getHMCount() { + // Gen 7 does not have any HMs + return 0; + } + + @Override + public Map getTMHMCompatibility() { + Map compat = new TreeMap<>(); + int pokemonCount = Gen7Constants.getPokemonCount(romEntry.romType); + int formeCount = Gen7Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= pokemonCount + formeCount; i++) { + byte[] data; + data = pokeGarc.files.get(i).get(0); + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen7Constants.tmCount + 1]; + for (int j = 0; j < 13; j++) { + readByteIntoFlags(data, flags, j * 8 + 1, Gen7Constants.bsTMHMCompatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map compatData) { + for (Map.Entry compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + byte[] data = pokeGarc.files.get(pkmn.number).get(0); + for (int j = 0; j < 13; j++) { + data[Gen7Constants.bsTMHMCompatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public boolean hasMoveTutors() { + return romEntry.romType == Gen7Constants.Type_USUM; + } + + @Override + public List getMoveTutorMoves() { + List mtMoves = new ArrayList<>(); + + int mtOffset = find(code, Gen7Constants.tutorsPrefix); + if (mtOffset > 0) { + mtOffset += Gen7Constants.tutorsPrefix.length() / 2; + int val = 0; + while (val != 0xFFFF) { + val = FileFunctions.read2ByteInt(code, mtOffset); + mtOffset += 2; + if (val == 0xFFFF) continue; + mtMoves.add(val); + } + } + + return mtMoves; + } + + @Override + public void setMoveTutorMoves(List moves) { + int mtOffset = find(code, Gen7Constants.tutorsPrefix); + if (mtOffset > 0) { + mtOffset += Gen7Constants.tutorsPrefix.length() / 2; + for (int move: moves) { + FileFunctions.write2ByteInt(code,mtOffset, move); + mtOffset += 2; + } + } + + try { + byte[] tutorCRO = readFile(romEntry.getFile("ShopsAndTutors")); + for (int i = 0; i < moves.size(); i++) { + int offset = Gen7Constants.tutorsOffset + i * 4; + FileFunctions.write2ByteInt(tutorCRO, offset, moves.get(i)); + } + writeFile(romEntry.getFile("ShopsAndTutors"), tutorCRO); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public Map getMoveTutorCompatibility() { + Map compat = new TreeMap<>(); + int pokemonCount = Gen7Constants.getPokemonCount(romEntry.romType); + int formeCount = Gen7Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= pokemonCount + formeCount; i++) { + byte[] data; + data = pokeGarc.files.get(i).get(0); + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen7Constants.tutorMoveCount + 1]; + for (int j = 0; j < 10; j++) { + readByteIntoFlags(data, flags, j * 8 + 1, Gen7Constants.bsMTCompatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setMoveTutorCompatibility(Map compatData) { + if (!hasMoveTutors()) return; + int pokemonCount = Gen7Constants.getPokemonCount(romEntry.romType); + int formeCount = Gen7Constants.getFormeCount(romEntry.romType); + for (int i = 1; i <= pokemonCount + formeCount; i++) { + byte[] data; + data = pokeGarc.files.get(i).get(0); + Pokemon pkmn = pokes[i]; + boolean[] flags = compatData.get(pkmn); + for (int j = 0; j < 10; j++) { + data[Gen7Constants.bsMTCompatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public String getROMName() { + return "Pokemon " + romEntry.name; + } + + @Override + public String getROMCode() { + return romEntry.romCode; + } + + @Override + public String getSupportLevel() { + return "Complete"; + } + + @Override + public boolean hasTimeBasedEncounters() { + return true; + } + + @Override + public List getMovesBannedFromLevelup() { + return Gen7Constants.bannedMoves; + } + + @Override + public boolean hasWildAltFormes() { + return true; + } + + @Override + public void removeImpossibleEvolutions(Settings settings) { + boolean changeMoveEvos = !(settings.getMovesetsMod() == Settings.MovesetsMod.UNCHANGED); + + Map> movesets = this.getMovesLearnt(); + Set extraEvolutions = new HashSet<>(); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + extraEvolutions.clear(); + for (Evolution evo : pkmn.evolutionsFrom) { + if (changeMoveEvos && evo.type == EvolutionType.LEVEL_WITH_MOVE) { + // read move + int move = evo.extraInfo; + int levelLearntAt = 1; + for (MoveLearnt ml : movesets.get(evo.from.number)) { + if (ml.move == move) { + levelLearntAt = ml.level; + break; + } + } + if (levelLearntAt == 1) { + // override for piloswine + levelLearntAt = 45; + } + // change to pure level evo + evo.type = EvolutionType.LEVEL; + evo.extraInfo = levelLearntAt; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // Pure Trade + if (evo.type == EvolutionType.TRADE) { + // Replace w/ level 37 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + addEvoUpdateLevel(impossibleEvolutionUpdates, evo); + } + // Trade w/ Item + if (evo.type == EvolutionType.TRADE_ITEM) { + // Get the current item & evolution + int item = evo.extraInfo; + if (evo.from.number == Species.slowpoke) { + // Slowpoke is awkward - he already has a level evo + // So we can't do Level up w/ Held Item for him + // Put Water Stone instead + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.waterStone; + addEvoUpdateStone(impossibleEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + addEvoUpdateHeldItem(impossibleEvolutionUpdates, evo, itemNames.get(item)); + // Replace, for this entry, w/ + // Level up w/ Held Item at Day + evo.type = EvolutionType.LEVEL_ITEM_DAY; + // now add an extra evo for + // Level up w/ Held Item at Night + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_NIGHT, item); + extraEntry.forme = evo.forme; + extraEvolutions.add(extraEntry); + } + } + if (evo.type == EvolutionType.TRADE_SPECIAL) { + // This is the karrablast <-> shelmet trade + // Replace it with Level up w/ Other Species in Party + // (22) + // Based on what species we're currently dealing with + evo.type = EvolutionType.LEVEL_WITH_OTHER; + evo.extraInfo = (evo.from.number == Species.karrablast ? Species.shelmet : Species.karrablast); + addEvoUpdateParty(impossibleEvolutionUpdates, evo, pokes[evo.extraInfo].fullName()); + } + // TBD: Pancham, Sliggoo? Sylveon? + } + + pkmn.evolutionsFrom.addAll(extraEvolutions); + for (Evolution ev : extraEvolutions) { + ev.to.evolutionsTo.add(ev); + } + } + } + + } + + @Override + public void makeEvolutionsEasier(Settings settings) { + boolean wildsRandomized = !settings.getWildPokemonMod().equals(Settings.WildPokemonMod.UNCHANGED); + + // Reduce the amount of happiness required to evolve. + int offset = find(code, Gen7Constants.friendshipValueForEvoLocator); + if (offset > 0) { + // Amount of required happiness for HAPPINESS evolutions. + if (code[offset] == (byte)220) { + code[offset] = (byte)160; + } + // Amount of required happiness for HAPPINESS_DAY evolutions. + if (code[offset + 12] == (byte)220) { + code[offset + 12] = (byte)160; + } + // Amount of required happiness for HAPPINESS_NIGHT evolutions. + if (code[offset + 36] == (byte)220) { + code[offset + 36] = (byte)160; + } + } + + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + Evolution extraEntry = null; + for (Evolution evo : pkmn.evolutionsFrom) { + if (wildsRandomized) { + if (evo.type == EvolutionType.LEVEL_WITH_OTHER) { + // Replace w/ level 35 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 35; + addEvoUpdateCondensed(easierEvolutionUpdates, evo, false); + } + } + if (romEntry.romType == Gen7Constants.Type_SM) { + if (evo.type == EvolutionType.LEVEL_SNOWY) { + extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL, 35); + extraEntry.forme = evo.forme; + addEvoUpdateCondensed(easierEvolutionUpdates, extraEntry, true); + } else if (evo.type == EvolutionType.LEVEL_ELECTRIFIED_AREA) { + extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL, 35); + extraEntry.forme = evo.forme; + addEvoUpdateCondensed(easierEvolutionUpdates, extraEntry, true); + } + } + } + if (extraEntry != null) { + pkmn.evolutionsFrom.add(extraEntry); + extraEntry.to.evolutionsTo.add(extraEntry); + } + } + } + + } + + @Override + public void removeTimeBasedEvolutions() { + Set extraEvolutions = new HashSet<>(); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + extraEvolutions.clear(); + for (Evolution evo : pkmn.evolutionsFrom) { + if (evo.type == EvolutionType.HAPPINESS_DAY) { + if (evo.from.number == Species.eevee) { + // We can't set Eevee to evolve into Espeon with happiness at night because that's how + // Umbreon works in the original game. Instead, make Eevee: == sun stone => Espeon + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.sunStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + // Add an extra evo for Happiness at Night + addEvoUpdateHappiness(timeBasedEvolutionUpdates, evo); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.HAPPINESS_NIGHT, 0); + extraEntry.forme = evo.forme; + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.HAPPINESS_NIGHT) { + if (evo.from.number == Species.eevee) { + // We can't set Eevee to evolve into Umbreon with happiness at day because that's how + // Espeon works in the original game. Instead, make Eevee: == moon stone => Umbreon + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.moonStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + // Add an extra evo for Happiness at Day + addEvoUpdateHappiness(timeBasedEvolutionUpdates, evo); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.HAPPINESS_DAY, 0); + extraEntry.forme = evo.forme; + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.LEVEL_ITEM_DAY) { + int item = evo.extraInfo; + // Make sure we don't already have an evo for the same item at night (e.g., when using Change Impossible Evos) + if (evo.from.evolutionsFrom.stream().noneMatch(e -> e.type == EvolutionType.LEVEL_ITEM_NIGHT && e.extraInfo == item)) { + // Add an extra evo for Level w/ Item During Night + addEvoUpdateHeldItem(timeBasedEvolutionUpdates, evo, itemNames.get(item)); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_NIGHT, item); + extraEntry.forme = evo.forme; + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.LEVEL_ITEM_NIGHT) { + int item = evo.extraInfo; + // Make sure we don't already have an evo for the same item at day (e.g., when using Change Impossible Evos) + if (evo.from.evolutionsFrom.stream().noneMatch(e -> e.type == EvolutionType.LEVEL_ITEM_DAY && e.extraInfo == item)) { + // Add an extra evo for Level w/ Item During Day + addEvoUpdateHeldItem(timeBasedEvolutionUpdates, evo, itemNames.get(item)); + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_DAY, item); + extraEntry.forme = evo.forme; + extraEvolutions.add(extraEntry); + } + } else if (evo.type == EvolutionType.LEVEL_DAY) { + if (evo.from.number == Species.rockruff) { + // We can't set Rockruff to evolve into Lycanroc-Midday with level at night because that's how + // Lycanroc-Midnight works in the original game. Instead, make Rockruff: == sun stone => Lycanroc-Midday + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.sunStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + addEvoUpdateLevel(timeBasedEvolutionUpdates, evo); + evo.type = EvolutionType.LEVEL; + } + } else if (evo.type == EvolutionType.LEVEL_NIGHT) { + if (evo.from.number == Species.rockruff) { + // We can't set Rockruff to evolve into Lycanroc-Midnight with level at night because that's how + // Lycanroc-Midday works in the original game. Instead, make Rockruff: == moon stone => Lycanroc-Midnight + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.moonStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } else { + addEvoUpdateLevel(timeBasedEvolutionUpdates, evo); + evo.type = EvolutionType.LEVEL; + } + } else if (evo.type == EvolutionType.LEVEL_DUSK) { + // This is the Rockruff => Lycanroc-Dusk evolution. We can't set it to evolve with level at other + // times because the other Lycanroc formes work like that in the original game. Instead, make + // Rockruff: == dusk stone => Lycanroc-Dusk + evo.type = EvolutionType.STONE; + evo.extraInfo = Items.duskStone; + addEvoUpdateStone(timeBasedEvolutionUpdates, evo, itemNames.get(evo.extraInfo)); + } + } + pkmn.evolutionsFrom.addAll(extraEvolutions); + for (Evolution ev : extraEvolutions) { + ev.to.evolutionsTo.add(ev); + } + } + } + } + + @Override + public boolean altFormesCanHaveDifferentEvolutions() { + return true; + } + + @Override + public boolean hasShopRandomization() { + return true; + } + + @Override + public boolean canChangeTrainerText() { + return true; + } + + @Override + public List getTrainerNames() { + List tnames = getStrings(false, romEntry.getInt("TrainerNamesTextOffset")); + tnames.remove(0); // blank one + + return tnames; + } + + @Override + public int maxTrainerNameLength() { + return 10; + } + + @Override + public void setTrainerNames(List trainerNames) { + List tnames = getStrings(false, romEntry.getInt("TrainerNamesTextOffset")); + List newTNames = new ArrayList<>(trainerNames); + newTNames.add(0, tnames.get(0)); // the 0-entry, preserve it + setStrings(false, romEntry.getInt("TrainerNamesTextOffset"), newTNames); + try { + writeStringsForAllLanguages(newTNames, romEntry.getInt("TrainerNamesTextOffset")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void writeStringsForAllLanguages(List strings, int index) throws IOException { + List nonEnglishLanguages = Arrays.asList("JaKana", "JaKanji", "Fr", "It", "De", "Es", "Ko", "ZhSimplified", "ZhTraditional"); + for (String nonEnglishLanguage : nonEnglishLanguages) { + String key = "TextStrings" + nonEnglishLanguage; + GARCArchive stringsGarcForLanguage = readGARC(romEntry.getFile(key),true); + setStrings(stringsGarcForLanguage, index, strings); + writeGARC(romEntry.getFile(key), stringsGarcForLanguage); + } + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.MAX_LENGTH; + } + + @Override + public List getTCNameLengthsByTrainer() { + return new ArrayList<>(); + } + + @Override + public List getTrainerClassNames() { + return getStrings(false, romEntry.getInt("TrainerClassesTextOffset")); + } + + @Override + public void setTrainerClassNames(List trainerClassNames) { + setStrings(false, romEntry.getInt("TrainerClassesTextOffset"), trainerClassNames); + try { + writeStringsForAllLanguages(trainerClassNames, romEntry.getInt("TrainerClassesTextOffset")); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public int maxTrainerClassNameLength() { + return 15; + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return false; + } + + @Override + public List getDoublesTrainerClasses() { + int[] doublesClasses = romEntry.arrayEntries.get("DoublesTrainerClasses"); + List doubles = new ArrayList<>(); + for (int tClass : doublesClasses) { + doubles.add(tClass); + } + return doubles; + } + + @Override + public String getDefaultExtension() { + return "cxi"; + } + + @Override + public int abilitiesPerPokemon() { + return 3; + } + + @Override + public int highestAbilityIndex() { + return Gen7Constants.getHighestAbilityIndex(romEntry.romType); + } + + @Override + public int internalStringLength(String string) { + return string.length(); + } + + @Override + public void randomizeIntroPokemon() { + // For now, do nothing. + } + + @Override + public ItemList getAllowedItems() { + return allowedItems; + } + + @Override + public ItemList getNonBadItems() { + return nonBadItems; + } + + @Override + public List getUniqueNoSellItems() { + return new ArrayList<>(); + } + + @Override + public List getRegularShopItems() { + return Gen7Constants.getRegularShopItems(romEntry.romType); + } + + @Override + public List getOPShopItems() { + return Gen7Constants.opShopItems; + } + + @Override + public String[] getItemNames() { + return itemNames.toArray(new String[0]); + } + + @Override + public String abilityName(int number) { + return abilityNames.get(number); + } + + @Override + public Map> getAbilityVariations() { + return Gen7Constants.abilityVariations; + } + + @Override + public List getUselessAbilities() { + return new ArrayList<>(Gen7Constants.uselessAbilities); + } + + @Override + public int getAbilityForTrainerPokemon(TrainerPokemon tp) { + // Before randomizing Trainer Pokemon, one possible value for abilitySlot is 0, + // which represents "Either Ability 1 or 2". During randomization, we make sure to + // to set abilitySlot to some non-zero value, but if you call this method without + // randomization, then you'll hit this case. + if (tp.abilitySlot < 1 || tp.abilitySlot > 3) { + return 0; + } + + List abilityList = Arrays.asList(tp.pokemon.ability1, tp.pokemon.ability2, tp.pokemon.ability3); + return abilityList.get(tp.abilitySlot - 1); + } + + @Override + public boolean hasMegaEvolutions() { + return true; + } + + private int tmFromIndex(int index) { + + if (index >= Gen7Constants.tmBlockOneOffset + && index < Gen7Constants.tmBlockOneOffset + Gen7Constants.tmBlockOneCount) { + return index - (Gen7Constants.tmBlockOneOffset - 1); + } else if (index >= Gen7Constants.tmBlockTwoOffset + && index < Gen7Constants.tmBlockTwoOffset + Gen7Constants.tmBlockTwoCount) { + return (index + Gen7Constants.tmBlockOneCount) - (Gen7Constants.tmBlockTwoOffset - 1); + } else { + return (index + Gen7Constants.tmBlockOneCount + Gen7Constants.tmBlockTwoCount) - (Gen7Constants.tmBlockThreeOffset - 1); + } + } + + private int indexFromTM(int tm) { + if (tm >= 1 && tm <= Gen7Constants.tmBlockOneCount) { + return tm + (Gen7Constants.tmBlockOneOffset - 1); + } else if (tm > Gen7Constants.tmBlockOneCount && tm <= Gen7Constants.tmBlockOneCount + Gen7Constants.tmBlockTwoCount) { + return tm + (Gen7Constants.tmBlockTwoOffset - 1 - Gen7Constants.tmBlockOneCount); + } else { + return tm + (Gen7Constants.tmBlockThreeOffset - 1 - (Gen7Constants.tmBlockOneCount + Gen7Constants.tmBlockTwoCount)); + } + } + + @Override + public List getCurrentFieldTMs() { + List fieldItems = this.getFieldItems(); + List fieldTMs = new ArrayList<>(); + + ItemList allowedItems = Gen7Constants.getAllowedItems(romEntry.romType); + for (int item : fieldItems) { + if (allowedItems.isTM(item)) { + fieldTMs.add(tmFromIndex(item)); + } + } + + return fieldTMs.stream().distinct().collect(Collectors.toList()); + } + + @Override + public void setFieldTMs(List fieldTMs) { + List fieldItems = this.getFieldItems(); + int fiLength = fieldItems.size(); + Iterator iterTMs = fieldTMs.iterator(); + Map tmMap = new HashMap<>(); + + ItemList allowedItems = Gen7Constants.getAllowedItems(romEntry.romType); + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (allowedItems.isTM(oldItem)) { + if (tmMap.get(oldItem) != null) { + fieldItems.set(i,tmMap.get(oldItem)); + continue; + } + int newItem = indexFromTM(iterTMs.next()); + fieldItems.set(i, newItem); + tmMap.put(oldItem,newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List getRegularFieldItems() { + List fieldItems = this.getFieldItems(); + List fieldRegItems = new ArrayList<>(); + + ItemList allowedItems = Gen7Constants.getAllowedItems(romEntry.romType); + for (int item : fieldItems) { + if (allowedItems.isAllowed(item) && !(allowedItems.isTM(item))) { + fieldRegItems.add(item); + } + } + + return fieldRegItems; + } + + @Override + public void setRegularFieldItems(List items) { + List fieldItems = this.getFieldItems(); + int fiLength = fieldItems.size(); + Iterator iterNewItems = items.iterator(); + + ItemList allowedItems = Gen7Constants.getAllowedItems(romEntry.romType); + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (!(allowedItems.isTM(oldItem)) && allowedItems.isAllowed(oldItem)) { + int newItem = iterNewItems.next(); + fieldItems.set(i, newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List getRequiredFieldTMs() { + return Gen7Constants.getRequiredFieldTMs(romEntry.romType); + } + + public List getFieldItems() { + List fieldItems = new ArrayList<>(); + int numberOfAreas = encounterGarc.files.size() / 11; + for (int i = 0; i < numberOfAreas; i++) { + byte[][] environmentData = Mini.UnpackMini(encounterGarc.getFile(i * 11),"ED"); + if (environmentData == null) continue; + + byte[][] itemDataFull = Mini.UnpackMini(environmentData[10],"EI"); + + byte[][] berryPileDataFull = Mini.UnpackMini(environmentData[11],"EB"); + + // Field/hidden items + for (byte[] itemData: itemDataFull) { + if (itemData.length > 0) { + int itemCount = itemData[0]; + + for (int j = 0; j < itemCount; j++) { + fieldItems.add(FileFunctions.read2ByteInt(itemData,(j * 64) + 52)); + } + } + } + + // Berry piles + for (byte[] berryPileData: berryPileDataFull) { + if (berryPileData.length > 0) { + int pileCount = berryPileData[0]; + for (int j = 0; j < pileCount; j++) { + for (int k = 0; k < 7; k++) { + fieldItems.add(FileFunctions.read2ByteInt(berryPileData,4 + j*68 + 54 + k*2)); + } + } + } + } + } + return fieldItems; + } + + public void setFieldItems(List items) { + try { + int numberOfAreas = encounterGarc.files.size() / 11; + Iterator iterItems = items.iterator(); + for (int i = 0; i < numberOfAreas; i++) { + byte[][] environmentData = Mini.UnpackMini(encounterGarc.getFile(i * 11),"ED"); + if (environmentData == null) continue; + + byte[][] itemDataFull = Mini.UnpackMini(environmentData[10],"EI"); + + byte[][] berryPileDataFull = Mini.UnpackMini(environmentData[11],"EB"); + + // Field/hidden items + for (byte[] itemData: itemDataFull) { + if (itemData.length > 0) { + int itemCount = itemData[0]; + + for (int j = 0; j < itemCount; j++) { + FileFunctions.write2ByteInt(itemData,(j * 64) + 52,iterItems.next()); + } + } + } + + byte[] itemDataPacked = Mini.PackMini(itemDataFull,"EI"); + environmentData[10] = itemDataPacked; + + // Berry piles + for (byte[] berryPileData: berryPileDataFull) { + if (berryPileData.length > 0) { + int pileCount = berryPileData[0]; + + for (int j = 0; j < pileCount; j++) { + for (int k = 0; k < 7; k++) { + FileFunctions.write2ByteInt(berryPileData,4 + j*68 + 54 + k*2,iterItems.next()); + } + } + } + } + + byte[] berryPileDataPacked = Mini.PackMini(berryPileDataFull,"EB"); + environmentData[11] = berryPileDataPacked; + + encounterGarc.setFile(i * 11, Mini.PackMini(environmentData,"ED")); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public List getIngameTrades() { + List ingameTrades = new ArrayList<>(); + try { + GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true); + List tradeStrings = getStrings(true, romEntry.getInt("IngameTradesTextOffset")); + byte[] tradesFile = staticGarc.files.get(4).get(0); + int numberOfIngameTrades = tradesFile.length / 0x34; + for (int i = 0; i < numberOfIngameTrades; i++) { + int offset = i * 0x34; + IngameTrade trade = new IngameTrade(); + int givenSpecies = FileFunctions.read2ByteInt(tradesFile, offset); + int requestedSpecies = FileFunctions.read2ByteInt(tradesFile, offset + 0x2C); + Pokemon givenPokemon = pokes[givenSpecies]; + Pokemon requestedPokemon = pokes[requestedSpecies]; + int forme = tradesFile[offset + 4]; + if (forme > givenPokemon.cosmeticForms && forme != 30 && forme != 31) { + int speciesWithForme = absolutePokeNumByBaseForme + .getOrDefault(givenSpecies, dummyAbsolutePokeNums) + .getOrDefault(forme, 0); + givenPokemon = pokes[speciesWithForme]; + } + trade.givenPokemon = givenPokemon; + trade.requestedPokemon = requestedPokemon; + trade.nickname = tradeStrings.get(FileFunctions.read2ByteInt(tradesFile, offset + 2)); + trade.otName = tradeStrings.get(FileFunctions.read2ByteInt(tradesFile, offset + 0x18)); + trade.otId = FileFunctions.readFullInt(tradesFile, offset + 0x10); + trade.ivs = new int[6]; + for (int iv = 0; iv < 6; iv++) { + trade.ivs[iv] = tradesFile[offset + 6 + iv]; + } + trade.item = FileFunctions.read2ByteInt(tradesFile, offset + 0x14); + if (trade.item < 0) { + trade.item = 0; + } + ingameTrades.add(trade); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return ingameTrades; + } + + @Override + public void setIngameTrades(List trades) { + try { + List oldTrades = this.getIngameTrades(); + GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true); + List tradeStrings = getStrings(true, romEntry.getInt("IngameTradesTextOffset")); + Map> hardcodedTradeTextOffsets = Gen7Constants.getHardcodedTradeTextOffsets(romEntry.romType); + byte[] tradesFile = staticGarc.files.get(4).get(0); + int numberOfIngameTrades = tradesFile.length / 0x34; + for (int i = 0; i < numberOfIngameTrades; i++) { + IngameTrade trade = trades.get(i); + int offset = i * 0x34; + Pokemon givenPokemon = trade.givenPokemon; + int forme = 0; + if (givenPokemon.formeNumber > 0) { + forme = givenPokemon.formeNumber; + givenPokemon = givenPokemon.baseForme; + } + FileFunctions.write2ByteInt(tradesFile, offset, givenPokemon.number); + tradesFile[offset + 4] = (byte) forme; + FileFunctions.write2ByteInt(tradesFile, offset + 0x2C, trade.requestedPokemon.number); + tradeStrings.set(FileFunctions.read2ByteInt(tradesFile, offset + 2), trade.nickname); + tradeStrings.set(FileFunctions.read2ByteInt(tradesFile, offset + 0x18), trade.otName); + FileFunctions.writeFullInt(tradesFile, offset + 0x10, trade.otId); + for (int iv = 0; iv < 6; iv++) { + tradesFile[offset + 6 + iv] = (byte) trade.ivs[iv]; + } + FileFunctions.write2ByteInt(tradesFile, offset + 0x14, trade.item); + + List hardcodedTextOffsetsForThisTrade = hardcodedTradeTextOffsets.get(i); + if (hardcodedTextOffsetsForThisTrade != null) { + updateHardcodedTradeText(oldTrades.get(i), trade, tradeStrings, hardcodedTextOffsetsForThisTrade); + } + } + writeGARC(romEntry.getFile("StaticPokemon"), staticGarc); + setStrings(true, romEntry.getInt("IngameTradesTextOffset"), tradeStrings); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + // NOTE: This method is kind of stupid, in that it doesn't try to reflow the text to better fit; it just + // blindly replaces the Pokemon's name. However, it seems to work well enough for what we need. + private void updateHardcodedTradeText(IngameTrade oldTrade, IngameTrade newTrade, List tradeStrings, List hardcodedTextOffsets) { + for (int offset : hardcodedTextOffsets) { + String hardcodedText = tradeStrings.get(offset); + String oldRequestedName = oldTrade.requestedPokemon.name; + String oldGivenName = oldTrade.givenPokemon.name; + String newRequestedName = newTrade.requestedPokemon.name; + String newGivenName = newTrade.givenPokemon.name; + hardcodedText = hardcodedText.replace(oldRequestedName, newRequestedName); + hardcodedText = hardcodedText.replace(oldGivenName, newGivenName); + tradeStrings.set(offset, hardcodedText); + } + } + + @Override + public boolean hasDVs() { + return false; + } + + @Override + public int generationOfPokemon() { + return 7; + } + + @Override + public void removeEvosForPokemonPool() { + // slightly more complicated than gen2/3 + // we have to update a "baby table" too + List pokemonIncluded = this.mainPokemonListInclFormes; + Set keepEvos = new HashSet<>(); + for (Pokemon pk : pokes) { + if (pk != null) { + keepEvos.clear(); + for (Evolution evol : pk.evolutionsFrom) { + if (pokemonIncluded.contains(evol.from) && pokemonIncluded.contains(evol.to)) { + keepEvos.add(evol); + } else { + evol.to.evolutionsTo.remove(evol); + } + } + pk.evolutionsFrom.retainAll(keepEvos); + } + } + + try { + // baby pokemon + GARCArchive babyGarc = readGARC(romEntry.getFile("BabyPokemon"), true); + int pokemonCount = Gen7Constants.getPokemonCount(romEntry.romType); + byte[] masterFile = babyGarc.getFile(pokemonCount + 1); + for (int i = 1; i <= pokemonCount; i++) { + byte[] babyFile = babyGarc.getFile(i); + Pokemon baby = pokes[i]; + while (baby.evolutionsTo.size() > 0) { + // Grab the first "to evolution" even if there are multiple + baby = baby.evolutionsTo.get(0).from; + } + writeWord(babyFile, 0, baby.number); + writeWord(masterFile, i * 2, baby.number); + babyGarc.setFile(i, babyFile); + } + babyGarc.setFile(pokemonCount + 1, masterFile); + writeGARC(romEntry.getFile("BabyPokemon"), babyGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public boolean supportsFourStartingMoves() { + return true; + } + + @Override + public List getFieldMoves() { + // Gen 7 does not have field moves + return new ArrayList<>(); + } + + @Override + public List getEarlyRequiredHMMoves() { + // Gen 7 does not have any HMs + return new ArrayList<>(); + } + + @Override + public Map getShopItems() { + int[] tmShops = romEntry.arrayEntries.get("TMShops"); + int[] regularShops = romEntry.arrayEntries.get("RegularShops"); + int[] shopItemSizes = romEntry.arrayEntries.get("ShopItemSizes"); + int shopCount = romEntry.getInt("ShopCount"); + Map shopItemsMap = new TreeMap<>(); + try { + byte[] shopsCRO = readFile(romEntry.getFile("ShopsAndTutors")); + int offset = Gen7Constants.getShopItemsOffset(romEntry.romType); + for (int i = 0; i < shopCount; i++) { + boolean badShop = false; + for (int tmShop : tmShops) { + if (i == tmShop) { + badShop = true; + offset += (shopItemSizes[i] * 2); + break; + } + } + for (int regularShop : regularShops) { + if (badShop) break; + if (i == regularShop) { + badShop = true; + offset += (shopItemSizes[i] * 2); + break; + } + } + if (!badShop) { + List items = new ArrayList<>(); + for (int j = 0; j < shopItemSizes[i]; j++) { + items.add(FileFunctions.read2ByteInt(shopsCRO, offset)); + offset += 2; + } + Shop shop = new Shop(); + shop.items = items; + shop.name = shopNames.get(i); + shop.isMainGame = Gen7Constants.getMainGameShops(romEntry.romType).contains(i); + shopItemsMap.put(i, shop); + } + } + return shopItemsMap; + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public void setShopItems(Map shopItems) { + int[] tmShops = romEntry.arrayEntries.get("TMShops"); + int[] regularShops = romEntry.arrayEntries.get("RegularShops"); + int[] shopItemSizes = romEntry.arrayEntries.get("ShopItemSizes"); + int shopCount = romEntry.getInt("ShopCount"); + try { + byte[] shopsCRO = readFile(romEntry.getFile("ShopsAndTutors")); + int offset = Gen7Constants.getShopItemsOffset(romEntry.romType); + for (int i = 0; i < shopCount; i++) { + boolean badShop = false; + for (int tmShop : tmShops) { + if (i == tmShop) { + badShop = true; + offset += (shopItemSizes[i] * 2); + break; + } + } + for (int regularShop : regularShops) { + if (badShop) break; + if (i == regularShop) { + badShop = true; + offset += (shopItemSizes[i] * 2); + break; + } + } + if (!badShop) { + List shopContents = shopItems.get(i).items; + Iterator iterItems = shopContents.iterator(); + for (int j = 0; j < shopItemSizes[i]; j++) { + Integer item = iterItems.next(); + FileFunctions.write2ByteInt(shopsCRO, offset, item); + offset += 2; + } + } + } + writeFile(romEntry.getFile("ShopsAndTutors"), shopsCRO); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public void setShopPrices() { + try { + GARCArchive itemPriceGarc = this.readGARC(romEntry.getFile("ItemData"),true); + for (int i = 1; i < itemPriceGarc.files.size(); i++) { + writeWord(itemPriceGarc.files.get(i).get(0),0, Gen7Constants.balancedItemPrices.get(i)); + } + writeGARC(romEntry.getFile("ItemData"),itemPriceGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + @Override + public List getPickupItems() { + List pickupItems = new ArrayList<>(); + try { + GARCArchive pickupGarc = this.readGARC(romEntry.getFile("PickupData"), false); + byte[] pickupData = pickupGarc.getFile(0); + int numberOfPickupItems = FileFunctions.readFullInt(pickupData, 0) - 1; // GameFreak why??? + for (int i = 0; i < numberOfPickupItems; i++) { + int offset = 4 + (i * 0xC); + int item = FileFunctions.read2ByteInt(pickupData, offset); + PickupItem pickupItem = new PickupItem(item); + for (int levelRange = 0; levelRange < 10; levelRange++) { + pickupItem.probabilities[levelRange] = pickupData[offset + levelRange + 2]; + } + pickupItems.add(pickupItem); + } + } catch (IOException e) { + throw new RandomizerIOException(e); + } + return pickupItems; + } + + @Override + public void setPickupItems(List pickupItems) { + try { + GARCArchive pickupGarc = this.readGARC(romEntry.getFile("PickupData"), false); + byte[] pickupData = pickupGarc.getFile(0); + for (int i = 0; i < pickupItems.size(); i++) { + int offset = 4 + (i * 0xC); + int item = pickupItems.get(i).item; + FileFunctions.write2ByteInt(pickupData, offset, item); + } + this.writeGARC(romEntry.getFile("PickupData"), pickupGarc); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private void computeCRC32sForRom() throws IOException { + this.actualFileCRC32s = new HashMap<>(); + this.actualCodeCRC32 = FileFunctions.getCRC32(code); + for (String fileKey : romEntry.files.keySet()) { + byte[] file = readFile(romEntry.getFile(fileKey)); + long crc32 = FileFunctions.getCRC32(file); + this.actualFileCRC32s.put(fileKey, crc32); + } + } + + @Override + public boolean isRomValid() { + int index = this.hasGameUpdateLoaded() ? 1 : 0; + if (romEntry.expectedCodeCRC32s[index] != actualCodeCRC32) { + return false; + } + + for (String fileKey : romEntry.files.keySet()) { + long expectedCRC32 = romEntry.files.get(fileKey).expectedCRC32s[index]; + long actualCRC32 = actualFileCRC32s.get(fileKey); + if (expectedCRC32 != actualCRC32) { + return false; + } + } + + return true; + } + + @Override + public BufferedImage getMascotImage() { + try { + GARCArchive pokespritesGARC = this.readGARC(romEntry.getFile("PokemonGraphics"), false); + int pkIndex = this.random.nextInt(pokespritesGARC.files.size() - 1) + 1; + if (romEntry.romType == Gen7Constants.Type_SM) { + while (pkIndex == 1109 || pkIndex == 1117) { + pkIndex = this.random.nextInt(pokespritesGARC.files.size() - 1) + 1; + } + } + byte[] iconBytes = pokespritesGARC.files.get(pkIndex).get(0); + BFLIM icon = new BFLIM(iconBytes); + return icon.getImage(); + } catch (IOException e) { + throw new RandomizerIOException(e); + } + } + + private class ZoneData { + public int worldIndex; + public int areaIndex; + public int parentMap; + public String locationName; + private byte[] data; + + public static final int size = 0x54; + + public ZoneData(byte[] zoneDataBytes, int index) { + data = new byte[size]; + System.arraycopy(zoneDataBytes, index * size, data, 0, size); + parentMap = FileFunctions.readFullInt(data, 0x1C); + } + } + + private class AreaData { + public int fileNumber; + public boolean hasTables; + public List encounterTables; + public List zones; + public String name; + + public AreaData() { + encounterTables = new ArrayList<>(); + } + } + + @Override + public List getAllConsumableHeldItems() { + return Gen7Constants.consumableHeldItems; + } + + @Override + public List getAllHeldItems() { + return Gen7Constants.allHeldItems; + } + + @Override + public boolean hasRivalFinalBattle() { + return true; + } + + @Override + public List getSensibleHeldItemsFor(TrainerPokemon tp, boolean consumableOnly, List moves, int[] pokeMoves) { + List items = new ArrayList<>(); + items.addAll(Gen7Constants.generalPurposeConsumableItems); + int frequencyBoostCount = 6; // Make some very good items more common, but not too common + if (!consumableOnly) { + frequencyBoostCount = 8; // bigger to account for larger item pool. + items.addAll(Gen7Constants.generalPurposeItems); + } + int numDamagingMoves = 0; + for (int moveIdx : pokeMoves) { + Move move = moves.get(moveIdx); + if (move == null) { + continue; + } + if (move.category == MoveCategory.PHYSICAL) { + numDamagingMoves++; + items.add(Items.liechiBerry); + items.add(Gen7Constants.consumableTypeBoostingItems.get(move.type)); + if (!consumableOnly) { + items.addAll(Gen7Constants.typeBoostingItems.get(move.type)); + items.add(Items.choiceBand); + items.add(Items.muscleBand); + } + } + if (move.category == MoveCategory.SPECIAL) { + numDamagingMoves++; + items.add(Items.petayaBerry); + items.add(Gen7Constants.consumableTypeBoostingItems.get(move.type)); + if (!consumableOnly) { + items.addAll(Gen7Constants.typeBoostingItems.get(move.type)); + items.add(Items.wiseGlasses); + items.add(Items.choiceSpecs); + } + } + if (!consumableOnly && Gen7Constants.moveBoostingItems.containsKey(moveIdx)) { + items.addAll(Gen7Constants.moveBoostingItems.get(moveIdx)); + } + } + if (numDamagingMoves >= 2) { + items.add(Items.assaultVest); + } + Map byType = Effectiveness.against(tp.pokemon.primaryType, tp.pokemon.secondaryType, 7); + for(Map.Entry entry : byType.entrySet()) { + Integer berry = Gen7Constants.weaknessReducingBerries.get(entry.getKey()); + if (entry.getValue() == Effectiveness.DOUBLE) { + items.add(berry); + } else if (entry.getValue() == Effectiveness.QUADRUPLE) { + for (int i = 0; i < frequencyBoostCount; i++) { + items.add(berry); + } + } + } + if (byType.get(Type.NORMAL) == Effectiveness.NEUTRAL) { + items.add(Items.chilanBerry); + } + + int ability = this.getAbilityForTrainerPokemon(tp); + if (ability == Abilities.levitate) { + items.removeAll(Arrays.asList(Items.shucaBerry)); + } else if (byType.get(Type.GROUND) == Effectiveness.DOUBLE || byType.get(Type.GROUND) == Effectiveness.QUADRUPLE) { + items.add(Items.airBalloon); + } + if (Gen7Constants.consumableAbilityBoostingItems.containsKey(ability)) { + items.add(Gen7Constants.consumableAbilityBoostingItems.get(ability)); + } + + if (!consumableOnly) { + if (Gen7Constants.abilityBoostingItems.containsKey(ability)) { + items.addAll(Gen7Constants.abilityBoostingItems.get(ability)); + } + if (tp.pokemon.primaryType == Type.POISON || tp.pokemon.secondaryType == Type.POISON) { + items.add(Items.blackSludge); + } + List speciesItems = Gen7Constants.speciesBoostingItems.get(tp.pokemon.number); + if (speciesItems != null) { + for (int i = 0; i < frequencyBoostCount; i++) { + items.addAll(speciesItems); + } + } + if (!tp.pokemon.evolutionsFrom.isEmpty() && tp.level >= 20) { + // eviolite can be too good for early game, so we gate it behind a minimum level. + // We go with the same level as the option for "No early wonder guard". + items.add(Items.eviolite); + } + } + return items; + } +} diff --git a/src/com/pkrandom/romhandlers/RomHandler.java b/src/com/pkrandom/romhandlers/RomHandler.java new file mode 100755 index 0000000..4d2e2c8 --- /dev/null +++ b/src/com/pkrandom/romhandlers/RomHandler.java @@ -0,0 +1,660 @@ +package com.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- RomHandler.java - defines the functionality that each randomization --*/ +/*-- handler must implement. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2020. --*/ +/*-- --*/ +/*-- The custom code written here is licensed under the terms of the GPL: --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +import java.awt.image.BufferedImage; +import java.io.PrintStream; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import com.pkrandom.MiscTweak; +import com.pkrandom.Settings; +import com.pkrandom.pokemon.*; + +public interface RomHandler { + + abstract class Factory { + public RomHandler create(Random random) { + return create(random, null); + } + + public abstract RomHandler create(Random random, PrintStream log); + + public abstract boolean isLoadable(String filename); + } + + // ======================= + // Basic load/save methods + // ======================= + + boolean loadRom(String filename); + + boolean saveRomFile(String filename, long seed); + + boolean saveRomDirectory(String filename); + + String loadedFilename(); + + // ============================================================= + // Methods relating to game updates for the 3DS and Switch games + // ============================================================= + + boolean hasGameUpdateLoaded(); + + boolean loadGameUpdate(String filename); + + void removeGameUpdate(); + + String getGameUpdateVersion(); + + // =========== + // Log methods + // =========== + + void setLog(PrintStream logStream); + + void printRomDiagnostics(PrintStream logStream); + + boolean isRomValid(); + + // ====================================================== + // Methods for retrieving a list of Pokemon objects. + // Note that for many of these lists, index 0 is null. + // Instead, you use index on the species' National Dex ID + // ====================================================== + + List getPokemon(); + + List getPokemonInclFormes(); + + List getAltFormes(); + + List getMegaEvolutions(); + + Pokemon getAltFormeOfPokemon(Pokemon pk, int forme); + + List getIrregularFormes(); + + // ================================== + // Methods to set up Gen Restrictions + // ================================== + + void setPokemonPool(Settings settings); + + void removeEvosForPokemonPool(); + + // =============== + // Starter Pokemon + // =============== + + List getStarters(); + + boolean setStarters(List newStarters); + + boolean hasStarterAltFormes(); + + int starterCount(); + + void customStarters(Settings settings); + + void randomizeStarters(Settings settings); + + void randomizeBasicTwoEvosStarters(Settings settings); + + List getPickedStarters(); + + boolean supportsStarterHeldItems(); + + List getStarterHeldItems(); + + void setStarterHeldItems(List items); + + void randomizeStarterHeldItems(Settings settings); + + // ======================= + // Pokemon Base Statistics + // ======================= + + // Run the stats shuffler on each Pokemon. + void shufflePokemonStats(Settings settings); + + // Randomize stats following evolutions for proportions or not (see + // tooltips) + void randomizePokemonStats(Settings settings); + + // Update base stats to specified generation + void updatePokemonStats(Settings settings); + + Map getUpdatedPokemonStats(int generation); + + void standardizeEXPCurves(Settings settings); + + // ==================================== + // Methods for selecting random Pokemon + // ==================================== + + // Give a random Pokemon who's in this game + Pokemon randomPokemon(); + + Pokemon randomPokemonInclFormes(); + + // Give a random non-legendary Pokemon who's in this game + // Business rules for who's legendary are in Pokemon class + Pokemon randomNonLegendaryPokemon(); + + // Give a random legendary Pokemon who's in this game + // Business rules for who's legendary are in Pokemon class + Pokemon randomLegendaryPokemon(); + + // Give a random Pokemon who has 2 evolution stages + // Should make a good starter Pokemon + Pokemon random2EvosPokemon(boolean allowAltFormes); + + // ============= + // Pokemon Types + // ============= + + // return a random type valid in this game. + Type randomType(); + + boolean typeInGame(Type type); + + // randomize Pokemon types, with a switch on whether evolutions + // should follow the same types or not. + // some evolutions dont anyway, e.g. Eeveelutions, Hitmons + void randomizePokemonTypes(Settings settings); + + // ================= + // Pokemon Abilities + // ================= + + int abilitiesPerPokemon(); + + int highestAbilityIndex(); + + String abilityName(int number); + + void randomizeAbilities(Settings settings); + + Map> getAbilityVariations(); + + List getUselessAbilities(); + + int getAbilityForTrainerPokemon(TrainerPokemon tp); + + boolean hasMegaEvolutions(); + + // ============ + // Wild Pokemon + // ============ + + List getEncounters(boolean useTimeOfDay); + + void setEncounters(boolean useTimeOfDay, List encounters); + + void randomEncounters(Settings settings); + + void area1to1Encounters(Settings settings); + + void game1to1Encounters(Settings settings); + + void onlyChangeWildLevels(Settings settings); + + boolean hasTimeBasedEncounters(); + + boolean hasWildAltFormes(); + + List bannedForWildEncounters(); + + void randomizeWildHeldItems(Settings settings); + + void changeCatchRates(Settings settings); + + void minimumCatchRate(int rateNonLegendary, int rateLegendary); + + void enableGuaranteedPokemonCatching(); + + // =============== + // Trainer Pokemon + // =============== + + List getTrainers(); + + List getMainPlaythroughTrainers(); + + List getEliteFourTrainers(boolean isChallengeMode); + + void setTrainers(List trainerData, boolean doubleBattleMode); + + void randomizeTrainerPokes(Settings settings); + + void randomizeTrainerHeldItems(Settings settings); + + List getSensibleHeldItemsFor(TrainerPokemon tp, boolean consumableOnly, List moves, int[] pokeMoves); + + List getAllConsumableHeldItems(); + + List getAllHeldItems(); + + void rivalCarriesStarter(); + + boolean hasRivalFinalBattle(); + + void forceFullyEvolvedTrainerPokes(Settings settings); + + void onlyChangeTrainerLevels(Settings settings); + + void addTrainerPokemon(Settings settings); + + void doubleBattleMode(); + + List getMoveSelectionPoolAtLevel(TrainerPokemon tp, boolean cyclicEvolutions); + + void pickTrainerMovesets(Settings settings); + + // ========= + // Move Data + // ========= + + void randomizeMovePowers(); + + void randomizeMovePPs(); + + void randomizeMoveAccuracies(); + + void randomizeMoveTypes(); + + boolean hasPhysicalSpecialSplit(); + + void randomizeMoveCategory(); + + void updateMoves(Settings settings); + + // stuff for printing move changes + void initMoveUpdates(); + + Map getMoveUpdates(); + + // return all the moves valid in this game. + List getMoves(); + + // ================ + // Pokemon Movesets + // ================ + + Map> getMovesLearnt(); + + void setMovesLearnt(Map> movesets); + + List getMovesBannedFromLevelup(); + + Map> getEggMoves(); + + void setEggMoves(Map> eggMoves); + + void randomizeMovesLearnt(Settings settings); + + void randomizeEggMoves(Settings settings); + + void orderDamagingMovesByDamage(); + + void metronomeOnlyMode(); + + boolean supportsFourStartingMoves(); + + // ============== + // Static Pokemon + // ============== + + List getStaticPokemon(); + + boolean setStaticPokemon(List staticPokemon); + + void randomizeStaticPokemon(Settings settings); + + boolean canChangeStaticPokemon(); + + boolean hasStaticAltFormes(); + + List bannedForStaticPokemon(); + + boolean forceSwapStaticMegaEvos(); + + void onlyChangeStaticLevels(Settings settings); + + boolean hasMainGameLegendaries(); + + List getMainGameLegendaries(); + + List getSpecialMusicStatics(); + + void applyCorrectStaticMusic(Map specialMusicStaticChanges); + + boolean hasStaticMusicFix(); + + // ============= + // Totem Pokemon + // ============= + + List getTotemPokemon(); + + void setTotemPokemon(List totemPokemon); + + void randomizeTotemPokemon(Settings settings); + + // ========= + // TMs & HMs + // ========= + + List getTMMoves(); + + List getHMMoves(); + + void setTMMoves(List moveIndexes); + + void randomizeTMMoves(Settings settings); + + int getTMCount(); + + int getHMCount(); + + /** + * Get TM/HM compatibility data from this rom. The result should contain a + * boolean array for each Pokemon indexed as such: + * + * 0: blank (false) / 1 - (getTMCount()) : TM compatibility / + * (getTMCount()+1) - (getTMCount()+getHMCount()) - HM compatibility + * + * @return Map of TM/HM compatibility + */ + + Map getTMHMCompatibility(); + + void setTMHMCompatibility(Map compatData); + + void randomizeTMHMCompatibility(Settings settings); + + void fullTMHMCompatibility(); + + void ensureTMCompatSanity(); + + void ensureTMEvolutionSanity(); + + void fullHMCompatibility(); + + // =========== + // Move Tutors + // =========== + + void copyTMCompatibilityToCosmeticFormes(); + + boolean hasMoveTutors(); + + List getMoveTutorMoves(); + + void setMoveTutorMoves(List moves); + + void randomizeMoveTutorMoves(Settings settings); + + Map getMoveTutorCompatibility(); + + void setMoveTutorCompatibility(Map compatData); + + void randomizeMoveTutorCompatibility(Settings settings); + + void fullMoveTutorCompatibility(); + + void ensureMoveTutorCompatSanity(); + + void ensureMoveTutorEvolutionSanity(); + + // ============= + // Trainer Names + // ============= + + void copyMoveTutorCompatibilityToCosmeticFormes(); + + boolean canChangeTrainerText(); + + List getTrainerNames(); + + void setTrainerNames(List trainerNames); + + enum TrainerNameMode { + SAME_LENGTH, MAX_LENGTH, MAX_LENGTH_WITH_CLASS + } + + TrainerNameMode trainerNameMode(); + + // Returns this with or without the class + int maxTrainerNameLength(); + + // Only relevant for gen2, which has fluid trainer name length but + // only a certain amount of space in the ROM bank. + int maxSumOfTrainerNameLengths(); + + // Only needed if above mode is "MAX LENGTH WITH CLASS" + List getTCNameLengthsByTrainer(); + + void randomizeTrainerNames(Settings settings); + + // =============== + // Trainer Classes + // =============== + + List getTrainerClassNames(); + + void setTrainerClassNames(List trainerClassNames); + + boolean fixedTrainerClassNamesLength(); + + int maxTrainerClassNameLength(); + + void randomizeTrainerClassNames(Settings settings); + + List getDoublesTrainerClasses(); + + // ===== + // Items + // ===== + + ItemList getAllowedItems(); + + ItemList getNonBadItems(); + + List getEvolutionItems(); + + List getXItems(); + + List getUniqueNoSellItems(); + + List getRegularShopItems(); + + List getOPShopItems(); + + String[] getItemNames(); + + // =========== + // Field Items + // =========== + + // TMs on the field + + List getRequiredFieldTMs(); + + List getCurrentFieldTMs(); + + void setFieldTMs(List fieldTMs); + + // Everything else + + List getRegularFieldItems(); + + void setRegularFieldItems(List items); + + // Randomizer methods + + void shuffleFieldItems(); + + void randomizeFieldItems(Settings settings); + + // ============ + // Special Shops + // ============= + + boolean hasShopRandomization(); + + void shuffleShopItems(); + + void randomizeShopItems(Settings settings); + + Map getShopItems(); + + void setShopItems(Map shopItems); + + void setShopPrices(); + + // ============ + // Pickup Items + // ============ + + List getPickupItems(); + + void setPickupItems(List pickupItems); + + void randomizePickupItems(Settings settings); + + // ============== + // In-Game Trades + // ============== + + List getIngameTrades(); + + void setIngameTrades(List trades); + + void randomizeIngameTrades(Settings settings); + + boolean hasDVs(); + + int maxTradeNicknameLength(); + + int maxTradeOTNameLength(); + + // ================== + // Pokemon Evolutions + // ================== + + void removeImpossibleEvolutions(Settings settings); + + void condenseLevelEvolutions(int maxLevel, int maxIntermediateLevel); + + void makeEvolutionsEasier(Settings settings); + + void removeTimeBasedEvolutions(); + + Set getImpossibleEvoUpdates(); + + Set getEasierEvoUpdates(); + + Set getTimeBasedEvoUpdates(); + + void randomizeEvolutions(Settings settings); + + void randomizeEvolutionsEveryLevel(Settings settings); + + // In the earlier games, alt formes use the same evolutions as the base forme. + // In later games, this was changed so that alt formes can have unique evolutions + // compared to the base forme. + boolean altFormesCanHaveDifferentEvolutions(); + + // ================================== + // (Mostly) unchanging lists of moves + // ================================== + + List getGameBreakingMoves(); + + List getIllegalMoves(); + + // includes game or gen-specific moves like Secret Power + // but NOT healing moves (Softboiled, Milk Drink) + List getFieldMoves(); + + // any HMs required to obtain 4 badges + // (excluding Gameshark codes or early drink in RBY) + List getEarlyRequiredHMMoves(); + + + // ==== + // Misc + // ==== + + boolean isYellow(); + + String getROMName(); + + String getROMCode(); + + String getSupportLevel(); + + String getDefaultExtension(); + + int internalStringLength(String string); + + void randomizeIntroPokemon(); + + BufferedImage getMascotImage(); + + int generationOfPokemon(); + + void writeCheckValueToROM(int value); + + // =========== + // code tweaks + // =========== + + int miscTweaksAvailable(); + + void applyMiscTweaks(Settings settings); + + void applyMiscTweak(MiscTweak tweak); + + boolean isEffectivenessUpdated(); + + void renderPlacementHistory(); + + // ========================== + // Misc forme-related methods + // ========================== + + boolean hasFunctionalFormes(); + + List getAbilityDependentFormes(); + + List getBannedFormesForPlayerPokemon(); + + List getBannedFormesForTrainerPokemon(); +} \ No newline at end of file -- cgit v1.2.3