summaryrefslogtreecommitdiff
path: root/src/com/sneed/pkrandom/romhandlers/Gen7RomHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/sneed/pkrandom/romhandlers/Gen7RomHandler.java')
-rw-r--r--src/com/sneed/pkrandom/romhandlers/Gen7RomHandler.java3821
1 files changed, 0 insertions, 3821 deletions
diff --git a/src/com/sneed/pkrandom/romhandlers/Gen7RomHandler.java b/src/com/sneed/pkrandom/romhandlers/Gen7RomHandler.java
deleted file mode 100644
index 1a9909b..0000000
--- a/src/com/sneed/pkrandom/romhandlers/Gen7RomHandler.java
+++ /dev/null
@@ -1,3821 +0,0 @@
-package com.sneed.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 <http://www.gnu.org/licenses/>. --*/
-/*----------------------------------------------------------------------------*/
-
-import com.sneed.pkrandom.FileFunctions;
-import com.sneed.pkrandom.MiscTweak;
-import com.sneed.pkrandom.RomFunctions;
-import com.sneed.pkrandom.Settings;
-import com.sneed.pkrandom.constants.*;
-import com.sneed.pkrandom.ctr.AMX;
-import com.sneed.pkrandom.ctr.BFLIM;
-import com.sneed.pkrandom.ctr.GARCArchive;
-import com.sneed.pkrandom.ctr.Mini;
-import com.sneed.pkrandom.exceptions.RandomizerIOException;
-import com.sneed.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<String, RomFileEntry> files = new HashMap<>();
- private Map<Integer, Integer> linkedStaticOffsets = new HashMap<>();
- private Map<String, String> strings = new HashMap<>();
- private Map<String, Integer> numbers = new HashMap<>();
- private Map<String, int[]> arrayEntries = new HashMap<>();
- private Map<String, OffsetWithinEntry[]> 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<RomEntry> 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<Integer,FormeInfo> formeMappings = new TreeMap<>();
- private Map<Integer,Map<Integer,Integer>> absolutePokeNumByBaseForme;
- private Map<Integer,Integer> dummyAbsolutePokeNums;
- private List<Pokemon> pokemonList;
- private List<Pokemon> pokemonListInclFormes;
- private List<MegaEvolution> megaEvolutions;
- private List<AreaData> areaDataList;
- private Move[] moves;
- private RomEntry romEntry;
- private byte[] code;
- private List<String> itemNames;
- private List<String> shopNames;
- private List<String> abilityNames;
- private ItemList allowedItems, nonBadItems;
- private long actualCodeCRC32;
- private Map<String, Long> 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<String> getStrings(boolean isStoryText, int index) {
- GARCArchive baseGARC = isStoryText ? storyTextGarc : stringsGarc;
- return getStrings(baseGARC, index);
- }
-
- private List<String> 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<String> strings) {
- GARCArchive baseGARC = isStoryText ? storyTextGarc : stringsGarc;
- setStrings(baseGARC, index, strings);
- }
-
- private void setStrings(GARCArchive textGARC, int index, List<String> 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<Integer,Integer> 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<Integer,FormeInfo> 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<Integer> 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<String> 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<Integer, Integer> 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<String> 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<Integer> 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<String> 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<Pokemon> getPokemon() {
- return pokemonList;
- }
-
- @Override
- public List<Pokemon> getPokemonInclFormes() {
- return pokemonListInclFormes;
- }
-
- @Override
- public List<Pokemon> getAltFormes() {
- int formeCount = Gen7Constants.getFormeCount(romEntry.romType);
- int pokemonCount = Gen7Constants.getPokemonCount(romEntry.romType);
- return pokemonListInclFormes.subList(pokemonCount + 1, pokemonCount + formeCount + 1);
- }
-
- @Override
- public List<MegaEvolution> 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<Pokemon> getIrregularFormes() {
- return Gen7Constants.getIrregularFormes(romEntry.romType).stream().map(i -> pokes[i]).collect(Collectors.toList());
- }
-
- @Override
- public boolean hasFunctionalFormes() {
- return true;
- }
-
- @Override
- public List<Pokemon> getStarters() {
- List<StaticEncounter> 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<Pokemon> 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<Pokemon> newStarters) {
- int starterTextIndex = romEntry.getInt("StarterTextOffset");
- List<String> 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<Integer, StatChange> getUpdatedPokemonStats(int generation) {
- Map<Integer, StatChange> 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<Integer> getStarterHeldItems() {
- List<Integer> 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<Integer> 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<Move> getMoves() {
- return Arrays.asList(moves);
- }
-
- @Override
- public List<EncounterSet> getEncounters(boolean useTimeOfDay) {
- List<EncounterSet> 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<EncounterSet> encountersList) {
- Iterator<EncounterSet> 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<Encounter> encounters) {
- Iterator<Encounter> 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<AreaData> getAreaData() throws IOException {
- GARCArchive worldDataGarc = readGARC(romEntry.getFile("WorldData"), false);
- List<byte[]> worlds = new ArrayList<>();
- for (Map<Integer, byte[]> 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<String> 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<String> createGoodLocationList() {
- List<String> locationList = getStrings(false, romEntry.getInt("MapNamesTextOffset"));
- List<String> 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<String> 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<String> locationList, List<byte[]> 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> zoneData) {
- Set<String> 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<Trainer> getTrainers() {
- List<Trainer> 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<String> tclasses = this.getTrainerClassNames();
- List<String> tnames = this.getTrainerNames();
- Map<Integer,String> 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<Integer> getMainPlaythroughTrainers() {
- return new ArrayList<>();
- }
-
- @Override
- public List<Integer> getEliteFourTrainers(boolean isChallengeMode) {
- return Arrays.stream(romEntry.arrayEntries.get("EliteFourIndices")).boxed().collect(Collectors.toList());
- }
-
- @Override
- public void setTrainers(List<Trainer> trainerData, boolean doubleBattleMode) {
- Iterator<Trainer> 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<Integer, List<MoveLearnt>> 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<TrainerPokemon> 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<Pokemon> 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<Integer> getEvolutionItems() {
- return Gen7Constants.evolutionItems;
- }
-
- @Override
- public Map<Integer, List<MoveLearnt>> getMovesLearnt() {
- Map<Integer, List<MoveLearnt>> 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<MoveLearnt> 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<Integer, List<MoveLearnt>> 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<MoveLearnt> 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<Integer, List<Integer>> getEggMoves() {
- Map<Integer, List<Integer>> eggMoves = new TreeMap<>();
- try {
- GARCArchive eggMovesGarc = this.readGARC(romEntry.getFile("EggMoves"),true);
- TreeMap<Pokemon, Integer> 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<Integer> 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<Pokemon> 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<Integer> 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<Integer, List<Integer>> eggMoves) {
- try {
- GARCArchive eggMovesGarc = this.readGARC(romEntry.getFile("EggMoves"), true);
- TreeMap<Pokemon, Integer> 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<Integer> moves = eggMoves.get(pkmn.number);
- for (int j = 0; j < moves.size(); j++) {
- writeWord(movedata, 4 + (j * 2), moves.get(j));
- }
- }
- Iterator<Pokemon> 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<Integer> 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<Integer> getMainGameLegendaries() {
- return Arrays.stream(romEntry.arrayEntries.get("MainGameLegendaries")).boxed().collect(Collectors.toList());
- }
-
- @Override
- public List<Integer> getSpecialMusicStatics() {
- return new ArrayList<>();
- }
-
- @Override
- public void applyCorrectStaticMusic(Map<Integer, Integer> specialMusicStaticChanges) {
-
- }
-
- @Override
- public boolean hasStaticMusicFix() {
- return false;
- }
-
- @Override
- public List<TotemPokemon> getTotemPokemon() {
- List<TotemPokemon> totems = new ArrayList<>();
- try {
- GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true);
- List<Integer> 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> totemPokemon) {
- try {
- GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true);
- List<Integer> totemIndices =
- Arrays.stream(romEntry.arrayEntries.get("TotemPokemonIndices")).boxed().collect(Collectors.toList());
- Iterator<TotemPokemon> 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<StaticEncounter> getStaticPokemon() {
- List<StaticEncounter> statics = new ArrayList<>();
- try {
- GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true);
- List<Integer> 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<StaticEncounter> statics) {
- List<StaticEncounter> encountersToRemove = new ArrayList<>();
- for (Map.Entry<Integer, Integer> 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<StaticEncounter> 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<StaticEncounter> staticPokemon) {
- try {
- unlinkStaticEncounters(staticPokemon);
- GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true);
- List<Integer> skipIndices =
- Arrays.stream(romEntry.arrayEntries.get("TotemPokemonIndices")).boxed().collect(Collectors.toList());
- skipIndices.addAll(Arrays.stream(romEntry.arrayEntries.get("AllyPokemonIndices")).boxed().collect(Collectors.toList()));
- Iterator<StaticEncounter> 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<StaticEncounter> statics) {
- List<Integer> offsetsToInsert = new ArrayList<>();
- for (Map.Entry<Integer, Integer> entry : romEntry.linkedStaticOffsets.entrySet()) {
- offsetsToInsert.add(entry.getValue());
- }
- Collections.sort(offsetsToInsert);
- for (Integer offsetToInsert : offsetsToInsert) {
- statics.add(offsetToInsert, new StaticEncounter());
- }
- for (Map.Entry<Integer, Integer> 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<Integer> 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<Integer> 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<Integer> getHMMoves() {
- // Gen 7 does not have any HMs
- return new ArrayList<>();
- }
-
- @Override
- public void setTMMoves(List<Integer> 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<String> itemDescriptions = getStrings(false, romEntry.getInt("ItemDescriptionsTextOffset"));
- List<String> 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<Integer> 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<Pokemon, boolean[]> getTMHMCompatibility() {
- Map<Pokemon, boolean[]> 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<Pokemon, boolean[]> compatData) {
- for (Map.Entry<Pokemon, boolean[]> 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<Integer> getMoveTutorMoves() {
- List<Integer> 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<Integer> 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<Pokemon, boolean[]> getMoveTutorCompatibility() {
- Map<Pokemon, boolean[]> 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<Pokemon, boolean[]> 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<Integer> getMovesBannedFromLevelup() {
- return Gen7Constants.bannedMoves;
- }
-
- @Override
- public boolean hasWildAltFormes() {
- return true;
- }
-
- @Override
- public void removeImpossibleEvolutions(Settings settings) {
- boolean changeMoveEvos = !(settings.getMovesetsMod() == Settings.MovesetsMod.UNCHANGED);
-
- Map<Integer, List<MoveLearnt>> movesets = this.getMovesLearnt();
- Set<Evolution> 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<Evolution> 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<String> getTrainerNames() {
- List<String> tnames = getStrings(false, romEntry.getInt("TrainerNamesTextOffset"));
- tnames.remove(0); // blank one
-
- return tnames;
- }
-
- @Override
- public int maxTrainerNameLength() {
- return 10;
- }
-
- @Override
- public void setTrainerNames(List<String> trainerNames) {
- List<String> tnames = getStrings(false, romEntry.getInt("TrainerNamesTextOffset"));
- List<String> 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<String> strings, int index) throws IOException {
- List<String> 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<Integer> getTCNameLengthsByTrainer() {
- return new ArrayList<>();
- }
-
- @Override
- public List<String> getTrainerClassNames() {
- return getStrings(false, romEntry.getInt("TrainerClassesTextOffset"));
- }
-
- @Override
- public void setTrainerClassNames(List<String> 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<Integer> getDoublesTrainerClasses() {
- int[] doublesClasses = romEntry.arrayEntries.get("DoublesTrainerClasses");
- List<Integer> 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<Integer> getUniqueNoSellItems() {
- return new ArrayList<>();
- }
-
- @Override
- public List<Integer> getRegularShopItems() {
- return Gen7Constants.getRegularShopItems(romEntry.romType);
- }
-
- @Override
- public List<Integer> 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<Integer, List<Integer>> getAbilityVariations() {
- return Gen7Constants.abilityVariations;
- }
-
- @Override
- public List<Integer> 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<Integer> 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<Integer> getCurrentFieldTMs() {
- List<Integer> fieldItems = this.getFieldItems();
- List<Integer> 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<Integer> fieldTMs) {
- List<Integer> fieldItems = this.getFieldItems();
- int fiLength = fieldItems.size();
- Iterator<Integer> iterTMs = fieldTMs.iterator();
- Map<Integer,Integer> 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<Integer> getRegularFieldItems() {
- List<Integer> fieldItems = this.getFieldItems();
- List<Integer> 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<Integer> items) {
- List<Integer> fieldItems = this.getFieldItems();
- int fiLength = fieldItems.size();
- Iterator<Integer> 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<Integer> getRequiredFieldTMs() {
- return Gen7Constants.getRequiredFieldTMs(romEntry.romType);
- }
-
- public List<Integer> getFieldItems() {
- List<Integer> 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<Integer> items) {
- try {
- int numberOfAreas = encounterGarc.files.size() / 11;
- Iterator<Integer> 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<IngameTrade> getIngameTrades() {
- List<IngameTrade> ingameTrades = new ArrayList<>();
- try {
- GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true);
- List<String> 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<IngameTrade> trades) {
- try {
- List<IngameTrade> oldTrades = this.getIngameTrades();
- GARCArchive staticGarc = readGARC(romEntry.getFile("StaticPokemon"), true);
- List<String> tradeStrings = getStrings(true, romEntry.getInt("IngameTradesTextOffset"));
- Map<Integer, List<Integer>> 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<Integer> 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<String> tradeStrings, List<Integer> 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<Pokemon> pokemonIncluded = this.mainPokemonListInclFormes;
- Set<Evolution> 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<Integer> getFieldMoves() {
- // Gen 7 does not have field moves
- return new ArrayList<>();
- }
-
- @Override
- public List<Integer> getEarlyRequiredHMMoves() {
- // Gen 7 does not have any HMs
- return new ArrayList<>();
- }
-
- @Override
- public Map<Integer, Shop> 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<Integer, Shop> 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<Integer> 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<Integer, Shop> 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<Integer> shopContents = shopItems.get(i).items;
- Iterator<Integer> 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<PickupItem> getPickupItems() {
- List<PickupItem> 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<PickupItem> 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<byte[]> encounterTables;
- public List<ZoneData> zones;
- public String name;
-
- public AreaData() {
- encounterTables = new ArrayList<>();
- }
- }
-
- @Override
- public List<Integer> getAllConsumableHeldItems() {
- return Gen7Constants.consumableHeldItems;
- }
-
- @Override
- public List<Integer> getAllHeldItems() {
- return Gen7Constants.allHeldItems;
- }
-
- @Override
- public boolean hasRivalFinalBattle() {
- return true;
- }
-
- @Override
- public List<Integer> getSensibleHeldItemsFor(TrainerPokemon tp, boolean consumableOnly, List<Move> moves, int[] pokeMoves) {
- List<Integer> 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<Type, Effectiveness> byType = Effectiveness.against(tp.pokemon.primaryType, tp.pokemon.secondaryType, 7);
- for(Map.Entry<Type, Effectiveness> 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<Integer> 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;
- }
-}