diff options
author | Dabomstew <dbs.stew@gmail.com> | 2016-02-22 10:57:03 +1300 |
---|---|---|
committer | Dabomstew <dbs.stew@gmail.com> | 2016-02-22 10:57:03 +1300 |
commit | a97ccb1d4cdc5961d90ad5cbb68743705ca12941 (patch) | |
tree | a75ecb9e55776ad56ad41309a9fbbf08bfa4adf1 /src/com/dabomstew/pkrandom/romhandlers | |
parent | b824cc40c298095ad46e2e02563688158e49a505 (diff) |
Reformat code en masse.
Diffstat (limited to 'src/com/dabomstew/pkrandom/romhandlers')
10 files changed, 16505 insertions, 17497 deletions
diff --git a/src/com/dabomstew/pkrandom/romhandlers/AbstractDSRomHandler.java b/src/com/dabomstew/pkrandom/romhandlers/AbstractDSRomHandler.java index 357edb6..eae4cf2 100755 --- a/src/com/dabomstew/pkrandom/romhandlers/AbstractDSRomHandler.java +++ b/src/com/dabomstew/pkrandom/romhandlers/AbstractDSRomHandler.java @@ -35,382 +35,372 @@ import com.dabomstew.pkrandom.pokemon.Type; public abstract class AbstractDSRomHandler extends AbstractRomHandler { - protected String dataFolder; - private NDSRom baseRom; - private String loadedFN; - - public AbstractDSRomHandler(Random random, PrintStream logStream) { - super(random, logStream); - } - - protected abstract boolean detectNDSRom(String ndsCode); - - @Override - public boolean loadRom(String filename) { - if (!this.detectNDSRom(getROMCodeFromFile(filename))) { - return false; - } - // Load inner rom - try { - baseRom = new NDSRom(filename); - } catch (IOException e) { - throw new RuntimeException(e); - } - loadedFN = filename; - loadedROM(baseRom.getCode()); - return true; - } - - @Override - public String loadedFilename() { - return loadedFN; - } - - protected byte[] get3byte(int amount) { - byte[] ret = new byte[3]; - ret[0] = (byte) (amount & 0xFF); - ret[1] = (byte) ((amount >> 8) & 0xFF); - ret[2] = (byte) ((amount >> 16) & 0xFF); - return ret; - } - - protected abstract void loadedROM(String romCode); - - protected abstract void savingROM(); - - @Override - public boolean saveRom(String filename) { - savingROM(); - try { - baseRom.saveTo(filename); - } catch (Exception e) { - throw new RuntimeException(e); - } - return true; - } - - public void closeInnerRom() throws IOException { - baseRom.closeROM(); - } - - @Override - public boolean canChangeStaticPokemon() { - return false; - } - - @Override - public boolean hasPhysicalSpecialSplit() { - // Default value for Gen4+. - // Handlers can override again in case of ROM hacks etc. - return true; - } - - public NARCContents readNARC(String subpath) throws IOException { - Map<String, byte[]> frames = readNitroFrames(subpath); - if (!frames.containsKey("FATB") || !frames.containsKey("FNTB") - || !frames.containsKey("FIMG")) { - System.err.println("Not a valid narc file"); - return null; - } - // File contents - NARCContents narc = new NARCContents(); - byte[] fatbframe = frames.get("FATB"); - byte[] fimgframe = frames.get("FIMG"); - int fileCount = readLong(fatbframe, 0); - for (int i = 0; i < fileCount; i++) { - int startOffset = readLong(fatbframe, 4 + i * 8); - int endOffset = readLong(fatbframe, 8 + i * 8); - int length = (endOffset - startOffset); - byte[] thisFile = new byte[length]; - try { - System.arraycopy(fimgframe, startOffset, thisFile, 0, length); - } catch (ArrayIndexOutOfBoundsException ex) { - throw new RuntimeException(ex); - } - narc.files.add(thisFile); - } - // Filenames? - byte[] fntbframe = frames.get("FNTB"); - int unk1 = readLong(fntbframe, 0); - if (unk1 == 8) { - // Filenames exist - narc.hasFilenames = true; - int offset = 8; - for (int i = 0; i < fileCount; i++) { - int fnLength = (fntbframe[offset] & 0xFF); - offset++; - byte[] filenameBA = new byte[fnLength]; - System.arraycopy(fntbframe, offset, filenameBA, 0, fnLength); - String filename = new String(filenameBA, "US-ASCII"); - narc.filenames.add(filename); - } - } else { - narc.hasFilenames = false; - for (int i = 0; i < fileCount; i++) { - narc.filenames.add(null); - } - } - return narc; - } - - public void writeNARC(String subpath, NARCContents narc) throws IOException { - // Get bytes required for FIMG frame - int bytesRequired = 0; - for (byte[] file : narc.files) { - bytesRequired += Math.ceil(file.length / 4.0) * 4; - } - // FIMG frame & FATB frame build - - // 4 for numentries, 8*size for entries, 8 for nitro header - byte[] fatbFrame = new byte[4 + narc.files.size() * 8 + 8]; - // bytesRequired + 8 for nitro header - byte[] fimgFrame = new byte[bytesRequired + 8]; - - // Nitro headers - fatbFrame[0] = 'B'; - fatbFrame[1] = 'T'; - fatbFrame[2] = 'A'; - fatbFrame[3] = 'F'; - writeLong(fatbFrame, 4, fatbFrame.length); - - fimgFrame[0] = 'G'; - fimgFrame[1] = 'M'; - fimgFrame[2] = 'I'; - fimgFrame[3] = 'F'; - writeLong(fimgFrame, 4, fimgFrame.length); - int offset = 0; - - writeLong(fatbFrame, 8, narc.files.size()); - for (int i = 0; i < narc.files.size(); i++) { - byte[] file = narc.files.get(i); - int bytesRequiredForFile = (int) (Math.ceil(file.length / 4.0) * 4); - System.arraycopy(file, 0, fimgFrame, offset + 8, file.length); - for (int filler = file.length; filler < bytesRequiredForFile; filler++) { - fimgFrame[offset + 8 + filler] = (byte) 0xFF; - } - writeLong(fatbFrame, 12 + i * 8, offset); - writeLong(fatbFrame, 16 + i * 8, offset + file.length); - offset += bytesRequiredForFile; - } - - // FNTB Frame - int bytesForFNTBFrame = 16; - if (narc.hasFilenames) { - for (String filename : narc.filenames) { - bytesForFNTBFrame += filename.getBytes("US-ASCII").length + 1; - } - } - byte[] fntbFrame = new byte[bytesForFNTBFrame]; - - fntbFrame[0] = 'B'; - fntbFrame[1] = 'T'; - fntbFrame[2] = 'N'; - fntbFrame[3] = 'F'; - writeLong(fntbFrame, 4, fntbFrame.length); - - if (narc.hasFilenames) { - writeLong(fntbFrame, 8, 8); - writeLong(fntbFrame, 12, 0x10000); - int fntbOffset = 16; - for (String filename : narc.filenames) { - byte[] fntbfilename = filename.getBytes("US-ASCII"); - fntbFrame[fntbOffset] = (byte) fntbfilename.length; - System.arraycopy(fntbfilename, 0, fntbFrame, fntbOffset + 1, - fntbfilename.length); - fntbOffset += 1 + fntbfilename.length; - } - } else { - writeLong(fntbFrame, 8, 4); - writeLong(fntbFrame, 12, 0x10000); - } - - // Now for the actual Nitro file - int nitrolength = 16 + fatbFrame.length + fntbFrame.length - + fimgFrame.length; - byte[] nitroFile = new byte[nitrolength]; - nitroFile[0] = 'N'; - nitroFile[1] = 'A'; - nitroFile[2] = 'R'; - nitroFile[3] = 'C'; - writeWord(nitroFile, 4, 0xFFFE); - writeWord(nitroFile, 6, 0x0100); - writeLong(nitroFile, 8, nitrolength); - writeWord(nitroFile, 12, 0x10); - writeWord(nitroFile, 14, 3); - System.arraycopy(fatbFrame, 0, nitroFile, 16, fatbFrame.length); - System.arraycopy(fntbFrame, 0, nitroFile, 16 + fatbFrame.length, - fntbFrame.length); - System.arraycopy(fimgFrame, 0, nitroFile, 16 + fatbFrame.length - + fntbFrame.length, fimgFrame.length); - this.writeFile(subpath, nitroFile); - } - - private Map<String, byte[]> readNitroFrames(String filename) - throws IOException { - byte[] wholeFile = this.readFile(filename); - - // Read the number of frames - int frameCount = readWord(wholeFile, 0x0E); - - // each frame - int offset = 0x10; - Map<String, byte[]> frames = new TreeMap<String, byte[]>(); - for (int i = 0; i < frameCount; i++) { - byte[] magic = new byte[] { wholeFile[offset + 3], - wholeFile[offset + 2], wholeFile[offset + 1], - wholeFile[offset] }; - String magicS = new String(magic, "US-ASCII"); - - int frame_size = readLong(wholeFile, offset + 4); - // Patch for BB/VW and other DS hacks which don't update - // the size of their expanded NARCs correctly - if (i == frameCount - 1 && offset + frame_size < wholeFile.length) { - frame_size = wholeFile.length - offset; - } - byte[] frame = new byte[frame_size - 8]; - System.arraycopy(wholeFile, offset + 8, frame, 0, frame_size - 8); - frames.put(magicS, frame); - offset += frame_size; - } - return frames; - } - - protected static String getROMCodeFromFile(String filename) { - try { - FileInputStream fis = new FileInputStream(filename); - fis.skip(0x0C); - byte[] sig = new byte[4]; - fis.read(sig); - fis.close(); - String ndsCode = new String(sig, "US-ASCII"); - return ndsCode; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - protected int readWord(byte[] data, int offset) { - return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8); - } - - protected int readLong(byte[] data, int offset) { - return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) - | ((data[offset + 2] & 0xFF) << 16) - | ((data[offset + 3] & 0xFF) << 24); - } - - protected int readRelativePointer(byte[] data, int offset) { - return readLong(data, offset) + offset + 4; - } - - protected void writeWord(byte[] data, int offset, int value) { - data[offset] = (byte) (value & 0xFF); - data[offset + 1] = (byte) ((value >> 8) & 0xFF); - } - - protected void writeLong(byte[] data, int offset, int value) { - data[offset] = (byte) (value & 0xFF); - data[offset + 1] = (byte) ((value >> 8) & 0xFF); - data[offset + 2] = (byte) ((value >> 16) & 0xFF); - data[offset + 3] = (byte) ((value >> 24) & 0xFF); - } - - protected void writeRelativePointer(byte[] data, int offset, int pointer) { - int relPointer = pointer - (offset + 4); - writeLong(data, offset, relPointer); - } - - protected byte[] readFile(String location) throws IOException { - return baseRom.getFile(location); - } - - protected void writeFile(String location, byte[] data) throws IOException { - writeFile(location, data, 0, data.length); - } - - protected void writeFile(String location, byte[] data, int offset, - int length) throws IOException { - if (offset != 0 || length != data.length) { - byte[] newData = new byte[length]; - System.arraycopy(data, offset, newData, 0, length); - data = newData; - } - baseRom.writeFile(location, data); - } - - protected byte[] readARM9() throws IOException { - return baseRom.getARM9(); - } - - protected void writeARM9(byte[] data) throws IOException { - baseRom.writeARM9(data); - } - - protected byte[] readOverlay(int number) throws IOException { - return baseRom.getOverlay(number); - } - - protected void writeOverlay(int number, byte[] data) throws IOException { - baseRom.writeOverlay(number, data); - } - - protected void readByteIntoFlags(byte[] data, boolean[] flags, - int offsetIntoFlags, int offsetIntoData) { - int thisByte = data[offsetIntoData] & 0xFF; - for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { - flags[offsetIntoFlags + i] = ((thisByte >> i) & 0x01) == 0x01; - } - } - - protected byte getByteFromFlags(boolean[] flags, int offsetIntoFlags) { - int thisByte = 0; - for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { - thisByte |= (flags[offsetIntoFlags + i] ? 1 : 0) << i; - } - return (byte) thisByte; - } - - protected int typeTMPaletteNumber(Type t) { - if (t == null) { - return 411; // CURSE - } - switch (t) { - case FIGHTING: - return 398; - case DRAGON: - return 399; - case WATER: - return 400; - case PSYCHIC: - return 401; - case NORMAL: - return 402; - case POISON: - return 403; - case ICE: - return 404; - case GRASS: - return 405; - case FIRE: - return 406; - case DARK: - return 407; - case STEEL: - return 408; - case ELECTRIC: - return 409; - case GROUND: - return 410; - case GHOST: - default: - return 411; // for CURSE - case ROCK: - return 412; - case FLYING: - return 413; - case BUG: - return 610; - } - } + protected String dataFolder; + private NDSRom baseRom; + private String loadedFN; + + public AbstractDSRomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + protected abstract boolean detectNDSRom(String ndsCode); + + @Override + public boolean loadRom(String filename) { + if (!this.detectNDSRom(getROMCodeFromFile(filename))) { + return false; + } + // Load inner rom + try { + baseRom = new NDSRom(filename); + } catch (IOException e) { + throw new RuntimeException(e); + } + loadedFN = filename; + loadedROM(baseRom.getCode()); + return true; + } + + @Override + public String loadedFilename() { + return loadedFN; + } + + protected byte[] get3byte(int amount) { + byte[] ret = new byte[3]; + ret[0] = (byte) (amount & 0xFF); + ret[1] = (byte) ((amount >> 8) & 0xFF); + ret[2] = (byte) ((amount >> 16) & 0xFF); + return ret; + } + + protected abstract void loadedROM(String romCode); + + protected abstract void savingROM(); + + @Override + public boolean saveRom(String filename) { + savingROM(); + try { + baseRom.saveTo(filename); + } catch (Exception e) { + throw new RuntimeException(e); + } + return true; + } + + public void closeInnerRom() throws IOException { + baseRom.closeROM(); + } + + @Override + public boolean canChangeStaticPokemon() { + return false; + } + + @Override + public boolean hasPhysicalSpecialSplit() { + // Default value for Gen4+. + // Handlers can override again in case of ROM hacks etc. + return true; + } + + public NARCContents readNARC(String subpath) throws IOException { + Map<String, byte[]> frames = readNitroFrames(subpath); + if (!frames.containsKey("FATB") || !frames.containsKey("FNTB") || !frames.containsKey("FIMG")) { + System.err.println("Not a valid narc file"); + return null; + } + // File contents + NARCContents narc = new NARCContents(); + byte[] fatbframe = frames.get("FATB"); + byte[] fimgframe = frames.get("FIMG"); + int fileCount = readLong(fatbframe, 0); + for (int i = 0; i < fileCount; i++) { + int startOffset = readLong(fatbframe, 4 + i * 8); + int endOffset = readLong(fatbframe, 8 + i * 8); + int length = (endOffset - startOffset); + byte[] thisFile = new byte[length]; + try { + System.arraycopy(fimgframe, startOffset, thisFile, 0, length); + } catch (ArrayIndexOutOfBoundsException ex) { + throw new RuntimeException(ex); + } + narc.files.add(thisFile); + } + // Filenames? + byte[] fntbframe = frames.get("FNTB"); + int unk1 = readLong(fntbframe, 0); + if (unk1 == 8) { + // Filenames exist + narc.hasFilenames = true; + int offset = 8; + for (int i = 0; i < fileCount; i++) { + int fnLength = (fntbframe[offset] & 0xFF); + offset++; + byte[] filenameBA = new byte[fnLength]; + System.arraycopy(fntbframe, offset, filenameBA, 0, fnLength); + String filename = new String(filenameBA, "US-ASCII"); + narc.filenames.add(filename); + } + } else { + narc.hasFilenames = false; + for (int i = 0; i < fileCount; i++) { + narc.filenames.add(null); + } + } + return narc; + } + + public void writeNARC(String subpath, NARCContents narc) throws IOException { + // Get bytes required for FIMG frame + int bytesRequired = 0; + for (byte[] file : narc.files) { + bytesRequired += Math.ceil(file.length / 4.0) * 4; + } + // FIMG frame & FATB frame build + + // 4 for numentries, 8*size for entries, 8 for nitro header + byte[] fatbFrame = new byte[4 + narc.files.size() * 8 + 8]; + // bytesRequired + 8 for nitro header + byte[] fimgFrame = new byte[bytesRequired + 8]; + + // Nitro headers + fatbFrame[0] = 'B'; + fatbFrame[1] = 'T'; + fatbFrame[2] = 'A'; + fatbFrame[3] = 'F'; + writeLong(fatbFrame, 4, fatbFrame.length); + + fimgFrame[0] = 'G'; + fimgFrame[1] = 'M'; + fimgFrame[2] = 'I'; + fimgFrame[3] = 'F'; + writeLong(fimgFrame, 4, fimgFrame.length); + int offset = 0; + + writeLong(fatbFrame, 8, narc.files.size()); + for (int i = 0; i < narc.files.size(); i++) { + byte[] file = narc.files.get(i); + int bytesRequiredForFile = (int) (Math.ceil(file.length / 4.0) * 4); + System.arraycopy(file, 0, fimgFrame, offset + 8, file.length); + for (int filler = file.length; filler < bytesRequiredForFile; filler++) { + fimgFrame[offset + 8 + filler] = (byte) 0xFF; + } + writeLong(fatbFrame, 12 + i * 8, offset); + writeLong(fatbFrame, 16 + i * 8, offset + file.length); + offset += bytesRequiredForFile; + } + + // FNTB Frame + int bytesForFNTBFrame = 16; + if (narc.hasFilenames) { + for (String filename : narc.filenames) { + bytesForFNTBFrame += filename.getBytes("US-ASCII").length + 1; + } + } + byte[] fntbFrame = new byte[bytesForFNTBFrame]; + + fntbFrame[0] = 'B'; + fntbFrame[1] = 'T'; + fntbFrame[2] = 'N'; + fntbFrame[3] = 'F'; + writeLong(fntbFrame, 4, fntbFrame.length); + + if (narc.hasFilenames) { + writeLong(fntbFrame, 8, 8); + writeLong(fntbFrame, 12, 0x10000); + int fntbOffset = 16; + for (String filename : narc.filenames) { + byte[] fntbfilename = filename.getBytes("US-ASCII"); + fntbFrame[fntbOffset] = (byte) fntbfilename.length; + System.arraycopy(fntbfilename, 0, fntbFrame, fntbOffset + 1, fntbfilename.length); + fntbOffset += 1 + fntbfilename.length; + } + } else { + writeLong(fntbFrame, 8, 4); + writeLong(fntbFrame, 12, 0x10000); + } + + // Now for the actual Nitro file + int nitrolength = 16 + fatbFrame.length + fntbFrame.length + fimgFrame.length; + byte[] nitroFile = new byte[nitrolength]; + nitroFile[0] = 'N'; + nitroFile[1] = 'A'; + nitroFile[2] = 'R'; + nitroFile[3] = 'C'; + writeWord(nitroFile, 4, 0xFFFE); + writeWord(nitroFile, 6, 0x0100); + writeLong(nitroFile, 8, nitrolength); + writeWord(nitroFile, 12, 0x10); + writeWord(nitroFile, 14, 3); + System.arraycopy(fatbFrame, 0, nitroFile, 16, fatbFrame.length); + System.arraycopy(fntbFrame, 0, nitroFile, 16 + fatbFrame.length, fntbFrame.length); + System.arraycopy(fimgFrame, 0, nitroFile, 16 + fatbFrame.length + fntbFrame.length, fimgFrame.length); + this.writeFile(subpath, nitroFile); + } + + private Map<String, byte[]> readNitroFrames(String filename) throws IOException { + byte[] wholeFile = this.readFile(filename); + + // Read the number of frames + int frameCount = readWord(wholeFile, 0x0E); + + // each frame + int offset = 0x10; + Map<String, byte[]> frames = new TreeMap<String, byte[]>(); + for (int i = 0; i < frameCount; i++) { + byte[] magic = new byte[] { wholeFile[offset + 3], wholeFile[offset + 2], wholeFile[offset + 1], + wholeFile[offset] }; + String magicS = new String(magic, "US-ASCII"); + + int frame_size = readLong(wholeFile, offset + 4); + // Patch for BB/VW and other DS hacks which don't update + // the size of their expanded NARCs correctly + if (i == frameCount - 1 && offset + frame_size < wholeFile.length) { + frame_size = wholeFile.length - offset; + } + byte[] frame = new byte[frame_size - 8]; + System.arraycopy(wholeFile, offset + 8, frame, 0, frame_size - 8); + frames.put(magicS, frame); + offset += frame_size; + } + return frames; + } + + protected static String getROMCodeFromFile(String filename) { + try { + FileInputStream fis = new FileInputStream(filename); + fis.skip(0x0C); + byte[] sig = new byte[4]; + fis.read(sig); + fis.close(); + String ndsCode = new String(sig, "US-ASCII"); + return ndsCode; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected int readWord(byte[] data, int offset) { + return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8); + } + + protected int readLong(byte[] data, int offset) { + return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) | ((data[offset + 2] & 0xFF) << 16) + | ((data[offset + 3] & 0xFF) << 24); + } + + protected int readRelativePointer(byte[] data, int offset) { + return readLong(data, offset) + offset + 4; + } + + protected void writeWord(byte[] data, int offset, int value) { + data[offset] = (byte) (value & 0xFF); + data[offset + 1] = (byte) ((value >> 8) & 0xFF); + } + + protected void writeLong(byte[] data, int offset, int value) { + data[offset] = (byte) (value & 0xFF); + data[offset + 1] = (byte) ((value >> 8) & 0xFF); + data[offset + 2] = (byte) ((value >> 16) & 0xFF); + data[offset + 3] = (byte) ((value >> 24) & 0xFF); + } + + protected void writeRelativePointer(byte[] data, int offset, int pointer) { + int relPointer = pointer - (offset + 4); + writeLong(data, offset, relPointer); + } + + protected byte[] readFile(String location) throws IOException { + return baseRom.getFile(location); + } + + protected void writeFile(String location, byte[] data) throws IOException { + writeFile(location, data, 0, data.length); + } + + protected void writeFile(String location, byte[] data, int offset, int length) throws IOException { + if (offset != 0 || length != data.length) { + byte[] newData = new byte[length]; + System.arraycopy(data, offset, newData, 0, length); + data = newData; + } + baseRom.writeFile(location, data); + } + + protected byte[] readARM9() throws IOException { + return baseRom.getARM9(); + } + + protected void writeARM9(byte[] data) throws IOException { + baseRom.writeARM9(data); + } + + protected byte[] readOverlay(int number) throws IOException { + return baseRom.getOverlay(number); + } + + protected void writeOverlay(int number, byte[] data) throws IOException { + baseRom.writeOverlay(number, data); + } + + protected void readByteIntoFlags(byte[] data, boolean[] flags, int offsetIntoFlags, int offsetIntoData) { + int thisByte = data[offsetIntoData] & 0xFF; + for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { + flags[offsetIntoFlags + i] = ((thisByte >> i) & 0x01) == 0x01; + } + } + + protected byte getByteFromFlags(boolean[] flags, int offsetIntoFlags) { + int thisByte = 0; + for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { + thisByte |= (flags[offsetIntoFlags + i] ? 1 : 0) << i; + } + return (byte) thisByte; + } + + protected int typeTMPaletteNumber(Type t) { + if (t == null) { + return 411; // CURSE + } + switch (t) { + case FIGHTING: + return 398; + case DRAGON: + return 399; + case WATER: + return 400; + case PSYCHIC: + return 401; + case NORMAL: + return 402; + case POISON: + return 403; + case ICE: + return 404; + case GRASS: + return 405; + case FIRE: + return 406; + case DARK: + return 407; + case STEEL: + return 408; + case ELECTRIC: + return 409; + case GROUND: + return 410; + case GHOST: + default: + return 411; // for CURSE + case ROCK: + return 412; + case FLYING: + return 413; + case BUG: + return 610; + } + } } diff --git a/src/com/dabomstew/pkrandom/romhandlers/AbstractGBRomHandler.java b/src/com/dabomstew/pkrandom/romhandlers/AbstractGBRomHandler.java index 0339350..168303d 100755 --- a/src/com/dabomstew/pkrandom/romhandlers/AbstractGBRomHandler.java +++ b/src/com/dabomstew/pkrandom/romhandlers/AbstractGBRomHandler.java @@ -32,128 +32,127 @@ import java.util.Random; public abstract class AbstractGBRomHandler extends AbstractRomHandler { - protected byte[] rom; - private String loadedFN; - - public AbstractGBRomHandler(Random random, PrintStream logStream) { - super(random, logStream); - } - - @Override - public boolean loadRom(String filename) { - byte[] loaded = loadFile(filename); - if (!detectRom(loaded)) { - return false; - } - this.rom = loaded; - loadedFN = filename; - loadedRom(); - return true; - } - - @Override - public String loadedFilename() { - return loadedFN; - } - - @Override - public boolean saveRom(String filename) { - savingRom(); - try { - FileOutputStream fos = new FileOutputStream(filename); - fos.write(rom); - fos.close(); - return true; - } catch (IOException ex) { - return false; - } - } - - @Override - public boolean canChangeStaticPokemon() { - return true; - } - - @Override - public boolean hasPhysicalSpecialSplit() { - // Default value for Gen1-Gen3. - // Handlers can override again in case of ROM hacks etc. - return false; - } - - public abstract boolean detectRom(byte[] rom); - - public abstract void loadedRom(); - - public abstract void savingRom(); - - protected static byte[] loadFile(String filename) { - try { - FileInputStream fis = new FileInputStream(filename); - byte[] file = new byte[fis.available()]; - fis.read(file); - fis.close(); - return file; - } catch (IOException ex) { - return new byte[0]; - } - } - - protected static byte[] loadFilePartial(String filename, int maxBytes) { - try { - FileInputStream fis = new FileInputStream(filename); - byte[] file = new byte[Math.min(maxBytes, fis.available())]; - fis.read(file); - fis.close(); - return file; - } catch (IOException ex) { - return new byte[0]; - } - } - - protected void readByteIntoFlags(boolean[] flags, int offsetIntoFlags, - int offsetIntoROM) { - int thisByte = rom[offsetIntoROM] & 0xFF; - for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { - flags[offsetIntoFlags + i] = ((thisByte >> i) & 0x01) == 0x01; - } - } - - protected byte getByteFromFlags(boolean[] flags, int offsetIntoFlags) { - int thisByte = 0; - for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { - thisByte |= (flags[offsetIntoFlags + i] ? 1 : 0) << i; - } - return (byte) thisByte; - } - - protected int readWord(int offset) { - return readWord(rom, offset); - } - - protected int readWord(byte[] data, int offset) { - return (data[offset] & 0xFF) + ((data[offset + 1] & 0xFF) << 8); - } - - protected void writeWord(int offset, int value) { - writeWord(rom, offset, value); - } - - protected void writeWord(byte[] data, int offset, int value) { - data[offset] = (byte) (value % 0x100); - data[offset + 1] = (byte) ((value / 0x100) % 0x100); - } - - protected boolean matches(byte[] data, int offset, byte[] needle) { - for (int i = 0; i < needle.length; i++) { - if (offset + i >= data.length) { - return false; - } - if (data[offset + i] != needle[i]) { - return false; - } - } - return true; - } + protected byte[] rom; + private String loadedFN; + + public AbstractGBRomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + @Override + public boolean loadRom(String filename) { + byte[] loaded = loadFile(filename); + if (!detectRom(loaded)) { + return false; + } + this.rom = loaded; + loadedFN = filename; + loadedRom(); + return true; + } + + @Override + public String loadedFilename() { + return loadedFN; + } + + @Override + public boolean saveRom(String filename) { + savingRom(); + try { + FileOutputStream fos = new FileOutputStream(filename); + fos.write(rom); + fos.close(); + return true; + } catch (IOException ex) { + return false; + } + } + + @Override + public boolean canChangeStaticPokemon() { + return true; + } + + @Override + public boolean hasPhysicalSpecialSplit() { + // Default value for Gen1-Gen3. + // Handlers can override again in case of ROM hacks etc. + return false; + } + + public abstract boolean detectRom(byte[] rom); + + public abstract void loadedRom(); + + public abstract void savingRom(); + + protected static byte[] loadFile(String filename) { + try { + FileInputStream fis = new FileInputStream(filename); + byte[] file = new byte[fis.available()]; + fis.read(file); + fis.close(); + return file; + } catch (IOException ex) { + return new byte[0]; + } + } + + protected static byte[] loadFilePartial(String filename, int maxBytes) { + try { + FileInputStream fis = new FileInputStream(filename); + byte[] file = new byte[Math.min(maxBytes, fis.available())]; + fis.read(file); + fis.close(); + return file; + } catch (IOException ex) { + return new byte[0]; + } + } + + protected void readByteIntoFlags(boolean[] flags, int offsetIntoFlags, int offsetIntoROM) { + int thisByte = rom[offsetIntoROM] & 0xFF; + for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { + flags[offsetIntoFlags + i] = ((thisByte >> i) & 0x01) == 0x01; + } + } + + protected byte getByteFromFlags(boolean[] flags, int offsetIntoFlags) { + int thisByte = 0; + for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) { + thisByte |= (flags[offsetIntoFlags + i] ? 1 : 0) << i; + } + return (byte) thisByte; + } + + protected int readWord(int offset) { + return readWord(rom, offset); + } + + protected int readWord(byte[] data, int offset) { + return (data[offset] & 0xFF) + ((data[offset + 1] & 0xFF) << 8); + } + + protected void writeWord(int offset, int value) { + writeWord(rom, offset, value); + } + + protected void writeWord(byte[] data, int offset, int value) { + data[offset] = (byte) (value % 0x100); + data[offset + 1] = (byte) ((value / 0x100) % 0x100); + } + + protected boolean matches(byte[] data, int offset, byte[] needle) { + for (int i = 0; i < needle.length; i++) { + if (offset + i >= data.length) { + return false; + } + if (data[offset + i] != needle[i]) { + return false; + } + } + return true; + } } diff --git a/src/com/dabomstew/pkrandom/romhandlers/AbstractRomHandler.java b/src/com/dabomstew/pkrandom/romhandlers/AbstractRomHandler.java index 0081063..2e2b3e3 100755 --- a/src/com/dabomstew/pkrandom/romhandlers/AbstractRomHandler.java +++ b/src/com/dabomstew/pkrandom/romhandlers/AbstractRomHandler.java @@ -67,3422 +67,3249 @@ import com.dabomstew.pkrandom.pokemon.Type; public abstract class AbstractRomHandler implements RomHandler { - private static final String tnamesFile = "trainernames.txt"; - private static final String tclassesFile = "trainerclasses.txt"; - private static final String nnamesFile = "nicknames.txt"; - - private boolean restrictionsSet; - protected List<Pokemon> mainPokemonList; - protected List<Pokemon> noLegendaryList, onlyLegendaryList; - protected final Random random; - protected PrintStream logStream; - - /* Constructor */ - - public AbstractRomHandler(Random random, PrintStream logStream) { - this.random = random; - this.logStream = logStream; - } - - /* Public Methods, implemented here for all gens */ - - protected void checkPokemonRestrictions() { - if (!restrictionsSet) { - setPokemonPool(null); - } - } - - public void setPokemonPool(GenRestrictions restrictions) { - restrictionsSet = true; - mainPokemonList = this.allPokemonWithoutNull(); - if (restrictions != null) { - mainPokemonList = new ArrayList<Pokemon>(); - List<Pokemon> allPokemon = this.getPokemon(); - - if (restrictions.allow_gen1) { - addPokesFromRange(mainPokemonList, allPokemon, 1, 151); - if (restrictions.assoc_g1_g2 && allPokemon.size() > 251) { - addEvosFromRange(mainPokemonList, 1, 151, 152, 251); - } - if (restrictions.assoc_g1_g4 && allPokemon.size() > 493) { - addEvosFromRange(mainPokemonList, 1, 151, 387, 493); - } - } - - if (restrictions.allow_gen2 && allPokemon.size() > 251) { - addPokesFromRange(mainPokemonList, allPokemon, 152, 251); - if (restrictions.assoc_g2_g1) { - addEvosFromRange(mainPokemonList, 152, 251, 1, 151); - } - if (restrictions.assoc_g2_g3 && allPokemon.size() > 386) { - addEvosFromRange(mainPokemonList, 152, 251, 252, 386); - } - if (restrictions.assoc_g2_g4 && allPokemon.size() > 493) { - addEvosFromRange(mainPokemonList, 152, 251, 387, 493); - } - } - - if (restrictions.allow_gen3 && allPokemon.size() > 386) { - addPokesFromRange(mainPokemonList, allPokemon, 252, 386); - if (restrictions.assoc_g3_g2) { - addEvosFromRange(mainPokemonList, 252, 386, 152, 251); - } - if (restrictions.assoc_g3_g4 && allPokemon.size() > 493) { - addEvosFromRange(mainPokemonList, 252, 386, 387, 493); - } - } - - if (restrictions.allow_gen4 && allPokemon.size() > 493) { - addPokesFromRange(mainPokemonList, allPokemon, 387, 493); - if (restrictions.assoc_g4_g1) { - addEvosFromRange(mainPokemonList, 387, 493, 1, 151); - } - if (restrictions.assoc_g4_g2) { - addEvosFromRange(mainPokemonList, 387, 493, 152, 251); - } - if (restrictions.assoc_g4_g3) { - addEvosFromRange(mainPokemonList, 387, 493, 252, 386); - } - } - - if (restrictions.allow_gen5 && allPokemon.size() > 649) { - addPokesFromRange(mainPokemonList, allPokemon, 494, 649); - } - } - - noLegendaryList = new ArrayList<Pokemon>(); - onlyLegendaryList = new ArrayList<Pokemon>(); - - for (Pokemon p : mainPokemonList) { - if (p.isLegendary()) { - onlyLegendaryList.add(p); - } else { - noLegendaryList.add(p); - } - } - } - - private void addPokesFromRange(List<Pokemon> pokemonPool, - List<Pokemon> allPokemon, int range_min, int range_max) { - for (int i = range_min; i <= range_max; i++) { - if (!pokemonPool.contains(allPokemon.get(i))) { - pokemonPool.add(allPokemon.get(i)); - } - } - } - - private void addEvosFromRange(List<Pokemon> pokemonPool, int first_min, - int first_max, int second_min, int second_max) { - Set<Pokemon> newPokemon = new TreeSet<Pokemon>(); - for (Pokemon pk : pokemonPool) { - if (pk.number >= first_min && pk.number <= first_max) { - for (Evolution ev : pk.evolutionsFrom) { - if (ev.to.number >= second_min - && ev.to.number <= second_max) { - if (!pokemonPool.contains(ev.to) - && !newPokemon.contains(ev.to)) { - newPokemon.add(ev.to); - } - } - } - - for (Evolution ev : pk.evolutionsTo) { - if (ev.from.number >= second_min - && ev.from.number <= second_max) { - if (!pokemonPool.contains(ev.from) - && !newPokemon.contains(ev.from)) { - newPokemon.add(ev.from); - } - } - } - } - } - - pokemonPool.addAll(newPokemon); - } - - @Override - public void randomizePokemonStats(boolean evolutionSanity) { - List<Pokemon> allPokes = this.getPokemon(); - - if (evolutionSanity) { - for (Pokemon pk : allPokes) { - if (pk != null) { - pk.temporaryFlag = false; - } - } - // Spread stats up MOST evolutions. - Set<Pokemon> dontCopyPokes = RomFunctions - .getBasicOrNoCopyPokemon(this); - - for (Pokemon pk : dontCopyPokes) { - pk.randomizeStatsWithinBST(this.random); - pk.temporaryFlag = true; - } - - // go "up" evolutions looking for pre-evos to do first - for (Pokemon pk : allPokes) { - if (pk != null && !pk.temporaryFlag) { - // Non-randomized pokes at this point must have - // a linear chain of single evolutions down to - // a randomized poke. - Stack<Evolution> currentStack = new Stack<Evolution>(); - Evolution ev = pk.evolutionsTo.get(0); - while (!ev.from.temporaryFlag) { - currentStack.push(ev); - ev = ev.from.evolutionsTo.get(0); - } - - // Now "ev" is set to an evolution from a randomized Pokemon - // to a non-randomized. - // Carry stats up, then do the same for everything left on - // the stack. - ev.to.copyRandomizedStatsUpEvolution(ev.from); - ev.to.temporaryFlag = true; - while (!currentStack.isEmpty()) { - ev = currentStack.pop(); - ev.to.copyRandomizedStatsUpEvolution(ev.from); - ev.to.temporaryFlag = true; - } - } - } - } else { - for (Pokemon pk : allPokes) { - if (pk != null) { - pk.randomizeStatsWithinBST(this.random); - } - } - } - - } - - protected void applyCamelCaseNames() { - List<Pokemon> pokes = getPokemon(); - for (Pokemon pkmn : pokes) { - if (pkmn == null) { - continue; - } - pkmn.name = RomFunctions.camelCase(pkmn.name); - } - - } - - @Override - public void minimumCatchRate(int rateNonLegendary, int rateLegendary) { - List<Pokemon> pokes = getPokemon(); - for (Pokemon pkmn : pokes) { - if (pkmn == null) { - continue; - } - int minCatchRate = pkmn.isLegendary() ? rateLegendary - : rateNonLegendary; - pkmn.catchRate = Math.max(pkmn.catchRate, minCatchRate); - } - - } - - @Override - public void standardizeEXPCurves() { - List<Pokemon> pokes = getPokemon(); - for (Pokemon pkmn : pokes) { - if (pkmn == null) { - continue; - } - pkmn.growthCurve = pkmn.isLegendary() ? ExpCurve.SLOW - : ExpCurve.MEDIUM_FAST; - } - } - - @Override - public void randomizePokemonTypes(boolean evolutionSanity) { - List<Pokemon> allPokes = this.getPokemon(); - if (evolutionSanity) { - // Type randomization with evolution sanity - for (Pokemon pk : allPokes) { - if (pk != null) { - pk.temporaryFlag = false; - } - } - - // Step 1: Basic or Excluded From Copying Pokemon - // A Basic/EFC pokemon has a 35% chance of a second type if it has - // an evolution that copies type/stats, a 50% chance otherwise - Set<Pokemon> dontCopyPokes = RomFunctions - .getBasicOrNoCopyPokemon(this); - - for (Pokemon pk : dontCopyPokes) { - pk.primaryType = randomType(); - pk.secondaryType = null; - if (pk.evolutionsFrom.size() == 1 - && pk.evolutionsFrom.get(0).carryStats) { - if (this.random.nextDouble() < 0.35) { - pk.secondaryType = randomType(); - while (pk.secondaryType == pk.primaryType) { - pk.secondaryType = randomType(); - } - } - } else { - if (this.random.nextDouble() < 0.5) { - pk.secondaryType = randomType(); - while (pk.secondaryType == pk.primaryType) { - pk.secondaryType = randomType(); - } - } - } - pk.temporaryFlag = true; - } - - // go "up" evolutions looking for pre-evos to do first - Set<Pokemon> middleEvos = RomFunctions.getMiddleEvolutions(this); - for (Pokemon pk : allPokes) { - if (pk != null && !pk.temporaryFlag) { - // Non-randomized pokes at this point must have - // a linear chain of single evolutions down to - // a randomized poke. - Stack<Evolution> currentStack = new Stack<Evolution>(); - Evolution ev = pk.evolutionsTo.get(0); - while (!ev.from.temporaryFlag) { - currentStack.push(ev); - ev = ev.from.evolutionsTo.get(0); - } - - // Now "ev" is set to an evolution from a randomized Pokemon - // to a non-randomized. - // Carry types up, add the chance to add secondary, then - // continue up the stack as required. - while (true) { - Pokemon to = ev.to; - Pokemon from = ev.from; - to.primaryType = from.primaryType; - to.secondaryType = from.secondaryType; - - if (to.secondaryType == null) { - double chance = middleEvos.contains(to) ? 0.15 - : 0.25; - if (this.random.nextDouble() < chance) { - to.secondaryType = randomType(); - while (to.secondaryType == to.primaryType) { - to.secondaryType = randomType(); - } - } - } - - to.temporaryFlag = true; - - if (currentStack.isEmpty()) { - break; - } else { - ev = currentStack.pop(); - } - } - } - } - } else { - // Entirely random types - for (Pokemon pkmn : allPokes) { - if (pkmn != null) { - pkmn.primaryType = randomType(); - pkmn.secondaryType = null; - if (this.random.nextDouble() < 0.5) { - pkmn.secondaryType = randomType(); - while (pkmn.secondaryType == pkmn.primaryType) { - pkmn.secondaryType = randomType(); - } - } - } - } - } - } - - private static final int WONDER_GUARD_INDEX = 25; - - @Override - public void randomizeAbilities(boolean allowWonderGuard) { - // Abilities don't exist in some games... - if (this.abilitiesPerPokemon() == 0) { - return; - } - - // Deal with "natural" abilities first regardless - List<Pokemon> allPokes = this.getPokemon(); - int maxAbility = this.highestAbilityIndex(); - for (Pokemon pk : allPokes) { - if (pk == null) { - continue; - } - - // Wonder Guard? - if (pk.ability1 != WONDER_GUARD_INDEX - && pk.ability2 != WONDER_GUARD_INDEX - && pk.ability3 != WONDER_GUARD_INDEX) { - // Pick first ability - pk.ability1 = this.random.nextInt(maxAbility) + 1; - // Wonder guard block - if (!allowWonderGuard) { - while (pk.ability1 == WONDER_GUARD_INDEX) { - pk.ability1 = this.random.nextInt(maxAbility) + 1; - } - } - - // Second ability? - if (this.random.nextDouble() < 0.5) { - // Yes, second ability - pk.ability2 = this.random.nextInt(maxAbility) + 1; - // Wonder guard? Also block first ability from reappearing - if (allowWonderGuard) { - while (pk.ability2 == pk.ability1) { - pk.ability2 = this.random.nextInt(maxAbility) + 1; - } - } else { - while (pk.ability2 == WONDER_GUARD_INDEX - || pk.ability2 == pk.ability1) { - pk.ability2 = this.random.nextInt(maxAbility) + 1; - } - } - } else { - // Nope - pk.ability2 = 0; - } - } - } - - // DW Abilities? - if (this.abilitiesPerPokemon() == 3) { - // Give a random DW ability to every Pokemon - for (Pokemon pk : allPokes) { - if (pk == null) { - continue; - } - if (pk.ability1 != WONDER_GUARD_INDEX - && pk.ability2 != WONDER_GUARD_INDEX - && pk.ability3 != WONDER_GUARD_INDEX) { - pk.ability3 = this.random.nextInt(maxAbility) + 1; - // Wonder guard? Also block other abilities from reappearing - if (allowWonderGuard) { - while (pk.ability3 == pk.ability1 - || pk.ability3 == pk.ability2) { - pk.ability3 = this.random.nextInt(maxAbility) + 1; - } - } else { - while (pk.ability3 == WONDER_GUARD_INDEX - || pk.ability3 == pk.ability1 - || pk.ability3 == pk.ability2) { - pk.ability3 = this.random.nextInt(maxAbility) + 1; - } - } - } - } - } - } - - public Pokemon randomPokemon() { - checkPokemonRestrictions(); - return mainPokemonList.get(this.random.nextInt(mainPokemonList.size())); - } - - @Override - public Pokemon randomNonLegendaryPokemon() { - checkPokemonRestrictions(); - return noLegendaryList.get(this.random.nextInt(noLegendaryList.size())); - } - - @Override - public Pokemon randomLegendaryPokemon() { - checkPokemonRestrictions(); - return onlyLegendaryList.get(this.random.nextInt(onlyLegendaryList - .size())); - } - - private List<Pokemon> twoEvoPokes; - - @Override - public Pokemon random2EvosPokemon() { - if (twoEvoPokes == null) { - // Prepare the list - twoEvoPokes = new ArrayList<Pokemon>(); - List<Pokemon> allPokes = this.getPokemon(); - for (Pokemon pk : allPokes) { - if (pk != null && pk.evolutionsTo.size() == 0 - && pk.evolutionsFrom.size() > 0) { - // Potential candidate - for (Evolution ev : pk.evolutionsFrom) { - // If any of the targets here evolve, the original - // Pokemon has 2+ stages. - if (ev.to.evolutionsFrom.size() > 0) { - twoEvoPokes.add(pk); - break; - } - } - } - } - } - return twoEvoPokes.get(this.random.nextInt(twoEvoPokes.size())); - } - - @Override - public void randomEncounters(boolean useTimeOfDay, boolean catchEmAll, - boolean typeThemed, boolean usePowerLevels, boolean noLegendaries) { - checkPokemonRestrictions(); - List<EncounterSet> currentEncounters = this.getEncounters(useTimeOfDay); - List<Pokemon> banned = this.bannedForWildEncounters(); - // Assume EITHER catch em all OR type themed OR match strength for now - if (catchEmAll) { - - List<Pokemon> allPokes = noLegendaries ? new ArrayList<Pokemon>( - noLegendaryList) : new ArrayList<Pokemon>(mainPokemonList); - allPokes.removeAll(banned); - for (EncounterSet area : currentEncounters) { - List<Pokemon> pickablePokemon = allPokes; - if (area.bannedPokemon.size() > 0) { - pickablePokemon = new ArrayList<Pokemon>(allPokes); - pickablePokemon.removeAll(area.bannedPokemon); - } - for (Encounter enc : area.encounters) { - // Pick a random pokemon - if (pickablePokemon.size() == 0) { - // Only banned pokes are left, ignore them and pick - // something else for now. - List<Pokemon> tempPickable = noLegendaries ? new ArrayList<Pokemon>( - noLegendaryList) : new ArrayList<Pokemon>( - mainPokemonList); - tempPickable.removeAll(banned); - tempPickable.removeAll(area.bannedPokemon); - if (tempPickable.size() == 0) { - throw new RuntimeException( - "ERROR: Couldn't replace a Pokemon!"); - } - int picked = this.random.nextInt(tempPickable.size()); - enc.pokemon = tempPickable.get(picked); - } else { - // Picked this Pokemon, remove it - int picked = this.random - .nextInt(pickablePokemon.size()); - enc.pokemon = pickablePokemon.get(picked); - pickablePokemon.remove(picked); - if (allPokes != pickablePokemon) { - allPokes.remove(enc.pokemon); - } - if (allPokes.size() == 0) { - // Start again - allPokes.addAll(noLegendaries ? noLegendaryList - : mainPokemonList); - allPokes.removeAll(banned); - if (pickablePokemon != allPokes) { - pickablePokemon.addAll(allPokes); - pickablePokemon.removeAll(area.bannedPokemon); - } - } - } - } - } - } else if (typeThemed) { - Map<Type, List<Pokemon>> cachedPokeLists = new TreeMap<Type, List<Pokemon>>(); - for (EncounterSet area : currentEncounters) { - List<Pokemon> possiblePokemon = null; - int iterLoops = 0; - while (possiblePokemon == null && iterLoops < 10000) { - Type areaTheme = randomType(); - if (!cachedPokeLists.containsKey(areaTheme)) { - List<Pokemon> pType = pokemonOfType(areaTheme, - noLegendaries); - pType.removeAll(banned); - cachedPokeLists.put(areaTheme, pType); - } - possiblePokemon = cachedPokeLists.get(areaTheme); - if (area.bannedPokemon.size() > 0) { - possiblePokemon = new ArrayList<Pokemon>( - possiblePokemon); - possiblePokemon.removeAll(area.bannedPokemon); - } - if (possiblePokemon.size() == 0) { - // Can't use this type for this area - possiblePokemon = null; - } - iterLoops++; - } - if (possiblePokemon == null) { - throw new RuntimeException( - "Could not randomize an area in a reasonable amount of attempts."); - } - for (Encounter enc : area.encounters) { - // Pick a random themed pokemon - enc.pokemon = possiblePokemon.get(this.random - .nextInt(possiblePokemon.size())); - } - } - } else if (usePowerLevels) { - List<Pokemon> allowedPokes = noLegendaries ? new ArrayList<Pokemon>( - noLegendaryList) : new ArrayList<Pokemon>(mainPokemonList); - allowedPokes.removeAll(banned); - for (EncounterSet area : currentEncounters) { - List<Pokemon> localAllowed = allowedPokes; - if (area.bannedPokemon.size() > 0) { - localAllowed = new ArrayList<Pokemon>(allowedPokes); - localAllowed.removeAll(area.bannedPokemon); - } - for (Encounter enc : area.encounters) { - enc.pokemon = pickWildPowerLvlReplacement(localAllowed, - enc.pokemon, false, null); - } - } - } else { - // Entirely random - for (EncounterSet area : currentEncounters) { - for (Encounter enc : area.encounters) { - enc.pokemon = noLegendaries ? randomNonLegendaryPokemon() - : randomPokemon(); - while (banned.contains(enc.pokemon) - || area.bannedPokemon.contains(enc.pokemon)) { - enc.pokemon = noLegendaries ? randomNonLegendaryPokemon() - : randomPokemon(); - } - } - } - } - - setEncounters(useTimeOfDay, currentEncounters); - } - - @Override - public void area1to1Encounters(boolean useTimeOfDay, boolean catchEmAll, - boolean typeThemed, boolean usePowerLevels, boolean noLegendaries) { - checkPokemonRestrictions(); - List<EncounterSet> currentEncounters = this.getEncounters(useTimeOfDay); - List<Pokemon> banned = this.bannedForWildEncounters(); - // Assume EITHER catch em all OR type themed for now - if (catchEmAll) { - List<Pokemon> allPokes = noLegendaries ? new ArrayList<Pokemon>( - noLegendaryList) : new ArrayList<Pokemon>(mainPokemonList); - allPokes.removeAll(banned); - for (EncounterSet area : currentEncounters) { - // Poke-set - Set<Pokemon> inArea = pokemonInArea(area); - // Build area map using catch em all - Map<Pokemon, Pokemon> areaMap = new TreeMap<Pokemon, Pokemon>(); - List<Pokemon> pickablePokemon = allPokes; - if (area.bannedPokemon.size() > 0) { - pickablePokemon = new ArrayList<Pokemon>(allPokes); - pickablePokemon.removeAll(area.bannedPokemon); - } - for (Pokemon areaPk : inArea) { - if (pickablePokemon.size() == 0) { - // No more pickable pokes left, take a random one - List<Pokemon> tempPickable = noLegendaries ? new ArrayList<Pokemon>( - noLegendaryList) : new ArrayList<Pokemon>( - mainPokemonList); - tempPickable.removeAll(banned); - tempPickable.removeAll(area.bannedPokemon); - if (tempPickable.size() == 0) { - throw new RuntimeException( - "ERROR: Couldn't replace a Pokemon!"); - } - int picked = this.random.nextInt(tempPickable.size()); - Pokemon pickedMN = tempPickable.get(picked); - areaMap.put(areaPk, pickedMN); - } else { - int picked = this.random.nextInt(allPokes.size()); - Pokemon pickedMN = allPokes.get(picked); - areaMap.put(areaPk, pickedMN); - pickablePokemon.remove(picked); - if (allPokes != pickablePokemon) { - allPokes.remove(pickedMN); - } - if (allPokes.size() == 0) { - // Start again - allPokes.addAll(noLegendaries ? noLegendaryList - : mainPokemonList); - allPokes.removeAll(banned); - if (pickablePokemon != allPokes) { - pickablePokemon.addAll(allPokes); - pickablePokemon.removeAll(area.bannedPokemon); - } - } - } - } - for (Encounter enc : area.encounters) { - // Apply the map - enc.pokemon = areaMap.get(enc.pokemon); - } - } - } else if (typeThemed) { - Map<Type, List<Pokemon>> cachedPokeLists = new TreeMap<Type, List<Pokemon>>(); - for (EncounterSet area : currentEncounters) { - // Poke-set - Set<Pokemon> inArea = pokemonInArea(area); - List<Pokemon> possiblePokemon = null; - int iterLoops = 0; - while (possiblePokemon == null && iterLoops < 10000) { - Type areaTheme = randomType(); - if (!cachedPokeLists.containsKey(areaTheme)) { - List<Pokemon> pType = pokemonOfType(areaTheme, - noLegendaries); - pType.removeAll(banned); - cachedPokeLists.put(areaTheme, pType); - } - possiblePokemon = cachedPokeLists.get(areaTheme); - if (area.bannedPokemon.size() > 0) { - possiblePokemon = new ArrayList<Pokemon>( - possiblePokemon); - possiblePokemon.removeAll(area.bannedPokemon); - } - if (possiblePokemon.size() < inArea.size()) { - // Can't use this type for this area - possiblePokemon = null; - } - iterLoops++; - } - if (possiblePokemon == null) { - throw new RuntimeException( - "Could not randomize an area in a reasonable amount of attempts."); - } - - // Build area map using type theme. - Map<Pokemon, Pokemon> areaMap = new TreeMap<Pokemon, Pokemon>(); - for (Pokemon areaPk : inArea) { - int picked = this.random.nextInt(possiblePokemon.size()); - Pokemon pickedMN = possiblePokemon.get(picked); - areaMap.put(areaPk, pickedMN); - possiblePokemon.remove(picked); - } - for (Encounter enc : area.encounters) { - // Apply the map - enc.pokemon = areaMap.get(enc.pokemon); - } - } - } else if (usePowerLevels) { - List<Pokemon> allowedPokes = noLegendaries ? new ArrayList<Pokemon>( - noLegendaryList) : new ArrayList<Pokemon>(mainPokemonList); - allowedPokes.removeAll(banned); - for (EncounterSet area : currentEncounters) { - // Poke-set - Set<Pokemon> inArea = pokemonInArea(area); - // Build area map using randoms - Map<Pokemon, Pokemon> areaMap = new TreeMap<Pokemon, Pokemon>(); - List<Pokemon> usedPks = new ArrayList<Pokemon>(); - List<Pokemon> localAllowed = allowedPokes; - if (area.bannedPokemon.size() > 0) { - localAllowed = new ArrayList<Pokemon>(allowedPokes); - localAllowed.removeAll(area.bannedPokemon); - } - for (Pokemon areaPk : inArea) { - Pokemon picked = pickWildPowerLvlReplacement(localAllowed, - areaPk, false, usedPks); - areaMap.put(areaPk, picked); - usedPks.add(picked); - } - for (Encounter enc : area.encounters) { - // Apply the map - enc.pokemon = areaMap.get(enc.pokemon); - } - } - } else { - // Entirely random - for (EncounterSet area : currentEncounters) { - // Poke-set - Set<Pokemon> inArea = pokemonInArea(area); - // Build area map using randoms - Map<Pokemon, Pokemon> areaMap = new TreeMap<Pokemon, Pokemon>(); - for (Pokemon areaPk : inArea) { - Pokemon picked = noLegendaries ? randomNonLegendaryPokemon() - : randomPokemon(); - while (areaMap.containsValue(picked) - || banned.contains(picked) - || area.bannedPokemon.contains(picked)) { - picked = noLegendaries ? randomNonLegendaryPokemon() - : randomPokemon(); - } - areaMap.put(areaPk, picked); - } - for (Encounter enc : area.encounters) { - // Apply the map - enc.pokemon = areaMap.get(enc.pokemon); - } - } - } - - setEncounters(useTimeOfDay, currentEncounters); - - } - - @Override - public void game1to1Encounters(boolean useTimeOfDay, - boolean usePowerLevels, boolean noLegendaries) { - checkPokemonRestrictions(); - // Build the full 1-to-1 map - Map<Pokemon, Pokemon> translateMap = new TreeMap<Pokemon, Pokemon>(); - List<Pokemon> remainingLeft = allPokemonWithoutNull(); - List<Pokemon> remainingRight = noLegendaries ? new ArrayList<Pokemon>( - noLegendaryList) : new ArrayList<Pokemon>(mainPokemonList); - List<Pokemon> banned = this.bannedForWildEncounters(); - // Banned pokemon should be mapped to themselves - for (Pokemon bannedPK : banned) { - translateMap.put(bannedPK, bannedPK); - remainingLeft.remove(bannedPK); - remainingRight.remove(bannedPK); - } - while (remainingLeft.isEmpty() == false) { - if (usePowerLevels) { - int pickedLeft = this.random.nextInt(remainingLeft.size()); - Pokemon pickedLeftP = remainingLeft.remove(pickedLeft); - Pokemon pickedRightP = null; - if (remainingRight.size() == 1) { - // pick this (it may or may not be the same poke) - pickedRightP = remainingRight.get(0); - } else { - // pick on power level with the current one blocked - pickedRightP = pickWildPowerLvlReplacement(remainingRight, - pickedLeftP, true, null); - } - remainingRight.remove(pickedRightP); - translateMap.put(pickedLeftP, pickedRightP); - } else { - int pickedLeft = this.random.nextInt(remainingLeft.size()); - int pickedRight = this.random.nextInt(remainingRight.size()); - Pokemon pickedLeftP = remainingLeft.remove(pickedLeft); - Pokemon pickedRightP = remainingRight.get(pickedRight); - while (pickedLeftP.number == pickedRightP.number - && remainingRight.size() != 1) { - // Reroll for a different pokemon if at all possible - pickedRight = this.random.nextInt(remainingRight.size()); - pickedRightP = remainingRight.get(pickedRight); - } - remainingRight.remove(pickedRight); - translateMap.put(pickedLeftP, pickedRightP); - } - if (remainingRight.size() == 0) { - // restart - remainingRight.addAll(noLegendaries ? noLegendaryList - : mainPokemonList); - remainingRight.removeAll(banned); - } - } - - // Map remaining to themselves just in case - List<Pokemon> allPokes = allPokemonWithoutNull(); - for (Pokemon poke : allPokes) { - if (!translateMap.containsKey(poke)) { - translateMap.put(poke, poke); - } - } - - List<EncounterSet> currentEncounters = this.getEncounters(useTimeOfDay); - - for (EncounterSet area : currentEncounters) { - for (Encounter enc : area.encounters) { - // Apply the map - enc.pokemon = translateMap.get(enc.pokemon); - if (area.bannedPokemon.contains(enc.pokemon)) { - // Ignore the map and put a random non-banned poke - List<Pokemon> tempPickable = noLegendaries ? new ArrayList<Pokemon>( - noLegendaryList) : new ArrayList<Pokemon>( - mainPokemonList); - tempPickable.removeAll(banned); - tempPickable.removeAll(area.bannedPokemon); - if (tempPickable.size() == 0) { - throw new RuntimeException( - "ERROR: Couldn't replace a Pokemon!"); - } - if (usePowerLevels) { - enc.pokemon = pickWildPowerLvlReplacement(tempPickable, - enc.pokemon, false, null); - } else { - int picked = this.random.nextInt(tempPickable.size()); - enc.pokemon = tempPickable.get(picked); - } - } - } - } - - setEncounters(useTimeOfDay, currentEncounters); - - } - - @Override - public void randomizeTrainerPokes(boolean rivalCarriesStarter, - boolean usePowerLevels, boolean noLegendaries, - boolean noEarlyWonderGuard) { - checkPokemonRestrictions(); - List<Trainer> currentTrainers = this.getTrainers(); - cachedReplacementLists = new TreeMap<Type, List<Pokemon>>(); - cachedAllList = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) - : new ArrayList<Pokemon>(mainPokemonList); - - // Fully random is easy enough - randomize then worry about rival - // carrying starter at the end - for (Trainer t : currentTrainers) { - if (t.tag != null && t.tag.equals("IRIVAL")) { - continue; // skip - } - for (TrainerPokemon tp : t.pokemon) { - boolean wgAllowed = (!noEarlyWonderGuard) || tp.level >= 20; - tp.pokemon = pickReplacement(tp.pokemon, usePowerLevels, null, - noLegendaries, wgAllowed); - } - } - - // Rival carries starter? - if (rivalCarriesStarter) { - rivalCarriesStarterUpdate(currentTrainers, "RIVAL", 1); - rivalCarriesStarterUpdate(currentTrainers, "FRIEND", 2); - } - - // Save it all up - this.setTrainers(currentTrainers); - } - - @Override - public void typeThemeTrainerPokes(boolean rivalCarriesStarter, - boolean usePowerLevels, boolean weightByFrequency, - boolean noLegendaries, boolean noEarlyWonderGuard) { - checkPokemonRestrictions(); - List<Trainer> currentTrainers = this.getTrainers(); - cachedReplacementLists = new TreeMap<Type, List<Pokemon>>(); - cachedAllList = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) - : new ArrayList<Pokemon>(mainPokemonList); - typeWeightings = new TreeMap<Type, Integer>(); - totalTypeWeighting = 0; - - // Construct groupings for types - // Anything starting with GYM or ELITE or CHAMPION is a group - Set<Trainer> assignedTrainers = new TreeSet<Trainer>(); - Map<String, List<Trainer>> groups = new TreeMap<String, List<Trainer>>(); - for (Trainer t : currentTrainers) { - if (t.tag != null && t.tag.equals("IRIVAL")) { - continue; // skip - } - String group = t.tag == null ? "" : t.tag; - if (group.contains("-")) { - group = group.substring(0, group.indexOf('-')); - } - if (group.startsWith("GYM") || group.startsWith("ELITE") - || group.startsWith("CHAMPION") - || group.startsWith("THEMED")) { - // Yep this is a group - if (!groups.containsKey(group)) { - groups.put(group, new ArrayList<Trainer>()); - } - groups.get(group).add(t); - assignedTrainers.add(t); - } else if (group.startsWith("GIO")) { - // Giovanni has same grouping as his gym, gym 8 - if (!groups.containsKey("GYM8")) { - groups.put("GYM8", new ArrayList<Trainer>()); - } - groups.get("GYM8").add(t); - assignedTrainers.add(t); - } - } - - // Give a type to each group - // Gym & elite types have to be unique - // So do uber types, including the type we pick for champion - Set<Type> usedGymTypes = new TreeSet<Type>(); - Set<Type> usedEliteTypes = new TreeSet<Type>(); - Set<Type> usedUberTypes = new TreeSet<Type>(); - for (String group : groups.keySet()) { - List<Trainer> trainersInGroup = groups.get(group); - Type typeForGroup = pickType(weightByFrequency, noLegendaries); - if (group.startsWith("GYM")) { - while (usedGymTypes.contains(typeForGroup)) { - typeForGroup = pickType(weightByFrequency, noLegendaries); - } - usedGymTypes.add(typeForGroup); - } - if (group.startsWith("ELITE")) { - while (usedEliteTypes.contains(typeForGroup)) { - typeForGroup = pickType(weightByFrequency, noLegendaries); - } - usedEliteTypes.add(typeForGroup); - } - if (group.equals("CHAMPION")) { - usedUberTypes.add(typeForGroup); - } - // Themed groups just have a theme, no special criteria - for (Trainer t : trainersInGroup) { - for (TrainerPokemon tp : t.pokemon) { - boolean wgAllowed = (!noEarlyWonderGuard) || tp.level >= 20; - tp.pokemon = pickReplacement(tp.pokemon, usePowerLevels, - typeForGroup, noLegendaries, wgAllowed); - } - } - } - - // Give a type to each unassigned trainer - for (Trainer t : currentTrainers) { - if (t.tag != null && t.tag.equals("IRIVAL")) { - continue; // skip - } - - if (!assignedTrainers.contains(t)) { - Type typeForTrainer = pickType(weightByFrequency, noLegendaries); - // Ubers: can't have the same type as each other - if (t.tag != null && t.tag.equals("UBER")) { - while (usedUberTypes.contains(typeForTrainer)) { - typeForTrainer = pickType(weightByFrequency, - noLegendaries); - } - usedUberTypes.add(typeForTrainer); - } - for (TrainerPokemon tp : t.pokemon) { - boolean shedAllowed = (!noEarlyWonderGuard) - || tp.level >= 20; - tp.pokemon = pickReplacement(tp.pokemon, usePowerLevels, - typeForTrainer, noLegendaries, shedAllowed); - } - } - } - - // Rival carries starter? - if (rivalCarriesStarter) { - rivalCarriesStarterUpdate(currentTrainers, "RIVAL", 1); - rivalCarriesStarterUpdate(currentTrainers, "FRIEND", 2); - } - - // Save it all up - this.setTrainers(currentTrainers); - } - - @Override - public void randomizeMovesLearnt(boolean typeThemed, boolean noBroken, - boolean forceFourStartingMoves) { - // Get current sets - Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); - List<Integer> allBanned = new ArrayList<Integer>(); - List<Integer> hms = this.getHMMoves(); - allBanned.addAll(hms); - @SuppressWarnings("unchecked") - List<Integer> banned = noBroken ? this.getGameBreakingMoves() - : Collections.EMPTY_LIST; - allBanned.addAll(banned); - allBanned.addAll(this.getMovesBannedFromLevelup()); - for (Pokemon pkmn : movesets.keySet()) { - Set<Integer> learnt = new TreeSet<Integer>(); - List<MoveLearnt> moves = movesets.get(pkmn); - // 4 starting moves? - if (forceFourStartingMoves) { - int lv1count = 0; - for (MoveLearnt ml : moves) { - if (ml.level == 1) { - lv1count++; - } - } - if (lv1count < 4) { - for (int i = 0; i < 4 - lv1count; i++) { - MoveLearnt fakeLv1 = new MoveLearnt(); - fakeLv1.level = 1; - fakeLv1.move = 0; - moves.add(0, fakeLv1); - } - } - } - // Last level 1 move should be replaced with a damaging one - int damagingMove = pickMove(pkmn, typeThemed, true, allBanned); - // Find last lv1 move - // lv1index ends up as the index of the first non-lv1 move - int lv1index = 0; - while (lv1index < moves.size() && moves.get(lv1index).level == 1) { - lv1index++; - } - // last lv1 move is 1 before lv1index - if (lv1index == 0) { - lv1index++; - } - moves.get(lv1index - 1).move = damagingMove; - moves.get(lv1index - 1).level = 1; // just in case - learnt.add(damagingMove); - // Rest replace with randoms - for (int i = 0; i < moves.size(); i++) { - if (i == (lv1index - 1)) { - continue; - } - int picked = pickMove(pkmn, typeThemed, false, allBanned); - while (learnt.contains(picked)) { - picked = pickMove(pkmn, typeThemed, false, allBanned); - } - moves.get(i).move = picked; - learnt.add(picked); - } - } - // Done, save - this.setMovesLearnt(movesets); - - } - - private static final int METRONOME_MOVE = 118; - - @Override - public void metronomeOnlyMode() { - - // movesets - Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); - - MoveLearnt metronomeML = new MoveLearnt(); - metronomeML.level = 1; - metronomeML.move = METRONOME_MOVE; - - for (List<MoveLearnt> ms : movesets.values()) { - if (ms != null && ms.size() > 0) { - ms.clear(); - ms.add(metronomeML); - } - } - - this.setMovesLearnt(movesets); - - // trainers - // run this to remove all custom non-Metronome moves - this.setTrainers(this.getTrainers()); - - // tms - List<Integer> tmMoves = this.getTMMoves(); - - for (int i = 0; i < tmMoves.size(); i++) { - tmMoves.set(i, METRONOME_MOVE); - } - - this.setTMMoves(tmMoves); - - // movetutors - if (this.hasMoveTutors()) { - List<Integer> mtMoves = this.getMoveTutorMoves(); - - for (int i = 0; i < mtMoves.size(); i++) { - mtMoves.set(i, METRONOME_MOVE); - } - - this.setMoveTutorMoves(mtMoves); - } - - // move tweaks - List<Move> moveData = this.getMoves(); - - Move metronome = moveData.get(METRONOME_MOVE); - - metronome.pp = 40; - - List<Integer> hms = this.getHMMoves(); - - for (int hm : hms) { - Move thisHM = moveData.get(hm); - thisHM.pp = 0; - } - } - - @Override - public void randomizeStaticPokemon(boolean legendForLegend) { - // Load - checkPokemonRestrictions(); - List<Pokemon> currentStaticPokemon = this.getStaticPokemon(); - List<Pokemon> replacements = new ArrayList<Pokemon>(); - List<Pokemon> banned = this.bannedForStaticPokemon(); - - if (legendForLegend) { - List<Pokemon> legendariesLeft = new ArrayList<Pokemon>( - onlyLegendaryList); - List<Pokemon> nonlegsLeft = new ArrayList<Pokemon>(noLegendaryList); - legendariesLeft.removeAll(banned); - nonlegsLeft.removeAll(banned); - for (int i = 0; i < currentStaticPokemon.size(); i++) { - Pokemon old = currentStaticPokemon.get(i); - Pokemon newPK; - if (old.isLegendary()) { - newPK = legendariesLeft.remove(this.random - .nextInt(legendariesLeft.size())); - if (legendariesLeft.size() == 0) { - legendariesLeft.addAll(onlyLegendaryList); - legendariesLeft.removeAll(banned); - } - } else { - newPK = nonlegsLeft.remove(this.random.nextInt(nonlegsLeft - .size())); - if (nonlegsLeft.size() == 0) { - nonlegsLeft.addAll(noLegendaryList); - nonlegsLeft.removeAll(banned); - } - } - replacements.add(newPK); - } - } else { - List<Pokemon> pokemonLeft = new ArrayList<Pokemon>(mainPokemonList); - pokemonLeft.removeAll(banned); - for (int i = 0; i < currentStaticPokemon.size(); i++) { - Pokemon newPK = pokemonLeft.remove(this.random - .nextInt(pokemonLeft.size())); - if (pokemonLeft.size() == 0) { - pokemonLeft.addAll(mainPokemonList); - pokemonLeft.removeAll(banned); - } - replacements.add(newPK); - } - } - - // Save - this.setStaticPokemon(replacements); - } - - @Override - public void randomizeTMMoves(boolean noBroken, boolean preserveField) { - // Pick some random TM moves. - int tmCount = this.getTMCount(); - List<Move> allMoves = this.getMoves(); - List<Integer> newTMs = new ArrayList<Integer>(); - List<Integer> hms = this.getHMMoves(); - List<Integer> oldTMs = this.getTMMoves(); - @SuppressWarnings("unchecked") - List<Integer> banned = new ArrayList<Integer>( - noBroken ? this.getGameBreakingMoves() : Collections.EMPTY_LIST); - // field moves? - List<Integer> fieldMoves = this.getFieldMoves(); - if (preserveField) { - List<Integer> banExistingField = new ArrayList<Integer>(oldTMs); - banExistingField.retainAll(fieldMoves); - banned.addAll(banExistingField); - } - for (int i = 0; i < tmCount; i++) { - if (preserveField && fieldMoves.contains(oldTMs.get(i))) { - newTMs.add(oldTMs.get(i)); - } else { - int chosenMove = this.random.nextInt(allMoves.size() - 1) + 1; - while (newTMs.contains(chosenMove) - || RomFunctions.bannedRandomMoves[chosenMove] - || hms.contains(chosenMove) - || banned.contains(chosenMove)) { - chosenMove = this.random.nextInt(allMoves.size() - 1) + 1; - } - newTMs.add(chosenMove); - } - } - this.setTMMoves(newTMs); - } - - @Override - public void randomizeTMHMCompatibility(boolean preferSameType) { - // Get current compatibility - // new: increase HM chances if required early on - List<Integer> requiredEarlyOn = this.getEarlyRequiredHMMoves(); - Map<Pokemon, boolean[]> compat = this.getTMHMCompatibility(); - List<Integer> tmHMs = new ArrayList<Integer>(this.getTMMoves()); - tmHMs.addAll(this.getHMMoves()); - List<Move> moveData = this.getMoves(); - for (Map.Entry<Pokemon, boolean[]> compatEntry : compat.entrySet()) { - Pokemon pkmn = compatEntry.getKey(); - boolean[] flags = compatEntry.getValue(); - for (int i = 1; i <= tmHMs.size(); i++) { - int move = tmHMs.get(i - 1); - Move mv = moveData.get(move); - double probability = 0.5; - if (preferSameType) { - if (pkmn.primaryType.equals(mv.type) - || (pkmn.secondaryType != null && pkmn.secondaryType - .equals(mv.type))) { - probability = 0.9; - } else if (mv.type != null && mv.type.equals(Type.NORMAL)) { - probability = 0.5; - } else { - probability = 0.25; - } - } - if (requiredEarlyOn.contains(move)) { - probability = Math.min(1.0, probability * 1.5); - } - flags[i] = (this.random.nextDouble() < probability); - } - } - - // Set the new compatibility - this.setTMHMCompatibility(compat); - } - - @Override - public void fullTMHMCompatibility() { - Map<Pokemon, boolean[]> compat = this.getTMHMCompatibility(); - for (Map.Entry<Pokemon, boolean[]> compatEntry : compat.entrySet()) { - boolean[] flags = compatEntry.getValue(); - for (int i = 1; i < flags.length; i++) { - flags[i] = true; - } - } - this.setTMHMCompatibility(compat); - } - - @Override - public void fullHMCompatibility() { - Map<Pokemon, boolean[]> compat = this.getTMHMCompatibility(); - int tmCount = this.getTMCount(); - for (boolean[] flags : compat.values()) { - for (int i = tmCount + 1; i < flags.length; i++) { - flags[i] = true; - } - } - - // Set the new compatibility - this.setTMHMCompatibility(compat); - } - - @Override - public void ensureTMCompatSanity() { - // if a pokemon learns a move in its moveset - // and there is a TM of that move, make sure - // that TM can be learned. - Map<Pokemon, boolean[]> compat = this.getTMHMCompatibility(); - Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); - List<Integer> tmMoves = this.getTMMoves(); - for (Pokemon pkmn : compat.keySet()) { - List<MoveLearnt> moveset = movesets.get(pkmn); - boolean[] pkmnCompat = compat.get(pkmn); - for (MoveLearnt ml : moveset) { - if (tmMoves.contains(ml.move)) { - int tmIndex = tmMoves.indexOf(ml.move); - pkmnCompat[tmIndex + 1] = true; - } - } - } - this.setTMHMCompatibility(compat); - } - - @Override - public void randomizeMoveTutorMoves(boolean noBroken, boolean preserveField) { - if (!this.hasMoveTutors()) { - return; - } - // Pick some random Move Tutor moves, excluding TMs. - List<Move> allMoves = this.getMoves(); - List<Integer> tms = this.getTMMoves(); - List<Integer> newMTs = new ArrayList<Integer>(); - List<Integer> oldMTs = this.getMoveTutorMoves(); - int mtCount = oldMTs.size(); - List<Integer> hms = this.getHMMoves(); - @SuppressWarnings("unchecked") - List<Integer> banned = new ArrayList<Integer>( - noBroken ? this.getGameBreakingMoves() : Collections.EMPTY_LIST); - // field moves? - List<Integer> fieldMoves = this.getFieldMoves(); - if (preserveField) { - List<Integer> banExistingField = new ArrayList<Integer>(oldMTs); - banExistingField.retainAll(fieldMoves); - banned.addAll(banExistingField); - } - for (int i = 0; i < mtCount; i++) { - if (preserveField && fieldMoves.contains(oldMTs.get(i))) { - newMTs.add(oldMTs.get(i)); - } else { - int chosenMove = this.random.nextInt(allMoves.size() - 1) + 1; - while (newMTs.contains(chosenMove) || tms.contains(chosenMove) - || RomFunctions.bannedRandomMoves[chosenMove] - || hms.contains(chosenMove) - || banned.contains(chosenMove)) { - chosenMove = this.random.nextInt(allMoves.size() - 1) + 1; - } - newMTs.add(chosenMove); - } - } - this.setMoveTutorMoves(newMTs); - } - - @Override - public void randomizeMoveTutorCompatibility(boolean preferSameType) { - if (!this.hasMoveTutors()) { - return; - } - // Get current compatibility - Map<Pokemon, boolean[]> compat = this.getMoveTutorCompatibility(); - List<Integer> mts = this.getMoveTutorMoves(); - List<Move> moveData = this.getMoves(); - for (Map.Entry<Pokemon, boolean[]> compatEntry : compat.entrySet()) { - Pokemon pkmn = compatEntry.getKey(); - boolean[] flags = compatEntry.getValue(); - for (int i = 1; i <= mts.size(); i++) { - int move = mts.get(i - 1); - Move mv = moveData.get(move); - double probability = 0.5; - if (preferSameType) { - if (pkmn.primaryType.equals(mv.type) - || (pkmn.secondaryType != null && pkmn.secondaryType - .equals(mv.type))) { - probability = 0.9; - } else if (mv.type != null && mv.type.equals(Type.NORMAL)) { - probability = 0.5; - } else { - probability = 0.25; - } - } - flags[i] = (this.random.nextDouble() < probability); - } - } - - // Set the new compatibility - this.setMoveTutorCompatibility(compat); - - } - - @Override - public void fullMoveTutorCompatibility() { - if (!this.hasMoveTutors()) { - return; - } - Map<Pokemon, boolean[]> compat = this.getMoveTutorCompatibility(); - for (Map.Entry<Pokemon, boolean[]> compatEntry : compat.entrySet()) { - boolean[] flags = compatEntry.getValue(); - for (int i = 1; i < flags.length; i++) { - flags[i] = true; - } - } - this.setMoveTutorCompatibility(compat); - } - - @Override - public void ensureMoveTutorCompatSanity() { - if (!this.hasMoveTutors()) { - return; - } - // if a pokemon learns a move in its moveset - // and there is a tutor of that move, make sure - // that tutor can be learned. - Map<Pokemon, boolean[]> compat = this.getMoveTutorCompatibility(); - Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); - List<Integer> mtMoves = this.getMoveTutorMoves(); - for (Pokemon pkmn : compat.keySet()) { - List<MoveLearnt> moveset = movesets.get(pkmn); - boolean[] pkmnCompat = compat.get(pkmn); - for (MoveLearnt ml : moveset) { - if (mtMoves.contains(ml.move)) { - int mtIndex = mtMoves.indexOf(ml.move); - pkmnCompat[mtIndex + 1] = true; - } - } - } - this.setMoveTutorCompatibility(compat); - - } - - @SuppressWarnings("unchecked") - @Override - public void randomizeTrainerNames(byte[] presetNames) { - List<String>[] allTrainerNames = new List[] { new ArrayList<String>(), - new ArrayList<String>() }; - Map<Integer, List<String>> trainerNamesByLength[] = new Map[] { - new TreeMap<Integer, List<String>>(), - new TreeMap<Integer, List<String>>() }; - // Check for the file - if (FileFunctions.configExists(tnamesFile)) { - try { - Scanner sc = null; - if (presetNames == null) { - sc = new Scanner(FileFunctions.openConfig(tnamesFile), - "UTF-8"); - } else { - sc = new Scanner(new ByteArrayInputStream(presetNames), - "UTF-8"); - } - while (sc.hasNextLine()) { - String trainername = sc.nextLine().trim(); - if (trainername.isEmpty()) { - continue; - } - if (trainername.startsWith("\uFEFF")) { - trainername = trainername.substring(1); - } - int idx = trainername.contains("&") ? 1 : 0; - int len = this.internalStringLength(trainername); - if (len <= 10) { - allTrainerNames[idx].add(trainername); - if (trainerNamesByLength[idx].containsKey(len)) { - trainerNamesByLength[idx].get(len).add(trainername); - } else { - List<String> namesOfThisLength = new ArrayList<String>(); - namesOfThisLength.add(trainername); - trainerNamesByLength[idx].put(len, - namesOfThisLength); - } - } - } - sc.close(); - } catch (FileNotFoundException e) { - // Can't read, just don't load anything - } - } - - // Get the current trainer names data - List<String> currentTrainerNames = this.getTrainerNames(); - if (currentTrainerNames.size() == 0) { - // RBY have no trainer names - return; - } - TrainerNameMode mode = this.trainerNameMode(); - int maxLength = this.maxTrainerNameLength(); - - // Init the translation map and new list - Map<String, String> translation = new HashMap<String, String>(); - List<String> newTrainerNames = new ArrayList<String>(); - List<Integer> tcNameLengths = this.getTCNameLengthsByTrainer(); - - // Start choosing - int tnIndex = -1; - for (String trainerName : currentTrainerNames) { - tnIndex++; - if (translation.containsKey(trainerName) - && trainerName.equalsIgnoreCase("GRUNT") == false - && trainerName.equalsIgnoreCase("EXECUTIVE") == false) { - // use an already picked translation - newTrainerNames.add(translation.get(trainerName)); - } else { - int idx = trainerName.contains("&") ? 1 : 0; - List<String> pickFrom = allTrainerNames[idx]; - int intStrLen = this.internalStringLength(trainerName); - if (mode == TrainerNameMode.SAME_LENGTH) { - pickFrom = trainerNamesByLength[idx].get(intStrLen); - } - String changeTo = trainerName; - if (pickFrom != null && pickFrom.size() > 0 && intStrLen > 1) { - int tries = 0; - changeTo = pickFrom - .get(this.random.nextInt(pickFrom.size())); - int ctl = this.internalStringLength(changeTo); - while ((mode == TrainerNameMode.MAX_LENGTH && ctl > maxLength) - || (mode == TrainerNameMode.MAX_LENGTH_WITH_CLASS && ctl - + tcNameLengths.get(tnIndex) > maxLength)) { - tries++; - if (tries == 50) { - changeTo = trainerName; - break; - } - changeTo = pickFrom.get(this.random.nextInt(pickFrom - .size())); - ctl = this.internalStringLength(changeTo); - } - } - translation.put(trainerName, changeTo); - newTrainerNames.add(changeTo); - } - } - - // Done choosing, save - this.setTrainerNames(newTrainerNames); - } - - @Override - public int maxTrainerNameLength() { - // default: no real limit - return Integer.MAX_VALUE; - } - - @SuppressWarnings("unchecked") - @Override - public void randomizeTrainerClassNames(byte[] presetNames) { - List<String> allTrainerClasses[] = new List[] { - new ArrayList<String>(), new ArrayList<String>() }; - Map<Integer, List<String>> trainerClassesByLength[] = new Map[] { - new HashMap<Integer, List<String>>(), - new HashMap<Integer, List<String>>() }; - // Check for the file - if (FileFunctions.configExists(tclassesFile)) { - try { - Scanner sc = null; - if (presetNames == null) { - sc = new Scanner(FileFunctions.openConfig(tclassesFile), - "UTF-8"); - } else { - sc = new Scanner(new ByteArrayInputStream(presetNames), - "UTF-8"); - } - while (sc.hasNextLine()) { - String trainerClassName = sc.nextLine().trim(); - if (trainerClassName.isEmpty()) { - continue; - } - if (trainerClassName.startsWith("\uFEFF")) { - trainerClassName = trainerClassName.substring(1); - } - String checkName = trainerClassName.toLowerCase(); - int idx = (checkName.endsWith("couple") - || checkName.contains(" and ") - || checkName.endsWith("kin") - || checkName.endsWith("team") - || checkName.contains("&") || (checkName - .endsWith("s") && !checkName.endsWith("ss"))) ? 1 - : 0; - allTrainerClasses[idx].add(trainerClassName); - int len = this.internalStringLength(trainerClassName); - if (trainerClassesByLength[idx].containsKey(len)) { - trainerClassesByLength[idx].get(len).add( - trainerClassName); - } else { - List<String> namesOfThisLength = new ArrayList<String>(); - namesOfThisLength.add(trainerClassName); - trainerClassesByLength[idx].put(len, namesOfThisLength); - } - } - sc.close(); - } catch (FileNotFoundException e) { - // Can't read, just don't load anything - } - } - - // Get the current trainer names data - List<String> currentClassNames = this.getTrainerClassNames(); - boolean mustBeSameLength = this.fixedTrainerClassNamesLength(); - int maxLength = this.maxTrainerClassNameLength(); - - // Init the translation map and new list - Map<String, String> translation = new HashMap<String, String>(); - List<String> newClassNames = new ArrayList<String>(); - - // Start choosing - for (String trainerClassName : currentClassNames) { - if (translation.containsKey(trainerClassName)) { - // use an already picked translation - newClassNames.add(translation.get(trainerClassName)); - } else { - String checkName = trainerClassName.toLowerCase(); - int idx = (checkName.endsWith("couple") - || checkName.contains(" and ") - || checkName.endsWith("kin") - || checkName.endsWith("team") - || checkName.contains(" & ") || (checkName - .endsWith("s") && !checkName.endsWith("ss"))) ? 1 : 0; - List<String> pickFrom = allTrainerClasses[idx]; - int intStrLen = this.internalStringLength(trainerClassName); - if (mustBeSameLength) { - pickFrom = trainerClassesByLength[idx].get(intStrLen); - } - String changeTo = trainerClassName; - if (pickFrom != null && pickFrom.size() > 0) { - changeTo = pickFrom - .get(this.random.nextInt(pickFrom.size())); - while (changeTo.length() > maxLength) { - changeTo = pickFrom.get(this.random.nextInt(pickFrom - .size())); - } - } - translation.put(trainerClassName, changeTo); - newClassNames.add(changeTo); - } - } - - // Done choosing, save - this.setTrainerClassNames(newClassNames); - } - - @Override - public int maxTrainerClassNameLength() { - // default: no real limit - return Integer.MAX_VALUE; - } - - @Override - public void randomizeWildHeldItems(boolean banBadItems) { - List<Pokemon> pokemon = allPokemonWithoutNull(); - ItemList possibleItems = banBadItems ? this.getNonBadItems() : this - .getAllowedItems(); - for (Pokemon pk : pokemon) { - if (pk.guaranteedHeldItem == -1 && pk.commonHeldItem == -1 - && pk.rareHeldItem == -1 && pk.darkGrassHeldItem == -1) { - // No held items at all, abort - return; - } - boolean canHaveDarkGrass = pk.darkGrassHeldItem != -1; - if (pk.guaranteedHeldItem != -1) { - // Guaranteed held items are supported. - if (pk.guaranteedHeldItem > 0) { - // Currently have a guaranteed item - double decision = this.random.nextDouble(); - if (decision < 0.9) { - // Stay as guaranteed - canHaveDarkGrass = false; - pk.guaranteedHeldItem = possibleItems - .randomItem(this.random); - } else { - // Change to 25% or 55% chance - pk.guaranteedHeldItem = 0; - pk.commonHeldItem = possibleItems - .randomItem(this.random); - pk.rareHeldItem = possibleItems.randomItem(this.random); - while (pk.rareHeldItem == pk.commonHeldItem) { - pk.rareHeldItem = possibleItems - .randomItem(this.random); - } - } - } else { - // No guaranteed item atm - double decision = this.random.nextDouble(); - if (decision < 0.5) { - // No held item at all - pk.commonHeldItem = 0; - pk.rareHeldItem = 0; - } else if (decision < 0.65) { - // Just a rare item - pk.commonHeldItem = 0; - pk.rareHeldItem = possibleItems.randomItem(this.random); - } else if (decision < 0.8) { - // Just a common item - pk.commonHeldItem = possibleItems - .randomItem(this.random); - pk.rareHeldItem = 0; - } else if (decision < 0.95) { - // Both a common and rare item - pk.commonHeldItem = possibleItems - .randomItem(this.random); - pk.rareHeldItem = possibleItems.randomItem(this.random); - while (pk.rareHeldItem == pk.commonHeldItem) { - pk.rareHeldItem = possibleItems - .randomItem(this.random); - } - } else { - // Guaranteed item - canHaveDarkGrass = false; - pk.guaranteedHeldItem = possibleItems - .randomItem(this.random); - pk.commonHeldItem = 0; - pk.rareHeldItem = 0; - } - } - } else { - // Code for no guaranteed items - double decision = this.random.nextDouble(); - if (decision < 0.5) { - // No held item at all - pk.commonHeldItem = 0; - pk.rareHeldItem = 0; - } else if (decision < 0.65) { - // Just a rare item - pk.commonHeldItem = 0; - pk.rareHeldItem = possibleItems.randomItem(this.random); - } else if (decision < 0.8) { - // Just a common item - pk.commonHeldItem = possibleItems.randomItem(this.random); - pk.rareHeldItem = 0; - } else { - // Both a common and rare item - pk.commonHeldItem = possibleItems.randomItem(this.random); - pk.rareHeldItem = possibleItems.randomItem(this.random); - while (pk.rareHeldItem == pk.commonHeldItem) { - pk.rareHeldItem = possibleItems.randomItem(this.random); - } - } - } - - if (canHaveDarkGrass) { - double dgDecision = this.random.nextDouble(); - if (dgDecision < 0.5) { - // Yes, dark grass item - pk.darkGrassHeldItem = possibleItems - .randomItem(this.random); - } else { - pk.darkGrassHeldItem = 0; - } - } else if (pk.darkGrassHeldItem != -1) { - pk.darkGrassHeldItem = 0; - } - } - - } - - @Override - public void randomizeStarterHeldItems(boolean banBadItems) { - List<Integer> oldHeldItems = this.getStarterHeldItems(); - List<Integer> newHeldItems = new ArrayList<Integer>(); - ItemList possibleItems = banBadItems ? this.getNonBadItems() : this - .getAllowedItems(); - for (int i = 0; i < oldHeldItems.size(); i++) { - newHeldItems.add(possibleItems.randomItem(this.random)); - } - this.setStarterHeldItems(newHeldItems); - } - - @Override - public void shuffleFieldItems() { - List<Integer> currentItems = this.getRegularFieldItems(); - List<Integer> currentTMs = this.getCurrentFieldTMs(); - - Collections.shuffle(currentItems, this.random); - Collections.shuffle(currentTMs, this.random); - - this.setRegularFieldItems(currentItems); - this.setFieldTMs(currentTMs); - } - - @Override - public void randomizeFieldItems(boolean banBadItems) { - ItemList possibleItems = banBadItems ? this.getNonBadItems() : this - .getAllowedItems(); - List<Integer> currentItems = this.getRegularFieldItems(); - List<Integer> currentTMs = this.getCurrentFieldTMs(); - List<Integer> requiredTMs = this.getRequiredFieldTMs(); - - int fieldItemCount = currentItems.size(); - int fieldTMCount = currentTMs.size(); - int reqTMCount = requiredTMs.size(); - int totalTMCount = this.getTMCount(); - - List<Integer> newItems = new ArrayList<Integer>(); - List<Integer> newTMs = new ArrayList<Integer>(); - - for (int i = 0; i < fieldItemCount; i++) { - newItems.add(possibleItems.randomNonTM(this.random)); - } - - newTMs.addAll(requiredTMs); - - for (int i = reqTMCount; i < fieldTMCount; i++) { - while (true) { - int tm = this.random.nextInt(totalTMCount) + 1; - if (!newTMs.contains(tm)) { - newTMs.add(tm); - break; - } - } - } - - Collections.shuffle(newItems, this.random); - Collections.shuffle(newTMs, this.random); - - this.setRegularFieldItems(newItems); - this.setFieldTMs(newTMs); - } - - @Override - public void randomizeIngameTrades(boolean randomizeRequest, - byte[] presetNicknames, boolean randomNickname, - byte[] presetTrainerNames, boolean randomOT, boolean randomStats, - boolean randomItem) { - checkPokemonRestrictions(); - // Process trainer names - List<String> singleTrainerNames = new ArrayList<String>(); - // Check for the file - if (FileFunctions.configExists(tnamesFile) && randomOT) { - int maxOT = this.maxTradeOTNameLength(); - try { - Scanner sc = null; - if (presetTrainerNames == null) { - sc = new Scanner(FileFunctions.openConfig(tnamesFile), - "UTF-8"); - } else { - sc = new Scanner(new ByteArrayInputStream( - presetTrainerNames), "UTF-8"); - } - while (sc.hasNextLine()) { - String trainername = sc.nextLine().trim(); - if (trainername.isEmpty()) { - continue; - } - if (trainername.startsWith("\uFEFF")) { - trainername = trainername.substring(1); - } - int idx = trainername.contains("&") ? 1 : 0; - int len = this.internalStringLength(trainername); - if (len <= maxOT && idx == 0 - && !singleTrainerNames.contains(trainername)) { - singleTrainerNames.add(trainername); - } - } - sc.close(); - } catch (FileNotFoundException e) { - // Can't read, just don't load anything - } - } - - // Process nicknames - List<String> nicknames = new ArrayList<String>(); - // Check for the file - if (FileFunctions.configExists(nnamesFile) && randomNickname) { - int maxNN = this.maxTradeNicknameLength(); - try { - Scanner sc = null; - if (presetNicknames == null) { - sc = new Scanner(FileFunctions.openConfig(nnamesFile), - "UTF-8"); - } else { - sc = new Scanner(new ByteArrayInputStream(presetNicknames), - "UTF-8"); - } - while (sc.hasNextLine()) { - String nickname = sc.nextLine().trim(); - if (nickname.isEmpty()) { - continue; - } - if (nickname.startsWith("\uFEFF")) { - nickname = nickname.substring(1); - } - int len = this.internalStringLength(nickname); - if (len <= maxNN && !nicknames.contains(nickname)) { - nicknames.add(nickname); - } - } - sc.close(); - } catch (FileNotFoundException e) { - // Can't read, just don't load anything - } - } - - // get old trades - List<IngameTrade> trades = this.getIngameTrades(); - List<Pokemon> usedRequests = new ArrayList<Pokemon>(); - List<Pokemon> usedGivens = new ArrayList<Pokemon>(); - List<String> usedOTs = new ArrayList<String>(); - List<String> usedNicknames = new ArrayList<String>(); - ItemList possibleItems = this.getAllowedItems(); - - int nickCount = nicknames.size(); - int trnameCount = singleTrainerNames.size(); - - for (IngameTrade trade : trades) { - // pick new given pokemon - Pokemon oldgiven = trade.givenPokemon; - Pokemon given = this.randomPokemon(); - while (usedGivens.contains(given)) { - given = this.randomPokemon(); - } - usedGivens.add(given); - trade.givenPokemon = given; - - // requested pokemon? - if (oldgiven == trade.requestedPokemon) { - // preserve trades for the same pokemon - trade.requestedPokemon = given; - } else if (randomizeRequest) { - Pokemon request = this.randomPokemon(); - while (usedRequests.contains(request) || request == given) { - request = this.randomPokemon(); - } - usedRequests.add(request); - trade.requestedPokemon = request; - } - - // nickname? - if (randomNickname && nickCount > usedNicknames.size()) { - String nickname = nicknames.get(this.random.nextInt(nickCount)); - while (usedNicknames.contains(nickname)) { - nickname = nicknames.get(this.random.nextInt(nickCount)); - } - usedNicknames.add(nickname); - trade.nickname = nickname; - } else if (trade.nickname.equalsIgnoreCase(oldgiven.name)) { - // change the name for sanity - trade.nickname = trade.givenPokemon.name; - } - - if (randomOT && trnameCount > usedOTs.size()) { - String ot = singleTrainerNames.get(this.random - .nextInt(trnameCount)); - while (usedOTs.contains(ot)) { - ot = singleTrainerNames.get(this.random - .nextInt(trnameCount)); - } - usedOTs.add(ot); - trade.otName = ot; - trade.otId = this.random.nextInt(65536); - } - - if (randomStats) { - int maxIV = this.hasDVs() ? 16 : 32; - for (int i = 0; i < trade.ivs.length; i++) { - trade.ivs[i] = this.random.nextInt(maxIV); - } - } - - if (randomItem) { - trade.item = possibleItems.randomItem(this.random); - } - } - - // things that the game doesn't support should just be ignored - this.setIngameTrades(trades); - } - - @Override - public int maxTradeNicknameLength() { - return 10; - } - - @Override - public int maxTradeOTNameLength() { - return 7; - } - - @Override - public void condenseLevelEvolutions(int maxLevel, int maxIntermediateLevel) { - List<Pokemon> allPokemon = this.getPokemon(); - Set<Evolution> changedEvos = new TreeSet<Evolution>(); - // search for level evolutions - for (Pokemon pk : allPokemon) { - if (pk != null) { - for (Evolution checkEvo : pk.evolutionsFrom) { - if (checkEvo.type.usesLevel()) { - // bring down the level of this evo if it exceeds max - // level - if (checkEvo.extraInfo > maxLevel) { - checkEvo.extraInfo = maxLevel; - changedEvos.add(checkEvo); - } - // Now, seperately, if an intermediate level evo is too - // high, bring it down - for (Evolution otherEvo : pk.evolutionsTo) { - if (otherEvo.type.usesLevel() - && otherEvo.extraInfo > maxIntermediateLevel) { - otherEvo.extraInfo = maxIntermediateLevel; - changedEvos.add(otherEvo); - } - } - } - } - } - } - // Log changes now that we're done (to avoid repeats) - log("--Condensed Level Evolutions--"); - for (Evolution evol : changedEvos) { - log(String.format("%s now evolves into %s at minimum level %d", - evol.from.name, evol.to.name, evol.extraInfo)); - } - logBlankLine(); - - } - - @Override - public void randomizeEvolutions(boolean similarStrength, boolean sameType, - boolean limitToThreeStages, boolean forceChange) { - checkPokemonRestrictions(); - List<Pokemon> pokemonPool = new ArrayList<Pokemon>(mainPokemonList); - int stageLimit = limitToThreeStages ? 3 : 10; - - // Cache old evolutions for data later - Map<Pokemon, List<Evolution>> originalEvos = new HashMap<Pokemon, List<Evolution>>(); - for (Pokemon pk : pokemonPool) { - originalEvos.put(pk, new ArrayList<Evolution>(pk.evolutionsFrom)); - } - - Set<EvolutionPair> newEvoPairs = new HashSet<EvolutionPair>(); - Set<EvolutionPair> oldEvoPairs = new HashSet<EvolutionPair>(); - - if (forceChange) { - for (Pokemon pk : pokemonPool) { - for (Evolution ev : pk.evolutionsFrom) { - oldEvoPairs.add(new EvolutionPair(ev.from, ev.to)); - } - } - } - - List<Pokemon> replacements = new ArrayList<Pokemon>(); - - int loops = 0; - while (loops < 1) { - // Setup for this loop. - boolean hadError = false; - for (Pokemon pk : pokemonPool) { - pk.evolutionsFrom.clear(); - pk.evolutionsTo.clear(); - } - newEvoPairs.clear(); - - // Shuffle pokemon list so the results aren't overly predictable. - Collections.shuffle(pokemonPool, this.random); - - for (Pokemon fromPK : pokemonPool) { - List<Evolution> oldEvos = originalEvos.get(fromPK); - for (Evolution ev : oldEvos) { - // Pick a Pokemon as replacement - replacements.clear(); - - // Step 1: base filters - for (Pokemon pk : mainPokemonList) { - // Prevent evolving into oneself (mandatory) - if (pk == fromPK) { - continue; - } - - // Force same EXP curve (mandatory) - if (pk.growthCurve != fromPK.growthCurve) { - continue; - } - - EvolutionPair ep = new EvolutionPair(fromPK, pk); - // Prevent split evos choosing the same Pokemon - // (mandatory) - if (newEvoPairs.contains(ep)) { - continue; - } - - // Prevent evolving into old thing if flagged - if (forceChange && oldEvoPairs.contains(ep)) { - continue; - } - - // Prevent evolution that causes cycle (mandatory) - if (evoCycleCheck(fromPK, pk)) { - continue; - } - - // Prevent evolution that exceeds stage limit - Evolution tempEvo = new Evolution(fromPK, pk, false, - EvolutionType.NONE, 0); - fromPK.evolutionsFrom.add(tempEvo); - pk.evolutionsTo.add(tempEvo); - boolean exceededLimit = false; - - Set<Pokemon> related = relatedPokemon(fromPK); - - for (Pokemon pk2 : related) { - int numPreEvos = numPreEvolutions(pk2, stageLimit); - if (numPreEvos >= stageLimit) { - exceededLimit = true; - break; - } else if (numPreEvos == stageLimit - 1 - && pk2.evolutionsFrom.size() == 0 - && originalEvos.get(pk2).size() > 0) { - exceededLimit = true; - break; - } - } - - fromPK.evolutionsFrom.remove(tempEvo); - pk.evolutionsTo.remove(tempEvo); - - if (exceededLimit) { - continue; - } - - // Passes everything, add as a candidate. - replacements.add(pk); - } - - // If we don't have any candidates after Step 1, severe - // failure - // exit out of this loop and try again from scratch - if (replacements.size() == 0) { - hadError = true; - break; - } - - // Step 2: filter by type, if needed - if (replacements.size() > 1 && sameType) { - Set<Pokemon> includeType = new HashSet<Pokemon>(); - for (Pokemon pk : replacements) { - if (pk.primaryType == fromPK.primaryType - || (fromPK.secondaryType != null && pk.primaryType == fromPK.secondaryType) - || (pk.secondaryType != null && pk.secondaryType == fromPK.primaryType) - || (fromPK.secondaryType != null - && pk.secondaryType != null && pk.secondaryType == fromPK.secondaryType)) { - includeType.add(pk); - } - } - - if (includeType.size() != 0) { - replacements.retainAll(includeType); - } - } - - // Step 3: pick - by similar strength or otherwise - Pokemon picked = null; - - if (replacements.size() == 1) { - // Foregone conclusion. - picked = replacements.get(0); - } else if (similarStrength) { - picked = pickEvoPowerLvlReplacement(replacements, ev.to); - } else { - picked = replacements.get(this.random - .nextInt(replacements.size())); - } - - // Step 4: add it to the new evos pool - Evolution newEvo = new Evolution(fromPK, picked, - ev.carryStats, ev.type, ev.extraInfo); - fromPK.evolutionsFrom.add(newEvo); - picked.evolutionsTo.add(newEvo); - newEvoPairs.add(new EvolutionPair(fromPK, picked)); - } - - if (hadError) { - // No need to check the other Pokemon if we already errored - break; - } - } - - // If no error, done and return - if (!hadError) { - return; - } else { - loops++; - } - } - - // If we made it out of the loop, we weren't able to randomize evos. - throw new RuntimeException( - "Not able to randomize evolutions in a sane amount of retries."); - } - - private Pokemon pickEvoPowerLvlReplacement(List<Pokemon> pokemonPool, - Pokemon current) { - // start with within 10% and add 5% either direction till we find - // something - int currentBST = current.bstForPowerLevels(); - int minTarget = currentBST - currentBST / 10; - int maxTarget = currentBST + currentBST / 10; - List<Pokemon> canPick = new ArrayList<Pokemon>(); - int expandRounds = 0; - while (canPick.isEmpty() || (canPick.size() < 3 && expandRounds < 3)) { - for (Pokemon pk : pokemonPool) { - if (pk.bstForPowerLevels() >= minTarget - && pk.bstForPowerLevels() <= maxTarget - && !canPick.contains(pk)) { - canPick.add(pk); - } - } - minTarget -= currentBST / 20; - maxTarget += currentBST / 20; - expandRounds++; - } - return canPick.get(this.random.nextInt(canPick.size())); - } - - private static class EvolutionPair { - private Pokemon from; - private Pokemon to; - - public EvolutionPair(Pokemon from, Pokemon to) { - this.from = from; - this.to = to; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((from == null) ? 0 : from.hashCode()); - result = prime * result + ((to == null) ? 0 : to.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - EvolutionPair other = (EvolutionPair) obj; - if (from == null) { - if (other.from != null) - return false; - } else if (!from.equals(other.from)) - return false; - if (to == null) { - if (other.to != null) - return false; - } else if (!to.equals(other.to)) - return false; - return true; - } - } - - /** - * Check whether adding an evolution from one Pokemon to another will cause - * an evolution cycle. - * - * @param from - * @param to - * @param newEvos - * @return - */ - private boolean evoCycleCheck(Pokemon from, Pokemon to) { - Evolution tempEvo = new Evolution(from, to, false, EvolutionType.NONE, - 0); - from.evolutionsFrom.add(tempEvo); - Set<Pokemon> visited = new HashSet<Pokemon>(); - Set<Pokemon> recStack = new HashSet<Pokemon>(); - boolean recur = isCyclic(from, visited, recStack); - from.evolutionsFrom.remove(tempEvo); - return recur; - } - - private boolean isCyclic(Pokemon pk, Set<Pokemon> visited, - Set<Pokemon> recStack) { - if (!visited.contains(pk)) { - visited.add(pk); - recStack.add(pk); - for (Evolution ev : pk.evolutionsFrom) { - if (!visited.contains(ev.to) - && isCyclic(ev.to, visited, recStack)) { - return true; - } else if (recStack.contains(ev.to)) { - return true; - } - } - } - recStack.remove(pk); - return false; - } - - // MOVE DATA - // All randomizers don't touch move ID 165 (Struggle) - // They also have other exclusions where necessary to stop things glitching. - - @Override - public void randomizeMovePowers() { - List<Move> moves = this.getMoves(); - for (Move mv : moves) { - if (mv != null && mv.internalId != 165 && mv.power >= 10) { - // "Generic" damaging move to randomize power - if (random.nextInt(3) != 2) { - // "Regular" move - mv.power = random.nextInt(11) * 5 + 50; // 50 ... 100 - } else { - // "Extreme" move - mv.power = random.nextInt(27) * 5 + 20; // 20 ... 150 - } - // Tiny chance for massive power jumps - for (int i = 0; i < 2; i++) { - if (random.nextInt(100) == 0) { - mv.power += 50; - } - } - } - } - } - - @Override - public void randomizeMovePPs() { - List<Move> moves = this.getMoves(); - for (Move mv : moves) { - if (mv != null && mv.internalId != 165) { - mv.pp = random.nextInt(8) * 5 + 5; // 5 ... 40 inclusive - } - } - } - - @Override - public void randomizeMoveAccuracies() { - List<Move> moves = this.getMoves(); - for (Move mv : moves) { - if (mv != null && mv.internalId != 165 && mv.hitratio >= 5) { - // "Sane" accuracy randomization - // Broken into three tiers based on original accuracy - // Designed to limit the chances of 100% accurate OHKO moves and - // keep a decent base of 100% accurate regular moves. - - if (mv.hitratio <= 50) { - // lowest tier (acc <= 50) - // new accuracy = rand(20...100) inclusive - mv.hitratio = random.nextInt(17) * 5 + 20; - } else if (mv.hitratio < 90) { - // middle tier (50 < acc < 90) - // count down from 100% to 20% in 5% increments with 20% - // chance to "stop" and use the current accuracy at each - // increment - // gives decent-but-not-100% accuracy most of the time - mv.hitratio = 100; - while (mv.hitratio > 20) { - if (random.nextInt(10) < 2) { - break; - } - mv.hitratio -= 5; - } - } else { - // highest tier (90 <= acc <= 100) - // count down from 100% to 20% in 5% increments with 40% - // chance to "stop" and use the current accuracy at each - // increment - // gives high accuracy most of the time - mv.hitratio = 100; - while (mv.hitratio > 20) { - if (random.nextInt(10) < 4) { - break; - } - mv.hitratio -= 5; - } - } - } - } - } - - @Override - public void randomizeMoveTypes() { - List<Move> moves = this.getMoves(); - for (Move mv : moves) { - if (mv != null && mv.internalId != 165 && mv.type != null) { - mv.type = randomType(); - } - } - } - - @Override - public void randomizeMoveCategory() { - if (!this.hasPhysicalSpecialSplit()) { - return; - } - List<Move> moves = this.getMoves(); - for (Move mv : moves) { - if (mv != null && mv.internalId != 165 - && mv.category != MoveCategory.STATUS) { - if (random.nextInt(2) == 0) { - mv.category = (mv.category == MoveCategory.PHYSICAL) ? MoveCategory.SPECIAL - : MoveCategory.PHYSICAL; - } - } - } - - } - - private Map<Integer, boolean[]> moveUpdates; - - @Override - public void initMoveUpdates() { - moveUpdates = new TreeMap<Integer, boolean[]>(); - } - - @Override - public void printMoveUpdates() { - log("--Move Updates--"); - List<Move> moves = this.getMoves(); - for (int moveID : moveUpdates.keySet()) { - boolean[] changes = moveUpdates.get(moveID); - Move mv = moves.get(moveID); - List<String> nonTypeChanges = new ArrayList<String>(); - if (changes[0]) { - nonTypeChanges.add(String.format("%d power", mv.power)); - } - if (changes[1]) { - nonTypeChanges.add(String.format("%d PP", mv.pp)); - } - if (changes[2]) { - nonTypeChanges.add(String.format("%.00f%% accuracy", - mv.hitratio)); - } - String logStr = "Made " + mv.name; - // type or not? - if (changes[3]) { - logStr += " be " + mv.type + "-type"; - if (nonTypeChanges.size() > 0) { - logStr += " and"; - } - } - if (nonTypeChanges.size() > 0) { - logStr += " have "; - if (nonTypeChanges.size() == 3) { - logStr += nonTypeChanges.get(0) + ", " - + nonTypeChanges.get(1) + " and " - + nonTypeChanges.get(2); - } else if (nonTypeChanges.size() == 2) { - logStr += nonTypeChanges.get(0) + " and " - + nonTypeChanges.get(1); - } else { - logStr += nonTypeChanges.get(0); - } - } - log(logStr); - } - logBlankLine(); - } - - @Override - public void updateMovesToGen5() { - List<Move> moves = this.getMoves(); - - // gen1 - // Karate Chop => FIGHTING (gen1) - updateMoveType(moves, 2, Type.FIGHTING); - // Razor Wind => 100% accuracy (gen1/2) - updateMoveAccuracy(moves, 13, 100); - // Gust => FLYING (gen1) - updateMoveType(moves, 16, Type.FLYING); - // Wing Attack => 60 power (gen1) - updateMovePower(moves, 17, 60); - // Whirlwind => 100 accuracy - updateMoveAccuracy(moves, 18, 100); - // Fly => 90 power (gen1/2/3) - updateMovePower(moves, 19, 90); - // Bind => 85% accuracy (gen1-4) - updateMoveAccuracy(moves, 20, 85); - // Vine Whip => 15 pp (gen1/2/3) - updateMovePP(moves, 22, 15); - // Jump Kick => 10 pp, 100 power (gen1-4) - updateMovePP(moves, 26, 10); - updateMovePower(moves, 26, 100); - // Sand Attack => GROUND (gen1) - updateMoveType(moves, 28, Type.GROUND); - // Tackle => 50 power, 100% accuracy , gen1-4 - updateMovePower(moves, 33, 50); - updateMoveAccuracy(moves, 33, 100); - // Wrap => 90% accuracy (gen1-4) - updateMoveAccuracy(moves, 35, 90); - // Thrash => 120 power, 10pp (gen1-4) - updateMovePP(moves, 37, 10); - updateMovePower(moves, 37, 120); - // Double-Edge => 120 power (gen1) - updateMovePower(moves, 38, 120); - // Move 44, Bite, becomes dark (but doesn't exist anyway) - // Disable => 100% accuracy (gen1-4) - updateMoveAccuracy(moves, 50, 100); - // Blizzard => 70% accuracy (gen1) - updateMoveAccuracy(moves, 59, 70); - // Move 67, Low Kick, has weight-based power in gen3+ - // Low Kick => 100% accuracy (gen1) - updateMoveAccuracy(moves, 67, 100); - // Absorb => 25pp (gen1/2/3) - updateMovePP(moves, 71, 25); - // Mega Drain => 15pp (gen1/2/3) - updateMovePP(moves, 72, 15); - // Petal Dance => 120power, 10pp (gen1-4) - updateMovePP(moves, 80, 10); - updateMovePower(moves, 80, 120); - // Fire Spin => 35 power, 85% acc (gen1-4) - updateMoveAccuracy(moves, 83, 85); - updateMovePower(moves, 83, 35); - // Rock Throw => 90% accuracy (gen1) - updateMoveAccuracy(moves, 88, 90); - // Dig => 80 power (gen1/2/3) - updateMovePower(moves, 91, 80); - // Toxic => 90% accuracy (gen1-4) - updateMoveAccuracy(moves, 92, 90); - // Hypnosis => 60% accuracy - updateMoveAccuracy(moves, 95, 60); - // Recover => 10pp (gen1/2/3) - updateMovePP(moves, 105, 10); - // SelfDestruct => 200power (gen1) - updateMovePower(moves, 120, 200); - // Clamp => 85% acc (gen1-4) - updateMoveAccuracy(moves, 128, 85); - updateMovePP(moves, 128, 15); - // HJKick => 130 power, 10pp (gen1-4) - updateMovePP(moves, 136, 10); - updateMovePower(moves, 136, 130); - // Glare => 90% acc (gen1-4) - updateMoveAccuracy(moves, 137, 90); - // Poison Gas => 80% acc (gen1-4) - updateMoveAccuracy(moves, 139, 80); - // Flash => 100% acc (gen1/2/3) - updateMoveAccuracy(moves, 148, 100); - // Crabhammer => 90% acc (gen1-4) - updateMoveAccuracy(moves, 152, 90); - // Explosion => 250 power (gen1) - updateMovePower(moves, 153, 250); - // GEN2+ moves only from here - if (moves.size() >= 251) { - // Curse => GHOST (gen2-4) - updateMoveType(moves, 174, Type.GHOST); - // Cotton Spore => 100% acc (gen2-4) - updateMoveAccuracy(moves, 178, 100); - // Scary Face => 100% acc (gen2-4) - updateMoveAccuracy(moves, 184, 100); - // Zap Cannon => 120 power (gen2-3) - updateMovePower(moves, 192, 120); - // Bone Rush => 90% acc (gen2-4) - updateMoveAccuracy(moves, 198, 90); - // Outrage => 120 power (gen2-3) - updateMovePower(moves, 200, 120); - updateMovePP(moves, 200, 10); - // Giga Drain => 10pp (gen2-3), 75 power (gen2-4) - updateMovePP(moves, 202, 10); - updateMovePower(moves, 202, 75); - // Fury Cutter => 20 power (gen2-4) - updateMovePower(moves, 210, 20); - // Future Sight => 10 pp, 100 power, 100% acc (gen2-4) - updateMovePP(moves, 248, 10); - updateMovePower(moves, 248, 100); - updateMoveAccuracy(moves, 248, 100); - // Rock Smash => 40 power (gen2-3) - updateMovePower(moves, 249, 40); - // Whirlpool => 35 pow, 85% acc (gen2-4) - updateMovePower(moves, 250, 35); - updateMoveAccuracy(moves, 250, 85); - } - // GEN3+ only moves from here - if (moves.size() >= 354) { - // Uproar => 90 power (gen3-4) - updateMovePower(moves, 253, 90); - updateMovePP(moves, 254, 20); - updateMovePower(moves, 291, 80); - // Sand Tomb => 35 pow, 85% acc (gen3-4) - updateMovePower(moves, 328, 35); - updateMoveAccuracy(moves, 328, 85); - // Bullet Seed => 25 power (gen3-4) - updateMovePower(moves, 331, 25); - // Icicle Spear => 25 power (gen3-4) - updateMovePower(moves, 333, 25); - // Covet => 60 power (gen3-4) - updateMovePower(moves, 343, 60); - updateMovePower(moves, 348, 90); - // Rock Blast => 90% acc (gen3-4) - updateMoveAccuracy(moves, 350, 90); - // Doom Desire => 140 pow, 100% acc, gen3-4 - updateMovePower(moves, 353, 140); - updateMoveAccuracy(moves, 353, 100); - } - // GEN4+ only moves from here - if (moves.size() >= 467) { - // Feint => 30 pow - updateMovePower(moves, 364, 30); - // Last Resort => 140 pow - updateMovePower(moves, 387, 140); - // Drain Punch => 10 pp, 75 pow - updateMovePP(moves, 409, 10); - updateMovePower(moves, 409, 75); - // Magma Storm => 75% acc - updateMoveAccuracy(moves, 463, 75); - } - } - - @Override - public void updateMovesToGen6() { - List<Move> moves = this.getMoves(); - - // gen 1 - // Swords Dance 20 PP - updateMovePP(moves, 14, 20); - - // Vine Whip 25 PP, 45 Power - updateMovePP(moves, 22, 25); - updateMovePower(moves, 22, 45); - - // Pin Missile 25 Power, 95% Accuracy - updateMovePower(moves, 42, 25); - updateMoveAccuracy(moves, 42, 95); - - // Flamethrower 90 Power - updateMovePower(moves, 53, 90); - - // Hydro Pump 110 Power - updateMovePower(moves, 56, 110); - - // Surf 90 Power - updateMovePower(moves, 57, 90); - - // Ice Beam 90 Power - updateMovePower(moves, 58, 90); - - // Blizzard 110 Power - updateMovePower(moves, 59, 110); - - // Growth 20 PP - updateMovePP(moves, 74, 20); - - // Thunderbolt 90 Power - updateMovePower(moves, 85, 90); - - // Thunder 110 Power - updateMovePower(moves, 87, 110); - - // Minimize 10 PP - updateMovePP(moves, 107, 10); - - // Barrier 20 PP - updateMovePP(moves, 112, 20); - - // Lick 30 Power - updateMovePower(moves, 122, 30); - - // Smog 30 Power - updateMovePower(moves, 123, 30); - - // Fire Blast 110 Power - updateMovePower(moves, 126, 110); - - // Skull Bash 10 PP, 130 Power - updateMovePP(moves, 130, 10); - updateMovePower(moves, 130, 130); - - // Glare 100% Accuracy - updateMoveAccuracy(moves, 137, 100); - - // Poison Gas 90% Accuracy - updateMoveAccuracy(moves, 139, 90); + private static final String tnamesFile = "trainernames.txt"; + private static final String tclassesFile = "trainerclasses.txt"; + private static final String nnamesFile = "nicknames.txt"; + + private boolean restrictionsSet; + protected List<Pokemon> mainPokemonList; + protected List<Pokemon> noLegendaryList, onlyLegendaryList; + protected final Random random; + protected PrintStream logStream; + + /* Constructor */ + + public AbstractRomHandler(Random random, PrintStream logStream) { + this.random = random; + this.logStream = logStream; + } + + /* Public Methods, implemented here for all gens */ + + protected void checkPokemonRestrictions() { + if (!restrictionsSet) { + setPokemonPool(null); + } + } + + public void setPokemonPool(GenRestrictions restrictions) { + restrictionsSet = true; + mainPokemonList = this.allPokemonWithoutNull(); + if (restrictions != null) { + mainPokemonList = new ArrayList<Pokemon>(); + List<Pokemon> allPokemon = this.getPokemon(); + + if (restrictions.allow_gen1) { + addPokesFromRange(mainPokemonList, allPokemon, 1, 151); + if (restrictions.assoc_g1_g2 && allPokemon.size() > 251) { + addEvosFromRange(mainPokemonList, 1, 151, 152, 251); + } + if (restrictions.assoc_g1_g4 && allPokemon.size() > 493) { + addEvosFromRange(mainPokemonList, 1, 151, 387, 493); + } + } + + if (restrictions.allow_gen2 && allPokemon.size() > 251) { + addPokesFromRange(mainPokemonList, allPokemon, 152, 251); + if (restrictions.assoc_g2_g1) { + addEvosFromRange(mainPokemonList, 152, 251, 1, 151); + } + if (restrictions.assoc_g2_g3 && allPokemon.size() > 386) { + addEvosFromRange(mainPokemonList, 152, 251, 252, 386); + } + if (restrictions.assoc_g2_g4 && allPokemon.size() > 493) { + addEvosFromRange(mainPokemonList, 152, 251, 387, 493); + } + } + + if (restrictions.allow_gen3 && allPokemon.size() > 386) { + addPokesFromRange(mainPokemonList, allPokemon, 252, 386); + if (restrictions.assoc_g3_g2) { + addEvosFromRange(mainPokemonList, 252, 386, 152, 251); + } + if (restrictions.assoc_g3_g4 && allPokemon.size() > 493) { + addEvosFromRange(mainPokemonList, 252, 386, 387, 493); + } + } + + if (restrictions.allow_gen4 && allPokemon.size() > 493) { + addPokesFromRange(mainPokemonList, allPokemon, 387, 493); + if (restrictions.assoc_g4_g1) { + addEvosFromRange(mainPokemonList, 387, 493, 1, 151); + } + if (restrictions.assoc_g4_g2) { + addEvosFromRange(mainPokemonList, 387, 493, 152, 251); + } + if (restrictions.assoc_g4_g3) { + addEvosFromRange(mainPokemonList, 387, 493, 252, 386); + } + } + + if (restrictions.allow_gen5 && allPokemon.size() > 649) { + addPokesFromRange(mainPokemonList, allPokemon, 494, 649); + } + } + + noLegendaryList = new ArrayList<Pokemon>(); + onlyLegendaryList = new ArrayList<Pokemon>(); + + for (Pokemon p : mainPokemonList) { + if (p.isLegendary()) { + onlyLegendaryList.add(p); + } else { + noLegendaryList.add(p); + } + } + } + + private void addPokesFromRange(List<Pokemon> pokemonPool, List<Pokemon> allPokemon, int range_min, int range_max) { + for (int i = range_min; i <= range_max; i++) { + if (!pokemonPool.contains(allPokemon.get(i))) { + pokemonPool.add(allPokemon.get(i)); + } + } + } + + private void addEvosFromRange(List<Pokemon> pokemonPool, int first_min, int first_max, int second_min, + int second_max) { + Set<Pokemon> newPokemon = new TreeSet<Pokemon>(); + for (Pokemon pk : pokemonPool) { + if (pk.number >= first_min && pk.number <= first_max) { + for (Evolution ev : pk.evolutionsFrom) { + if (ev.to.number >= second_min && ev.to.number <= second_max) { + if (!pokemonPool.contains(ev.to) && !newPokemon.contains(ev.to)) { + newPokemon.add(ev.to); + } + } + } + + for (Evolution ev : pk.evolutionsTo) { + if (ev.from.number >= second_min && ev.from.number <= second_max) { + if (!pokemonPool.contains(ev.from) && !newPokemon.contains(ev.from)) { + newPokemon.add(ev.from); + } + } + } + } + } + + pokemonPool.addAll(newPokemon); + } + + @Override + public void randomizePokemonStats(boolean evolutionSanity) { + List<Pokemon> allPokes = this.getPokemon(); + + if (evolutionSanity) { + for (Pokemon pk : allPokes) { + if (pk != null) { + pk.temporaryFlag = false; + } + } + // Spread stats up MOST evolutions. + Set<Pokemon> dontCopyPokes = RomFunctions.getBasicOrNoCopyPokemon(this); + + for (Pokemon pk : dontCopyPokes) { + pk.randomizeStatsWithinBST(this.random); + pk.temporaryFlag = true; + } + + // go "up" evolutions looking for pre-evos to do first + for (Pokemon pk : allPokes) { + if (pk != null && !pk.temporaryFlag) { + // Non-randomized pokes at this point must have + // a linear chain of single evolutions down to + // a randomized poke. + Stack<Evolution> currentStack = new Stack<Evolution>(); + Evolution ev = pk.evolutionsTo.get(0); + while (!ev.from.temporaryFlag) { + currentStack.push(ev); + ev = ev.from.evolutionsTo.get(0); + } + + // Now "ev" is set to an evolution from a randomized Pokemon + // to a non-randomized. + // Carry stats up, then do the same for everything left on + // the stack. + ev.to.copyRandomizedStatsUpEvolution(ev.from); + ev.to.temporaryFlag = true; + while (!currentStack.isEmpty()) { + ev = currentStack.pop(); + ev.to.copyRandomizedStatsUpEvolution(ev.from); + ev.to.temporaryFlag = true; + } + } + } + } else { + for (Pokemon pk : allPokes) { + if (pk != null) { + pk.randomizeStatsWithinBST(this.random); + } + } + } + + } + + protected void applyCamelCaseNames() { + List<Pokemon> pokes = getPokemon(); + for (Pokemon pkmn : pokes) { + if (pkmn == null) { + continue; + } + pkmn.name = RomFunctions.camelCase(pkmn.name); + } + + } + + @Override + public void minimumCatchRate(int rateNonLegendary, int rateLegendary) { + List<Pokemon> pokes = getPokemon(); + for (Pokemon pkmn : pokes) { + if (pkmn == null) { + continue; + } + int minCatchRate = pkmn.isLegendary() ? rateLegendary : rateNonLegendary; + pkmn.catchRate = Math.max(pkmn.catchRate, minCatchRate); + } + + } + + @Override + public void standardizeEXPCurves() { + List<Pokemon> pokes = getPokemon(); + for (Pokemon pkmn : pokes) { + if (pkmn == null) { + continue; + } + pkmn.growthCurve = pkmn.isLegendary() ? ExpCurve.SLOW : ExpCurve.MEDIUM_FAST; + } + } + + @Override + public void randomizePokemonTypes(boolean evolutionSanity) { + List<Pokemon> allPokes = this.getPokemon(); + if (evolutionSanity) { + // Type randomization with evolution sanity + for (Pokemon pk : allPokes) { + if (pk != null) { + pk.temporaryFlag = false; + } + } + + // Step 1: Basic or Excluded From Copying Pokemon + // A Basic/EFC pokemon has a 35% chance of a second type if it has + // an evolution that copies type/stats, a 50% chance otherwise + Set<Pokemon> dontCopyPokes = RomFunctions.getBasicOrNoCopyPokemon(this); + + for (Pokemon pk : dontCopyPokes) { + pk.primaryType = randomType(); + pk.secondaryType = null; + if (pk.evolutionsFrom.size() == 1 && pk.evolutionsFrom.get(0).carryStats) { + if (this.random.nextDouble() < 0.35) { + pk.secondaryType = randomType(); + while (pk.secondaryType == pk.primaryType) { + pk.secondaryType = randomType(); + } + } + } else { + if (this.random.nextDouble() < 0.5) { + pk.secondaryType = randomType(); + while (pk.secondaryType == pk.primaryType) { + pk.secondaryType = randomType(); + } + } + } + pk.temporaryFlag = true; + } + + // go "up" evolutions looking for pre-evos to do first + Set<Pokemon> middleEvos = RomFunctions.getMiddleEvolutions(this); + for (Pokemon pk : allPokes) { + if (pk != null && !pk.temporaryFlag) { + // Non-randomized pokes at this point must have + // a linear chain of single evolutions down to + // a randomized poke. + Stack<Evolution> currentStack = new Stack<Evolution>(); + Evolution ev = pk.evolutionsTo.get(0); + while (!ev.from.temporaryFlag) { + currentStack.push(ev); + ev = ev.from.evolutionsTo.get(0); + } + + // Now "ev" is set to an evolution from a randomized Pokemon + // to a non-randomized. + // Carry types up, add the chance to add secondary, then + // continue up the stack as required. + while (true) { + Pokemon to = ev.to; + Pokemon from = ev.from; + to.primaryType = from.primaryType; + to.secondaryType = from.secondaryType; + + if (to.secondaryType == null) { + double chance = middleEvos.contains(to) ? 0.15 : 0.25; + if (this.random.nextDouble() < chance) { + to.secondaryType = randomType(); + while (to.secondaryType == to.primaryType) { + to.secondaryType = randomType(); + } + } + } + + to.temporaryFlag = true; + + if (currentStack.isEmpty()) { + break; + } else { + ev = currentStack.pop(); + } + } + } + } + } else { + // Entirely random types + for (Pokemon pkmn : allPokes) { + if (pkmn != null) { + pkmn.primaryType = randomType(); + pkmn.secondaryType = null; + if (this.random.nextDouble() < 0.5) { + pkmn.secondaryType = randomType(); + while (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = randomType(); + } + } + } + } + } + } + + private static final int WONDER_GUARD_INDEX = 25; + + @Override + public void randomizeAbilities(boolean allowWonderGuard) { + // Abilities don't exist in some games... + if (this.abilitiesPerPokemon() == 0) { + return; + } + + // Deal with "natural" abilities first regardless + List<Pokemon> allPokes = this.getPokemon(); + int maxAbility = this.highestAbilityIndex(); + for (Pokemon pk : allPokes) { + if (pk == null) { + continue; + } + + // Wonder Guard? + if (pk.ability1 != WONDER_GUARD_INDEX && pk.ability2 != WONDER_GUARD_INDEX + && pk.ability3 != WONDER_GUARD_INDEX) { + // Pick first ability + pk.ability1 = this.random.nextInt(maxAbility) + 1; + // Wonder guard block + if (!allowWonderGuard) { + while (pk.ability1 == WONDER_GUARD_INDEX) { + pk.ability1 = this.random.nextInt(maxAbility) + 1; + } + } + + // Second ability? + if (this.random.nextDouble() < 0.5) { + // Yes, second ability + pk.ability2 = this.random.nextInt(maxAbility) + 1; + // Wonder guard? Also block first ability from reappearing + if (allowWonderGuard) { + while (pk.ability2 == pk.ability1) { + pk.ability2 = this.random.nextInt(maxAbility) + 1; + } + } else { + while (pk.ability2 == WONDER_GUARD_INDEX || pk.ability2 == pk.ability1) { + pk.ability2 = this.random.nextInt(maxAbility) + 1; + } + } + } else { + // Nope + pk.ability2 = 0; + } + } + } + + // DW Abilities? + if (this.abilitiesPerPokemon() == 3) { + // Give a random DW ability to every Pokemon + for (Pokemon pk : allPokes) { + if (pk == null) { + continue; + } + if (pk.ability1 != WONDER_GUARD_INDEX && pk.ability2 != WONDER_GUARD_INDEX + && pk.ability3 != WONDER_GUARD_INDEX) { + pk.ability3 = this.random.nextInt(maxAbility) + 1; + // Wonder guard? Also block other abilities from reappearing + if (allowWonderGuard) { + while (pk.ability3 == pk.ability1 || pk.ability3 == pk.ability2) { + pk.ability3 = this.random.nextInt(maxAbility) + 1; + } + } else { + while (pk.ability3 == WONDER_GUARD_INDEX || pk.ability3 == pk.ability1 + || pk.ability3 == pk.ability2) { + pk.ability3 = this.random.nextInt(maxAbility) + 1; + } + } + } + } + } + } + + public Pokemon randomPokemon() { + checkPokemonRestrictions(); + return mainPokemonList.get(this.random.nextInt(mainPokemonList.size())); + } + + @Override + public Pokemon randomNonLegendaryPokemon() { + checkPokemonRestrictions(); + return noLegendaryList.get(this.random.nextInt(noLegendaryList.size())); + } + + @Override + public Pokemon randomLegendaryPokemon() { + checkPokemonRestrictions(); + return onlyLegendaryList.get(this.random.nextInt(onlyLegendaryList.size())); + } + + private List<Pokemon> twoEvoPokes; + + @Override + public Pokemon random2EvosPokemon() { + if (twoEvoPokes == null) { + // Prepare the list + twoEvoPokes = new ArrayList<Pokemon>(); + List<Pokemon> allPokes = this.getPokemon(); + for (Pokemon pk : allPokes) { + if (pk != null && pk.evolutionsTo.size() == 0 && pk.evolutionsFrom.size() > 0) { + // Potential candidate + for (Evolution ev : pk.evolutionsFrom) { + // If any of the targets here evolve, the original + // Pokemon has 2+ stages. + if (ev.to.evolutionsFrom.size() > 0) { + twoEvoPokes.add(pk); + break; + } + } + } + } + } + return twoEvoPokes.get(this.random.nextInt(twoEvoPokes.size())); + } + + @Override + public void randomEncounters(boolean useTimeOfDay, boolean catchEmAll, boolean typeThemed, boolean usePowerLevels, + boolean noLegendaries) { + checkPokemonRestrictions(); + List<EncounterSet> currentEncounters = this.getEncounters(useTimeOfDay); + List<Pokemon> banned = this.bannedForWildEncounters(); + // Assume EITHER catch em all OR type themed OR match strength for now + if (catchEmAll) { + + List<Pokemon> allPokes = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) : new ArrayList<Pokemon>( + mainPokemonList); + allPokes.removeAll(banned); + for (EncounterSet area : currentEncounters) { + List<Pokemon> pickablePokemon = allPokes; + if (area.bannedPokemon.size() > 0) { + pickablePokemon = new ArrayList<Pokemon>(allPokes); + pickablePokemon.removeAll(area.bannedPokemon); + } + for (Encounter enc : area.encounters) { + // Pick a random pokemon + if (pickablePokemon.size() == 0) { + // Only banned pokes are left, ignore them and pick + // something else for now. + List<Pokemon> tempPickable = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) + : new ArrayList<Pokemon>(mainPokemonList); + tempPickable.removeAll(banned); + tempPickable.removeAll(area.bannedPokemon); + if (tempPickable.size() == 0) { + throw new RuntimeException("ERROR: Couldn't replace a Pokemon!"); + } + int picked = this.random.nextInt(tempPickable.size()); + enc.pokemon = tempPickable.get(picked); + } else { + // Picked this Pokemon, remove it + int picked = this.random.nextInt(pickablePokemon.size()); + enc.pokemon = pickablePokemon.get(picked); + pickablePokemon.remove(picked); + if (allPokes != pickablePokemon) { + allPokes.remove(enc.pokemon); + } + if (allPokes.size() == 0) { + // Start again + allPokes.addAll(noLegendaries ? noLegendaryList : mainPokemonList); + allPokes.removeAll(banned); + if (pickablePokemon != allPokes) { + pickablePokemon.addAll(allPokes); + pickablePokemon.removeAll(area.bannedPokemon); + } + } + } + } + } + } else if (typeThemed) { + Map<Type, List<Pokemon>> cachedPokeLists = new TreeMap<Type, List<Pokemon>>(); + for (EncounterSet area : currentEncounters) { + List<Pokemon> possiblePokemon = null; + int iterLoops = 0; + while (possiblePokemon == null && iterLoops < 10000) { + Type areaTheme = randomType(); + if (!cachedPokeLists.containsKey(areaTheme)) { + List<Pokemon> pType = pokemonOfType(areaTheme, noLegendaries); + pType.removeAll(banned); + cachedPokeLists.put(areaTheme, pType); + } + possiblePokemon = cachedPokeLists.get(areaTheme); + if (area.bannedPokemon.size() > 0) { + possiblePokemon = new ArrayList<Pokemon>(possiblePokemon); + possiblePokemon.removeAll(area.bannedPokemon); + } + if (possiblePokemon.size() == 0) { + // Can't use this type for this area + possiblePokemon = null; + } + iterLoops++; + } + if (possiblePokemon == null) { + throw new RuntimeException("Could not randomize an area in a reasonable amount of attempts."); + } + for (Encounter enc : area.encounters) { + // Pick a random themed pokemon + enc.pokemon = possiblePokemon.get(this.random.nextInt(possiblePokemon.size())); + } + } + } else if (usePowerLevels) { + List<Pokemon> allowedPokes = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) + : new ArrayList<Pokemon>(mainPokemonList); + allowedPokes.removeAll(banned); + for (EncounterSet area : currentEncounters) { + List<Pokemon> localAllowed = allowedPokes; + if (area.bannedPokemon.size() > 0) { + localAllowed = new ArrayList<Pokemon>(allowedPokes); + localAllowed.removeAll(area.bannedPokemon); + } + for (Encounter enc : area.encounters) { + enc.pokemon = pickWildPowerLvlReplacement(localAllowed, enc.pokemon, false, null); + } + } + } else { + // Entirely random + for (EncounterSet area : currentEncounters) { + for (Encounter enc : area.encounters) { + enc.pokemon = noLegendaries ? randomNonLegendaryPokemon() : randomPokemon(); + while (banned.contains(enc.pokemon) || area.bannedPokemon.contains(enc.pokemon)) { + enc.pokemon = noLegendaries ? randomNonLegendaryPokemon() : randomPokemon(); + } + } + } + } + + setEncounters(useTimeOfDay, currentEncounters); + } + + @Override + public void area1to1Encounters(boolean useTimeOfDay, boolean catchEmAll, boolean typeThemed, + boolean usePowerLevels, boolean noLegendaries) { + checkPokemonRestrictions(); + List<EncounterSet> currentEncounters = this.getEncounters(useTimeOfDay); + List<Pokemon> banned = this.bannedForWildEncounters(); + // Assume EITHER catch em all OR type themed for now + if (catchEmAll) { + List<Pokemon> allPokes = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) : new ArrayList<Pokemon>( + mainPokemonList); + allPokes.removeAll(banned); + for (EncounterSet area : currentEncounters) { + // Poke-set + Set<Pokemon> inArea = pokemonInArea(area); + // Build area map using catch em all + Map<Pokemon, Pokemon> areaMap = new TreeMap<Pokemon, Pokemon>(); + List<Pokemon> pickablePokemon = allPokes; + if (area.bannedPokemon.size() > 0) { + pickablePokemon = new ArrayList<Pokemon>(allPokes); + pickablePokemon.removeAll(area.bannedPokemon); + } + for (Pokemon areaPk : inArea) { + if (pickablePokemon.size() == 0) { + // No more pickable pokes left, take a random one + List<Pokemon> tempPickable = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) + : new ArrayList<Pokemon>(mainPokemonList); + tempPickable.removeAll(banned); + tempPickable.removeAll(area.bannedPokemon); + if (tempPickable.size() == 0) { + throw new RuntimeException("ERROR: Couldn't replace a Pokemon!"); + } + int picked = this.random.nextInt(tempPickable.size()); + Pokemon pickedMN = tempPickable.get(picked); + areaMap.put(areaPk, pickedMN); + } else { + int picked = this.random.nextInt(allPokes.size()); + Pokemon pickedMN = allPokes.get(picked); + areaMap.put(areaPk, pickedMN); + pickablePokemon.remove(picked); + if (allPokes != pickablePokemon) { + allPokes.remove(pickedMN); + } + if (allPokes.size() == 0) { + // Start again + allPokes.addAll(noLegendaries ? noLegendaryList : mainPokemonList); + allPokes.removeAll(banned); + if (pickablePokemon != allPokes) { + pickablePokemon.addAll(allPokes); + pickablePokemon.removeAll(area.bannedPokemon); + } + } + } + } + for (Encounter enc : area.encounters) { + // Apply the map + enc.pokemon = areaMap.get(enc.pokemon); + } + } + } else if (typeThemed) { + Map<Type, List<Pokemon>> cachedPokeLists = new TreeMap<Type, List<Pokemon>>(); + for (EncounterSet area : currentEncounters) { + // Poke-set + Set<Pokemon> inArea = pokemonInArea(area); + List<Pokemon> possiblePokemon = null; + int iterLoops = 0; + while (possiblePokemon == null && iterLoops < 10000) { + Type areaTheme = randomType(); + if (!cachedPokeLists.containsKey(areaTheme)) { + List<Pokemon> pType = pokemonOfType(areaTheme, noLegendaries); + pType.removeAll(banned); + cachedPokeLists.put(areaTheme, pType); + } + possiblePokemon = cachedPokeLists.get(areaTheme); + if (area.bannedPokemon.size() > 0) { + possiblePokemon = new ArrayList<Pokemon>(possiblePokemon); + possiblePokemon.removeAll(area.bannedPokemon); + } + if (possiblePokemon.size() < inArea.size()) { + // Can't use this type for this area + possiblePokemon = null; + } + iterLoops++; + } + if (possiblePokemon == null) { + throw new RuntimeException("Could not randomize an area in a reasonable amount of attempts."); + } + + // Build area map using type theme. + Map<Pokemon, Pokemon> areaMap = new TreeMap<Pokemon, Pokemon>(); + for (Pokemon areaPk : inArea) { + int picked = this.random.nextInt(possiblePokemon.size()); + Pokemon pickedMN = possiblePokemon.get(picked); + areaMap.put(areaPk, pickedMN); + possiblePokemon.remove(picked); + } + for (Encounter enc : area.encounters) { + // Apply the map + enc.pokemon = areaMap.get(enc.pokemon); + } + } + } else if (usePowerLevels) { + List<Pokemon> allowedPokes = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) + : new ArrayList<Pokemon>(mainPokemonList); + allowedPokes.removeAll(banned); + for (EncounterSet area : currentEncounters) { + // Poke-set + Set<Pokemon> inArea = pokemonInArea(area); + // Build area map using randoms + Map<Pokemon, Pokemon> areaMap = new TreeMap<Pokemon, Pokemon>(); + List<Pokemon> usedPks = new ArrayList<Pokemon>(); + List<Pokemon> localAllowed = allowedPokes; + if (area.bannedPokemon.size() > 0) { + localAllowed = new ArrayList<Pokemon>(allowedPokes); + localAllowed.removeAll(area.bannedPokemon); + } + for (Pokemon areaPk : inArea) { + Pokemon picked = pickWildPowerLvlReplacement(localAllowed, areaPk, false, usedPks); + areaMap.put(areaPk, picked); + usedPks.add(picked); + } + for (Encounter enc : area.encounters) { + // Apply the map + enc.pokemon = areaMap.get(enc.pokemon); + } + } + } else { + // Entirely random + for (EncounterSet area : currentEncounters) { + // Poke-set + Set<Pokemon> inArea = pokemonInArea(area); + // Build area map using randoms + Map<Pokemon, Pokemon> areaMap = new TreeMap<Pokemon, Pokemon>(); + for (Pokemon areaPk : inArea) { + Pokemon picked = noLegendaries ? randomNonLegendaryPokemon() : randomPokemon(); + while (areaMap.containsValue(picked) || banned.contains(picked) + || area.bannedPokemon.contains(picked)) { + picked = noLegendaries ? randomNonLegendaryPokemon() : randomPokemon(); + } + areaMap.put(areaPk, picked); + } + for (Encounter enc : area.encounters) { + // Apply the map + enc.pokemon = areaMap.get(enc.pokemon); + } + } + } + + setEncounters(useTimeOfDay, currentEncounters); + + } + + @Override + public void game1to1Encounters(boolean useTimeOfDay, boolean usePowerLevels, boolean noLegendaries) { + checkPokemonRestrictions(); + // Build the full 1-to-1 map + Map<Pokemon, Pokemon> translateMap = new TreeMap<Pokemon, Pokemon>(); + List<Pokemon> remainingLeft = allPokemonWithoutNull(); + List<Pokemon> remainingRight = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) + : new ArrayList<Pokemon>(mainPokemonList); + List<Pokemon> banned = this.bannedForWildEncounters(); + // Banned pokemon should be mapped to themselves + for (Pokemon bannedPK : banned) { + translateMap.put(bannedPK, bannedPK); + remainingLeft.remove(bannedPK); + remainingRight.remove(bannedPK); + } + while (remainingLeft.isEmpty() == false) { + if (usePowerLevels) { + int pickedLeft = this.random.nextInt(remainingLeft.size()); + Pokemon pickedLeftP = remainingLeft.remove(pickedLeft); + Pokemon pickedRightP = null; + if (remainingRight.size() == 1) { + // pick this (it may or may not be the same poke) + pickedRightP = remainingRight.get(0); + } else { + // pick on power level with the current one blocked + pickedRightP = pickWildPowerLvlReplacement(remainingRight, pickedLeftP, true, null); + } + remainingRight.remove(pickedRightP); + translateMap.put(pickedLeftP, pickedRightP); + } else { + int pickedLeft = this.random.nextInt(remainingLeft.size()); + int pickedRight = this.random.nextInt(remainingRight.size()); + Pokemon pickedLeftP = remainingLeft.remove(pickedLeft); + Pokemon pickedRightP = remainingRight.get(pickedRight); + while (pickedLeftP.number == pickedRightP.number && remainingRight.size() != 1) { + // Reroll for a different pokemon if at all possible + pickedRight = this.random.nextInt(remainingRight.size()); + pickedRightP = remainingRight.get(pickedRight); + } + remainingRight.remove(pickedRight); + translateMap.put(pickedLeftP, pickedRightP); + } + if (remainingRight.size() == 0) { + // restart + remainingRight.addAll(noLegendaries ? noLegendaryList : mainPokemonList); + remainingRight.removeAll(banned); + } + } + + // Map remaining to themselves just in case + List<Pokemon> allPokes = allPokemonWithoutNull(); + for (Pokemon poke : allPokes) { + if (!translateMap.containsKey(poke)) { + translateMap.put(poke, poke); + } + } + + List<EncounterSet> currentEncounters = this.getEncounters(useTimeOfDay); + + for (EncounterSet area : currentEncounters) { + for (Encounter enc : area.encounters) { + // Apply the map + enc.pokemon = translateMap.get(enc.pokemon); + if (area.bannedPokemon.contains(enc.pokemon)) { + // Ignore the map and put a random non-banned poke + List<Pokemon> tempPickable = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) + : new ArrayList<Pokemon>(mainPokemonList); + tempPickable.removeAll(banned); + tempPickable.removeAll(area.bannedPokemon); + if (tempPickable.size() == 0) { + throw new RuntimeException("ERROR: Couldn't replace a Pokemon!"); + } + if (usePowerLevels) { + enc.pokemon = pickWildPowerLvlReplacement(tempPickable, enc.pokemon, false, null); + } else { + int picked = this.random.nextInt(tempPickable.size()); + enc.pokemon = tempPickable.get(picked); + } + } + } + } + + setEncounters(useTimeOfDay, currentEncounters); + + } + + @Override + public void randomizeTrainerPokes(boolean rivalCarriesStarter, boolean usePowerLevels, boolean noLegendaries, + boolean noEarlyWonderGuard) { + checkPokemonRestrictions(); + List<Trainer> currentTrainers = this.getTrainers(); + cachedReplacementLists = new TreeMap<Type, List<Pokemon>>(); + cachedAllList = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) : new ArrayList<Pokemon>( + mainPokemonList); + + // Fully random is easy enough - randomize then worry about rival + // carrying starter at the end + for (Trainer t : currentTrainers) { + if (t.tag != null && t.tag.equals("IRIVAL")) { + continue; // skip + } + for (TrainerPokemon tp : t.pokemon) { + boolean wgAllowed = (!noEarlyWonderGuard) || tp.level >= 20; + tp.pokemon = pickReplacement(tp.pokemon, usePowerLevels, null, noLegendaries, wgAllowed); + } + } + + // Rival carries starter? + if (rivalCarriesStarter) { + rivalCarriesStarterUpdate(currentTrainers, "RIVAL", 1); + rivalCarriesStarterUpdate(currentTrainers, "FRIEND", 2); + } + + // Save it all up + this.setTrainers(currentTrainers); + } + + @Override + public void typeThemeTrainerPokes(boolean rivalCarriesStarter, boolean usePowerLevels, boolean weightByFrequency, + boolean noLegendaries, boolean noEarlyWonderGuard) { + checkPokemonRestrictions(); + List<Trainer> currentTrainers = this.getTrainers(); + cachedReplacementLists = new TreeMap<Type, List<Pokemon>>(); + cachedAllList = noLegendaries ? new ArrayList<Pokemon>(noLegendaryList) : new ArrayList<Pokemon>( + mainPokemonList); + typeWeightings = new TreeMap<Type, Integer>(); + totalTypeWeighting = 0; + + // Construct groupings for types + // Anything starting with GYM or ELITE or CHAMPION is a group + Set<Trainer> assignedTrainers = new TreeSet<Trainer>(); + Map<String, List<Trainer>> groups = new TreeMap<String, List<Trainer>>(); + for (Trainer t : currentTrainers) { + if (t.tag != null && t.tag.equals("IRIVAL")) { + continue; // skip + } + String group = t.tag == null ? "" : t.tag; + if (group.contains("-")) { + group = group.substring(0, group.indexOf('-')); + } + if (group.startsWith("GYM") || group.startsWith("ELITE") || group.startsWith("CHAMPION") + || group.startsWith("THEMED")) { + // Yep this is a group + if (!groups.containsKey(group)) { + groups.put(group, new ArrayList<Trainer>()); + } + groups.get(group).add(t); + assignedTrainers.add(t); + } else if (group.startsWith("GIO")) { + // Giovanni has same grouping as his gym, gym 8 + if (!groups.containsKey("GYM8")) { + groups.put("GYM8", new ArrayList<Trainer>()); + } + groups.get("GYM8").add(t); + assignedTrainers.add(t); + } + } + + // Give a type to each group + // Gym & elite types have to be unique + // So do uber types, including the type we pick for champion + Set<Type> usedGymTypes = new TreeSet<Type>(); + Set<Type> usedEliteTypes = new TreeSet<Type>(); + Set<Type> usedUberTypes = new TreeSet<Type>(); + for (String group : groups.keySet()) { + List<Trainer> trainersInGroup = groups.get(group); + Type typeForGroup = pickType(weightByFrequency, noLegendaries); + if (group.startsWith("GYM")) { + while (usedGymTypes.contains(typeForGroup)) { + typeForGroup = pickType(weightByFrequency, noLegendaries); + } + usedGymTypes.add(typeForGroup); + } + if (group.startsWith("ELITE")) { + while (usedEliteTypes.contains(typeForGroup)) { + typeForGroup = pickType(weightByFrequency, noLegendaries); + } + usedEliteTypes.add(typeForGroup); + } + if (group.equals("CHAMPION")) { + usedUberTypes.add(typeForGroup); + } + // Themed groups just have a theme, no special criteria + for (Trainer t : trainersInGroup) { + for (TrainerPokemon tp : t.pokemon) { + boolean wgAllowed = (!noEarlyWonderGuard) || tp.level >= 20; + tp.pokemon = pickReplacement(tp.pokemon, usePowerLevels, typeForGroup, noLegendaries, wgAllowed); + } + } + } + + // Give a type to each unassigned trainer + for (Trainer t : currentTrainers) { + if (t.tag != null && t.tag.equals("IRIVAL")) { + continue; // skip + } + + if (!assignedTrainers.contains(t)) { + Type typeForTrainer = pickType(weightByFrequency, noLegendaries); + // Ubers: can't have the same type as each other + if (t.tag != null && t.tag.equals("UBER")) { + while (usedUberTypes.contains(typeForTrainer)) { + typeForTrainer = pickType(weightByFrequency, noLegendaries); + } + usedUberTypes.add(typeForTrainer); + } + for (TrainerPokemon tp : t.pokemon) { + boolean shedAllowed = (!noEarlyWonderGuard) || tp.level >= 20; + tp.pokemon = pickReplacement(tp.pokemon, usePowerLevels, typeForTrainer, noLegendaries, shedAllowed); + } + } + } + + // Rival carries starter? + if (rivalCarriesStarter) { + rivalCarriesStarterUpdate(currentTrainers, "RIVAL", 1); + rivalCarriesStarterUpdate(currentTrainers, "FRIEND", 2); + } + + // Save it all up + this.setTrainers(currentTrainers); + } + + @Override + public void randomizeMovesLearnt(boolean typeThemed, boolean noBroken, boolean forceFourStartingMoves) { + // Get current sets + Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); + List<Integer> allBanned = new ArrayList<Integer>(); + List<Integer> hms = this.getHMMoves(); + allBanned.addAll(hms); + @SuppressWarnings("unchecked") + List<Integer> banned = noBroken ? this.getGameBreakingMoves() : Collections.EMPTY_LIST; + allBanned.addAll(banned); + allBanned.addAll(this.getMovesBannedFromLevelup()); + for (Pokemon pkmn : movesets.keySet()) { + Set<Integer> learnt = new TreeSet<Integer>(); + List<MoveLearnt> moves = movesets.get(pkmn); + // 4 starting moves? + if (forceFourStartingMoves) { + int lv1count = 0; + for (MoveLearnt ml : moves) { + if (ml.level == 1) { + lv1count++; + } + } + if (lv1count < 4) { + for (int i = 0; i < 4 - lv1count; i++) { + MoveLearnt fakeLv1 = new MoveLearnt(); + fakeLv1.level = 1; + fakeLv1.move = 0; + moves.add(0, fakeLv1); + } + } + } + // Last level 1 move should be replaced with a damaging one + int damagingMove = pickMove(pkmn, typeThemed, true, allBanned); + // Find last lv1 move + // lv1index ends up as the index of the first non-lv1 move + int lv1index = 0; + while (lv1index < moves.size() && moves.get(lv1index).level == 1) { + lv1index++; + } + // last lv1 move is 1 before lv1index + if (lv1index == 0) { + lv1index++; + } + moves.get(lv1index - 1).move = damagingMove; + moves.get(lv1index - 1).level = 1; // just in case + learnt.add(damagingMove); + // Rest replace with randoms + for (int i = 0; i < moves.size(); i++) { + if (i == (lv1index - 1)) { + continue; + } + int picked = pickMove(pkmn, typeThemed, false, allBanned); + while (learnt.contains(picked)) { + picked = pickMove(pkmn, typeThemed, false, allBanned); + } + moves.get(i).move = picked; + learnt.add(picked); + } + } + // Done, save + this.setMovesLearnt(movesets); + + } + + private static final int METRONOME_MOVE = 118; + + @Override + public void metronomeOnlyMode() { + + // movesets + Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); + + MoveLearnt metronomeML = new MoveLearnt(); + metronomeML.level = 1; + metronomeML.move = METRONOME_MOVE; + + for (List<MoveLearnt> ms : movesets.values()) { + if (ms != null && ms.size() > 0) { + ms.clear(); + ms.add(metronomeML); + } + } + + this.setMovesLearnt(movesets); + + // trainers + // run this to remove all custom non-Metronome moves + this.setTrainers(this.getTrainers()); + + // tms + List<Integer> tmMoves = this.getTMMoves(); + + for (int i = 0; i < tmMoves.size(); i++) { + tmMoves.set(i, METRONOME_MOVE); + } + + this.setTMMoves(tmMoves); + + // movetutors + if (this.hasMoveTutors()) { + List<Integer> mtMoves = this.getMoveTutorMoves(); + + for (int i = 0; i < mtMoves.size(); i++) { + mtMoves.set(i, METRONOME_MOVE); + } + + this.setMoveTutorMoves(mtMoves); + } + + // move tweaks + List<Move> moveData = this.getMoves(); + + Move metronome = moveData.get(METRONOME_MOVE); + + metronome.pp = 40; + + List<Integer> hms = this.getHMMoves(); + + for (int hm : hms) { + Move thisHM = moveData.get(hm); + thisHM.pp = 0; + } + } + + @Override + public void randomizeStaticPokemon(boolean legendForLegend) { + // Load + checkPokemonRestrictions(); + List<Pokemon> currentStaticPokemon = this.getStaticPokemon(); + List<Pokemon> replacements = new ArrayList<Pokemon>(); + List<Pokemon> banned = this.bannedForStaticPokemon(); + + if (legendForLegend) { + List<Pokemon> legendariesLeft = new ArrayList<Pokemon>(onlyLegendaryList); + List<Pokemon> nonlegsLeft = new ArrayList<Pokemon>(noLegendaryList); + legendariesLeft.removeAll(banned); + nonlegsLeft.removeAll(banned); + for (int i = 0; i < currentStaticPokemon.size(); i++) { + Pokemon old = currentStaticPokemon.get(i); + Pokemon newPK; + if (old.isLegendary()) { + newPK = legendariesLeft.remove(this.random.nextInt(legendariesLeft.size())); + if (legendariesLeft.size() == 0) { + legendariesLeft.addAll(onlyLegendaryList); + legendariesLeft.removeAll(banned); + } + } else { + newPK = nonlegsLeft.remove(this.random.nextInt(nonlegsLeft.size())); + if (nonlegsLeft.size() == 0) { + nonlegsLeft.addAll(noLegendaryList); + nonlegsLeft.removeAll(banned); + } + } + replacements.add(newPK); + } + } else { + List<Pokemon> pokemonLeft = new ArrayList<Pokemon>(mainPokemonList); + pokemonLeft.removeAll(banned); + for (int i = 0; i < currentStaticPokemon.size(); i++) { + Pokemon newPK = pokemonLeft.remove(this.random.nextInt(pokemonLeft.size())); + if (pokemonLeft.size() == 0) { + pokemonLeft.addAll(mainPokemonList); + pokemonLeft.removeAll(banned); + } + replacements.add(newPK); + } + } + + // Save + this.setStaticPokemon(replacements); + } + + @Override + public void randomizeTMMoves(boolean noBroken, boolean preserveField) { + // Pick some random TM moves. + int tmCount = this.getTMCount(); + List<Move> allMoves = this.getMoves(); + List<Integer> newTMs = new ArrayList<Integer>(); + List<Integer> hms = this.getHMMoves(); + List<Integer> oldTMs = this.getTMMoves(); + @SuppressWarnings("unchecked") + List<Integer> banned = new ArrayList<Integer>(noBroken ? this.getGameBreakingMoves() : Collections.EMPTY_LIST); + // field moves? + List<Integer> fieldMoves = this.getFieldMoves(); + if (preserveField) { + List<Integer> banExistingField = new ArrayList<Integer>(oldTMs); + banExistingField.retainAll(fieldMoves); + banned.addAll(banExistingField); + } + for (int i = 0; i < tmCount; i++) { + if (preserveField && fieldMoves.contains(oldTMs.get(i))) { + newTMs.add(oldTMs.get(i)); + } else { + int chosenMove = this.random.nextInt(allMoves.size() - 1) + 1; + while (newTMs.contains(chosenMove) || RomFunctions.bannedRandomMoves[chosenMove] + || hms.contains(chosenMove) || banned.contains(chosenMove)) { + chosenMove = this.random.nextInt(allMoves.size() - 1) + 1; + } + newTMs.add(chosenMove); + } + } + this.setTMMoves(newTMs); + } + + @Override + public void randomizeTMHMCompatibility(boolean preferSameType) { + // Get current compatibility + // new: increase HM chances if required early on + List<Integer> requiredEarlyOn = this.getEarlyRequiredHMMoves(); + Map<Pokemon, boolean[]> compat = this.getTMHMCompatibility(); + List<Integer> tmHMs = new ArrayList<Integer>(this.getTMMoves()); + tmHMs.addAll(this.getHMMoves()); + List<Move> moveData = this.getMoves(); + for (Map.Entry<Pokemon, boolean[]> compatEntry : compat.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + for (int i = 1; i <= tmHMs.size(); i++) { + int move = tmHMs.get(i - 1); + Move mv = moveData.get(move); + double probability = 0.5; + if (preferSameType) { + if (pkmn.primaryType.equals(mv.type) + || (pkmn.secondaryType != null && pkmn.secondaryType.equals(mv.type))) { + probability = 0.9; + } else if (mv.type != null && mv.type.equals(Type.NORMAL)) { + probability = 0.5; + } else { + probability = 0.25; + } + } + if (requiredEarlyOn.contains(move)) { + probability = Math.min(1.0, probability * 1.5); + } + flags[i] = (this.random.nextDouble() < probability); + } + } + + // Set the new compatibility + this.setTMHMCompatibility(compat); + } + + @Override + public void fullTMHMCompatibility() { + Map<Pokemon, boolean[]> compat = this.getTMHMCompatibility(); + for (Map.Entry<Pokemon, boolean[]> compatEntry : compat.entrySet()) { + boolean[] flags = compatEntry.getValue(); + for (int i = 1; i < flags.length; i++) { + flags[i] = true; + } + } + this.setTMHMCompatibility(compat); + } + + @Override + public void fullHMCompatibility() { + Map<Pokemon, boolean[]> compat = this.getTMHMCompatibility(); + int tmCount = this.getTMCount(); + for (boolean[] flags : compat.values()) { + for (int i = tmCount + 1; i < flags.length; i++) { + flags[i] = true; + } + } + + // Set the new compatibility + this.setTMHMCompatibility(compat); + } + + @Override + public void ensureTMCompatSanity() { + // if a pokemon learns a move in its moveset + // and there is a TM of that move, make sure + // that TM can be learned. + Map<Pokemon, boolean[]> compat = this.getTMHMCompatibility(); + Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); + List<Integer> tmMoves = this.getTMMoves(); + for (Pokemon pkmn : compat.keySet()) { + List<MoveLearnt> moveset = movesets.get(pkmn); + boolean[] pkmnCompat = compat.get(pkmn); + for (MoveLearnt ml : moveset) { + if (tmMoves.contains(ml.move)) { + int tmIndex = tmMoves.indexOf(ml.move); + pkmnCompat[tmIndex + 1] = true; + } + } + } + this.setTMHMCompatibility(compat); + } + + @Override + public void randomizeMoveTutorMoves(boolean noBroken, boolean preserveField) { + if (!this.hasMoveTutors()) { + return; + } + // Pick some random Move Tutor moves, excluding TMs. + List<Move> allMoves = this.getMoves(); + List<Integer> tms = this.getTMMoves(); + List<Integer> newMTs = new ArrayList<Integer>(); + List<Integer> oldMTs = this.getMoveTutorMoves(); + int mtCount = oldMTs.size(); + List<Integer> hms = this.getHMMoves(); + @SuppressWarnings("unchecked") + List<Integer> banned = new ArrayList<Integer>(noBroken ? this.getGameBreakingMoves() : Collections.EMPTY_LIST); + // field moves? + List<Integer> fieldMoves = this.getFieldMoves(); + if (preserveField) { + List<Integer> banExistingField = new ArrayList<Integer>(oldMTs); + banExistingField.retainAll(fieldMoves); + banned.addAll(banExistingField); + } + for (int i = 0; i < mtCount; i++) { + if (preserveField && fieldMoves.contains(oldMTs.get(i))) { + newMTs.add(oldMTs.get(i)); + } else { + int chosenMove = this.random.nextInt(allMoves.size() - 1) + 1; + while (newMTs.contains(chosenMove) || tms.contains(chosenMove) + || RomFunctions.bannedRandomMoves[chosenMove] || hms.contains(chosenMove) + || banned.contains(chosenMove)) { + chosenMove = this.random.nextInt(allMoves.size() - 1) + 1; + } + newMTs.add(chosenMove); + } + } + this.setMoveTutorMoves(newMTs); + } + + @Override + public void randomizeMoveTutorCompatibility(boolean preferSameType) { + if (!this.hasMoveTutors()) { + return; + } + // Get current compatibility + Map<Pokemon, boolean[]> compat = this.getMoveTutorCompatibility(); + List<Integer> mts = this.getMoveTutorMoves(); + List<Move> moveData = this.getMoves(); + for (Map.Entry<Pokemon, boolean[]> compatEntry : compat.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + for (int i = 1; i <= mts.size(); i++) { + int move = mts.get(i - 1); + Move mv = moveData.get(move); + double probability = 0.5; + if (preferSameType) { + if (pkmn.primaryType.equals(mv.type) + || (pkmn.secondaryType != null && pkmn.secondaryType.equals(mv.type))) { + probability = 0.9; + } else if (mv.type != null && mv.type.equals(Type.NORMAL)) { + probability = 0.5; + } else { + probability = 0.25; + } + } + flags[i] = (this.random.nextDouble() < probability); + } + } + + // Set the new compatibility + this.setMoveTutorCompatibility(compat); + + } + + @Override + public void fullMoveTutorCompatibility() { + if (!this.hasMoveTutors()) { + return; + } + Map<Pokemon, boolean[]> compat = this.getMoveTutorCompatibility(); + for (Map.Entry<Pokemon, boolean[]> compatEntry : compat.entrySet()) { + boolean[] flags = compatEntry.getValue(); + for (int i = 1; i < flags.length; i++) { + flags[i] = true; + } + } + this.setMoveTutorCompatibility(compat); + } + + @Override + public void ensureMoveTutorCompatSanity() { + if (!this.hasMoveTutors()) { + return; + } + // if a pokemon learns a move in its moveset + // and there is a tutor of that move, make sure + // that tutor can be learned. + Map<Pokemon, boolean[]> compat = this.getMoveTutorCompatibility(); + Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); + List<Integer> mtMoves = this.getMoveTutorMoves(); + for (Pokemon pkmn : compat.keySet()) { + List<MoveLearnt> moveset = movesets.get(pkmn); + boolean[] pkmnCompat = compat.get(pkmn); + for (MoveLearnt ml : moveset) { + if (mtMoves.contains(ml.move)) { + int mtIndex = mtMoves.indexOf(ml.move); + pkmnCompat[mtIndex + 1] = true; + } + } + } + this.setMoveTutorCompatibility(compat); + + } + + @SuppressWarnings("unchecked") + @Override + public void randomizeTrainerNames(byte[] presetNames) { + List<String>[] allTrainerNames = new List[] { new ArrayList<String>(), new ArrayList<String>() }; + Map<Integer, List<String>> trainerNamesByLength[] = new Map[] { new TreeMap<Integer, List<String>>(), + new TreeMap<Integer, List<String>>() }; + // Check for the file + if (FileFunctions.configExists(tnamesFile)) { + try { + Scanner sc = null; + if (presetNames == null) { + sc = new Scanner(FileFunctions.openConfig(tnamesFile), "UTF-8"); + } else { + sc = new Scanner(new ByteArrayInputStream(presetNames), "UTF-8"); + } + while (sc.hasNextLine()) { + String trainername = sc.nextLine().trim(); + if (trainername.isEmpty()) { + continue; + } + if (trainername.startsWith("\uFEFF")) { + trainername = trainername.substring(1); + } + int idx = trainername.contains("&") ? 1 : 0; + int len = this.internalStringLength(trainername); + if (len <= 10) { + allTrainerNames[idx].add(trainername); + if (trainerNamesByLength[idx].containsKey(len)) { + trainerNamesByLength[idx].get(len).add(trainername); + } else { + List<String> namesOfThisLength = new ArrayList<String>(); + namesOfThisLength.add(trainername); + trainerNamesByLength[idx].put(len, namesOfThisLength); + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + // Can't read, just don't load anything + } + } + + // Get the current trainer names data + List<String> currentTrainerNames = this.getTrainerNames(); + if (currentTrainerNames.size() == 0) { + // RBY have no trainer names + return; + } + TrainerNameMode mode = this.trainerNameMode(); + int maxLength = this.maxTrainerNameLength(); + + // Init the translation map and new list + Map<String, String> translation = new HashMap<String, String>(); + List<String> newTrainerNames = new ArrayList<String>(); + List<Integer> tcNameLengths = this.getTCNameLengthsByTrainer(); + + // Start choosing + int tnIndex = -1; + for (String trainerName : currentTrainerNames) { + tnIndex++; + if (translation.containsKey(trainerName) && trainerName.equalsIgnoreCase("GRUNT") == false + && trainerName.equalsIgnoreCase("EXECUTIVE") == false) { + // use an already picked translation + newTrainerNames.add(translation.get(trainerName)); + } else { + int idx = trainerName.contains("&") ? 1 : 0; + List<String> pickFrom = allTrainerNames[idx]; + int intStrLen = this.internalStringLength(trainerName); + if (mode == TrainerNameMode.SAME_LENGTH) { + pickFrom = trainerNamesByLength[idx].get(intStrLen); + } + String changeTo = trainerName; + if (pickFrom != null && pickFrom.size() > 0 && intStrLen > 1) { + int tries = 0; + changeTo = pickFrom.get(this.random.nextInt(pickFrom.size())); + int ctl = this.internalStringLength(changeTo); + while ((mode == TrainerNameMode.MAX_LENGTH && ctl > maxLength) + || (mode == TrainerNameMode.MAX_LENGTH_WITH_CLASS && ctl + tcNameLengths.get(tnIndex) > maxLength)) { + tries++; + if (tries == 50) { + changeTo = trainerName; + break; + } + changeTo = pickFrom.get(this.random.nextInt(pickFrom.size())); + ctl = this.internalStringLength(changeTo); + } + } + translation.put(trainerName, changeTo); + newTrainerNames.add(changeTo); + } + } + + // Done choosing, save + this.setTrainerNames(newTrainerNames); + } + + @Override + public int maxTrainerNameLength() { + // default: no real limit + return Integer.MAX_VALUE; + } + + @SuppressWarnings("unchecked") + @Override + public void randomizeTrainerClassNames(byte[] presetNames) { + List<String> allTrainerClasses[] = new List[] { new ArrayList<String>(), new ArrayList<String>() }; + Map<Integer, List<String>> trainerClassesByLength[] = new Map[] { new HashMap<Integer, List<String>>(), + new HashMap<Integer, List<String>>() }; + // Check for the file + if (FileFunctions.configExists(tclassesFile)) { + try { + Scanner sc = null; + if (presetNames == null) { + sc = new Scanner(FileFunctions.openConfig(tclassesFile), "UTF-8"); + } else { + sc = new Scanner(new ByteArrayInputStream(presetNames), "UTF-8"); + } + while (sc.hasNextLine()) { + String trainerClassName = sc.nextLine().trim(); + if (trainerClassName.isEmpty()) { + continue; + } + if (trainerClassName.startsWith("\uFEFF")) { + trainerClassName = trainerClassName.substring(1); + } + String checkName = trainerClassName.toLowerCase(); + int idx = (checkName.endsWith("couple") || checkName.contains(" and ") || checkName.endsWith("kin") + || checkName.endsWith("team") || checkName.contains("&") || (checkName.endsWith("s") && !checkName + .endsWith("ss"))) ? 1 : 0; + allTrainerClasses[idx].add(trainerClassName); + int len = this.internalStringLength(trainerClassName); + if (trainerClassesByLength[idx].containsKey(len)) { + trainerClassesByLength[idx].get(len).add(trainerClassName); + } else { + List<String> namesOfThisLength = new ArrayList<String>(); + namesOfThisLength.add(trainerClassName); + trainerClassesByLength[idx].put(len, namesOfThisLength); + } + } + sc.close(); + } catch (FileNotFoundException e) { + // Can't read, just don't load anything + } + } + + // Get the current trainer names data + List<String> currentClassNames = this.getTrainerClassNames(); + boolean mustBeSameLength = this.fixedTrainerClassNamesLength(); + int maxLength = this.maxTrainerClassNameLength(); + + // Init the translation map and new list + Map<String, String> translation = new HashMap<String, String>(); + List<String> newClassNames = new ArrayList<String>(); + + // Start choosing + for (String trainerClassName : currentClassNames) { + if (translation.containsKey(trainerClassName)) { + // use an already picked translation + newClassNames.add(translation.get(trainerClassName)); + } else { + String checkName = trainerClassName.toLowerCase(); + int idx = (checkName.endsWith("couple") || checkName.contains(" and ") || checkName.endsWith("kin") + || checkName.endsWith("team") || checkName.contains(" & ") || (checkName.endsWith("s") && !checkName + .endsWith("ss"))) ? 1 : 0; + List<String> pickFrom = allTrainerClasses[idx]; + int intStrLen = this.internalStringLength(trainerClassName); + if (mustBeSameLength) { + pickFrom = trainerClassesByLength[idx].get(intStrLen); + } + String changeTo = trainerClassName; + if (pickFrom != null && pickFrom.size() > 0) { + changeTo = pickFrom.get(this.random.nextInt(pickFrom.size())); + while (changeTo.length() > maxLength) { + changeTo = pickFrom.get(this.random.nextInt(pickFrom.size())); + } + } + translation.put(trainerClassName, changeTo); + newClassNames.add(changeTo); + } + } + + // Done choosing, save + this.setTrainerClassNames(newClassNames); + } + + @Override + public int maxTrainerClassNameLength() { + // default: no real limit + return Integer.MAX_VALUE; + } + + @Override + public void randomizeWildHeldItems(boolean banBadItems) { + List<Pokemon> pokemon = allPokemonWithoutNull(); + ItemList possibleItems = banBadItems ? this.getNonBadItems() : this.getAllowedItems(); + for (Pokemon pk : pokemon) { + if (pk.guaranteedHeldItem == -1 && pk.commonHeldItem == -1 && pk.rareHeldItem == -1 + && pk.darkGrassHeldItem == -1) { + // No held items at all, abort + return; + } + boolean canHaveDarkGrass = pk.darkGrassHeldItem != -1; + if (pk.guaranteedHeldItem != -1) { + // Guaranteed held items are supported. + if (pk.guaranteedHeldItem > 0) { + // Currently have a guaranteed item + double decision = this.random.nextDouble(); + if (decision < 0.9) { + // Stay as guaranteed + canHaveDarkGrass = false; + pk.guaranteedHeldItem = possibleItems.randomItem(this.random); + } else { + // Change to 25% or 55% chance + pk.guaranteedHeldItem = 0; + pk.commonHeldItem = possibleItems.randomItem(this.random); + pk.rareHeldItem = possibleItems.randomItem(this.random); + while (pk.rareHeldItem == pk.commonHeldItem) { + pk.rareHeldItem = possibleItems.randomItem(this.random); + } + } + } else { + // No guaranteed item atm + double decision = this.random.nextDouble(); + if (decision < 0.5) { + // No held item at all + pk.commonHeldItem = 0; + pk.rareHeldItem = 0; + } else if (decision < 0.65) { + // Just a rare item + pk.commonHeldItem = 0; + pk.rareHeldItem = possibleItems.randomItem(this.random); + } else if (decision < 0.8) { + // Just a common item + pk.commonHeldItem = possibleItems.randomItem(this.random); + pk.rareHeldItem = 0; + } else if (decision < 0.95) { + // Both a common and rare item + pk.commonHeldItem = possibleItems.randomItem(this.random); + pk.rareHeldItem = possibleItems.randomItem(this.random); + while (pk.rareHeldItem == pk.commonHeldItem) { + pk.rareHeldItem = possibleItems.randomItem(this.random); + } + } else { + // Guaranteed item + canHaveDarkGrass = false; + pk.guaranteedHeldItem = possibleItems.randomItem(this.random); + pk.commonHeldItem = 0; + pk.rareHeldItem = 0; + } + } + } else { + // Code for no guaranteed items + double decision = this.random.nextDouble(); + if (decision < 0.5) { + // No held item at all + pk.commonHeldItem = 0; + pk.rareHeldItem = 0; + } else if (decision < 0.65) { + // Just a rare item + pk.commonHeldItem = 0; + pk.rareHeldItem = possibleItems.randomItem(this.random); + } else if (decision < 0.8) { + // Just a common item + pk.commonHeldItem = possibleItems.randomItem(this.random); + pk.rareHeldItem = 0; + } else { + // Both a common and rare item + pk.commonHeldItem = possibleItems.randomItem(this.random); + pk.rareHeldItem = possibleItems.randomItem(this.random); + while (pk.rareHeldItem == pk.commonHeldItem) { + pk.rareHeldItem = possibleItems.randomItem(this.random); + } + } + } + + if (canHaveDarkGrass) { + double dgDecision = this.random.nextDouble(); + if (dgDecision < 0.5) { + // Yes, dark grass item + pk.darkGrassHeldItem = possibleItems.randomItem(this.random); + } else { + pk.darkGrassHeldItem = 0; + } + } else if (pk.darkGrassHeldItem != -1) { + pk.darkGrassHeldItem = 0; + } + } + + } + + @Override + public void randomizeStarterHeldItems(boolean banBadItems) { + List<Integer> oldHeldItems = this.getStarterHeldItems(); + List<Integer> newHeldItems = new ArrayList<Integer>(); + ItemList possibleItems = banBadItems ? this.getNonBadItems() : this.getAllowedItems(); + for (int i = 0; i < oldHeldItems.size(); i++) { + newHeldItems.add(possibleItems.randomItem(this.random)); + } + this.setStarterHeldItems(newHeldItems); + } + + @Override + public void shuffleFieldItems() { + List<Integer> currentItems = this.getRegularFieldItems(); + List<Integer> currentTMs = this.getCurrentFieldTMs(); + + Collections.shuffle(currentItems, this.random); + Collections.shuffle(currentTMs, this.random); + + this.setRegularFieldItems(currentItems); + this.setFieldTMs(currentTMs); + } + + @Override + public void randomizeFieldItems(boolean banBadItems) { + ItemList possibleItems = banBadItems ? this.getNonBadItems() : this.getAllowedItems(); + List<Integer> currentItems = this.getRegularFieldItems(); + List<Integer> currentTMs = this.getCurrentFieldTMs(); + List<Integer> requiredTMs = this.getRequiredFieldTMs(); + + int fieldItemCount = currentItems.size(); + int fieldTMCount = currentTMs.size(); + int reqTMCount = requiredTMs.size(); + int totalTMCount = this.getTMCount(); + + List<Integer> newItems = new ArrayList<Integer>(); + List<Integer> newTMs = new ArrayList<Integer>(); + + for (int i = 0; i < fieldItemCount; i++) { + newItems.add(possibleItems.randomNonTM(this.random)); + } + + newTMs.addAll(requiredTMs); + + for (int i = reqTMCount; i < fieldTMCount; i++) { + while (true) { + int tm = this.random.nextInt(totalTMCount) + 1; + if (!newTMs.contains(tm)) { + newTMs.add(tm); + break; + } + } + } + + Collections.shuffle(newItems, this.random); + Collections.shuffle(newTMs, this.random); + + this.setRegularFieldItems(newItems); + this.setFieldTMs(newTMs); + } + + @Override + public void randomizeIngameTrades(boolean randomizeRequest, byte[] presetNicknames, boolean randomNickname, + byte[] presetTrainerNames, boolean randomOT, boolean randomStats, boolean randomItem) { + checkPokemonRestrictions(); + // Process trainer names + List<String> singleTrainerNames = new ArrayList<String>(); + // Check for the file + if (FileFunctions.configExists(tnamesFile) && randomOT) { + int maxOT = this.maxTradeOTNameLength(); + try { + Scanner sc = null; + if (presetTrainerNames == null) { + sc = new Scanner(FileFunctions.openConfig(tnamesFile), "UTF-8"); + } else { + sc = new Scanner(new ByteArrayInputStream(presetTrainerNames), "UTF-8"); + } + while (sc.hasNextLine()) { + String trainername = sc.nextLine().trim(); + if (trainername.isEmpty()) { + continue; + } + if (trainername.startsWith("\uFEFF")) { + trainername = trainername.substring(1); + } + int idx = trainername.contains("&") ? 1 : 0; + int len = this.internalStringLength(trainername); + if (len <= maxOT && idx == 0 && !singleTrainerNames.contains(trainername)) { + singleTrainerNames.add(trainername); + } + } + sc.close(); + } catch (FileNotFoundException e) { + // Can't read, just don't load anything + } + } + + // Process nicknames + List<String> nicknames = new ArrayList<String>(); + // Check for the file + if (FileFunctions.configExists(nnamesFile) && randomNickname) { + int maxNN = this.maxTradeNicknameLength(); + try { + Scanner sc = null; + if (presetNicknames == null) { + sc = new Scanner(FileFunctions.openConfig(nnamesFile), "UTF-8"); + } else { + sc = new Scanner(new ByteArrayInputStream(presetNicknames), "UTF-8"); + } + while (sc.hasNextLine()) { + String nickname = sc.nextLine().trim(); + if (nickname.isEmpty()) { + continue; + } + if (nickname.startsWith("\uFEFF")) { + nickname = nickname.substring(1); + } + int len = this.internalStringLength(nickname); + if (len <= maxNN && !nicknames.contains(nickname)) { + nicknames.add(nickname); + } + } + sc.close(); + } catch (FileNotFoundException e) { + // Can't read, just don't load anything + } + } + + // get old trades + List<IngameTrade> trades = this.getIngameTrades(); + List<Pokemon> usedRequests = new ArrayList<Pokemon>(); + List<Pokemon> usedGivens = new ArrayList<Pokemon>(); + List<String> usedOTs = new ArrayList<String>(); + List<String> usedNicknames = new ArrayList<String>(); + ItemList possibleItems = this.getAllowedItems(); + + int nickCount = nicknames.size(); + int trnameCount = singleTrainerNames.size(); + + for (IngameTrade trade : trades) { + // pick new given pokemon + Pokemon oldgiven = trade.givenPokemon; + Pokemon given = this.randomPokemon(); + while (usedGivens.contains(given)) { + given = this.randomPokemon(); + } + usedGivens.add(given); + trade.givenPokemon = given; + + // requested pokemon? + if (oldgiven == trade.requestedPokemon) { + // preserve trades for the same pokemon + trade.requestedPokemon = given; + } else if (randomizeRequest) { + Pokemon request = this.randomPokemon(); + while (usedRequests.contains(request) || request == given) { + request = this.randomPokemon(); + } + usedRequests.add(request); + trade.requestedPokemon = request; + } + + // nickname? + if (randomNickname && nickCount > usedNicknames.size()) { + String nickname = nicknames.get(this.random.nextInt(nickCount)); + while (usedNicknames.contains(nickname)) { + nickname = nicknames.get(this.random.nextInt(nickCount)); + } + usedNicknames.add(nickname); + trade.nickname = nickname; + } else if (trade.nickname.equalsIgnoreCase(oldgiven.name)) { + // change the name for sanity + trade.nickname = trade.givenPokemon.name; + } + + if (randomOT && trnameCount > usedOTs.size()) { + String ot = singleTrainerNames.get(this.random.nextInt(trnameCount)); + while (usedOTs.contains(ot)) { + ot = singleTrainerNames.get(this.random.nextInt(trnameCount)); + } + usedOTs.add(ot); + trade.otName = ot; + trade.otId = this.random.nextInt(65536); + } + + if (randomStats) { + int maxIV = this.hasDVs() ? 16 : 32; + for (int i = 0; i < trade.ivs.length; i++) { + trade.ivs[i] = this.random.nextInt(maxIV); + } + } + + if (randomItem) { + trade.item = possibleItems.randomItem(this.random); + } + } + + // things that the game doesn't support should just be ignored + this.setIngameTrades(trades); + } + + @Override + public int maxTradeNicknameLength() { + return 10; + } + + @Override + public int maxTradeOTNameLength() { + return 7; + } + + @Override + public void condenseLevelEvolutions(int maxLevel, int maxIntermediateLevel) { + List<Pokemon> allPokemon = this.getPokemon(); + Set<Evolution> changedEvos = new TreeSet<Evolution>(); + // search for level evolutions + for (Pokemon pk : allPokemon) { + if (pk != null) { + for (Evolution checkEvo : pk.evolutionsFrom) { + if (checkEvo.type.usesLevel()) { + // bring down the level of this evo if it exceeds max + // level + if (checkEvo.extraInfo > maxLevel) { + checkEvo.extraInfo = maxLevel; + changedEvos.add(checkEvo); + } + // Now, seperately, if an intermediate level evo is too + // high, bring it down + for (Evolution otherEvo : pk.evolutionsTo) { + if (otherEvo.type.usesLevel() && otherEvo.extraInfo > maxIntermediateLevel) { + otherEvo.extraInfo = maxIntermediateLevel; + changedEvos.add(otherEvo); + } + } + } + } + } + } + // Log changes now that we're done (to avoid repeats) + log("--Condensed Level Evolutions--"); + for (Evolution evol : changedEvos) { + log(String.format("%s now evolves into %s at minimum level %d", evol.from.name, evol.to.name, + evol.extraInfo)); + } + logBlankLine(); + + } + + @Override + public void randomizeEvolutions(boolean similarStrength, boolean sameType, boolean limitToThreeStages, + boolean forceChange) { + checkPokemonRestrictions(); + List<Pokemon> pokemonPool = new ArrayList<Pokemon>(mainPokemonList); + int stageLimit = limitToThreeStages ? 3 : 10; + + // Cache old evolutions for data later + Map<Pokemon, List<Evolution>> originalEvos = new HashMap<Pokemon, List<Evolution>>(); + for (Pokemon pk : pokemonPool) { + originalEvos.put(pk, new ArrayList<Evolution>(pk.evolutionsFrom)); + } + + Set<EvolutionPair> newEvoPairs = new HashSet<EvolutionPair>(); + Set<EvolutionPair> oldEvoPairs = new HashSet<EvolutionPair>(); + + if (forceChange) { + for (Pokemon pk : pokemonPool) { + for (Evolution ev : pk.evolutionsFrom) { + oldEvoPairs.add(new EvolutionPair(ev.from, ev.to)); + } + } + } + + List<Pokemon> replacements = new ArrayList<Pokemon>(); + + int loops = 0; + while (loops < 1) { + // Setup for this loop. + boolean hadError = false; + for (Pokemon pk : pokemonPool) { + pk.evolutionsFrom.clear(); + pk.evolutionsTo.clear(); + } + newEvoPairs.clear(); + + // Shuffle pokemon list so the results aren't overly predictable. + Collections.shuffle(pokemonPool, this.random); + + for (Pokemon fromPK : pokemonPool) { + List<Evolution> oldEvos = originalEvos.get(fromPK); + for (Evolution ev : oldEvos) { + // Pick a Pokemon as replacement + replacements.clear(); + + // Step 1: base filters + for (Pokemon pk : mainPokemonList) { + // Prevent evolving into oneself (mandatory) + if (pk == fromPK) { + continue; + } + + // Force same EXP curve (mandatory) + if (pk.growthCurve != fromPK.growthCurve) { + continue; + } + + EvolutionPair ep = new EvolutionPair(fromPK, pk); + // Prevent split evos choosing the same Pokemon + // (mandatory) + if (newEvoPairs.contains(ep)) { + continue; + } + + // Prevent evolving into old thing if flagged + if (forceChange && oldEvoPairs.contains(ep)) { + continue; + } + + // Prevent evolution that causes cycle (mandatory) + if (evoCycleCheck(fromPK, pk)) { + continue; + } + + // Prevent evolution that exceeds stage limit + Evolution tempEvo = new Evolution(fromPK, pk, false, EvolutionType.NONE, 0); + fromPK.evolutionsFrom.add(tempEvo); + pk.evolutionsTo.add(tempEvo); + boolean exceededLimit = false; + + Set<Pokemon> related = relatedPokemon(fromPK); + + for (Pokemon pk2 : related) { + int numPreEvos = numPreEvolutions(pk2, stageLimit); + if (numPreEvos >= stageLimit) { + exceededLimit = true; + break; + } else if (numPreEvos == stageLimit - 1 && pk2.evolutionsFrom.size() == 0 + && originalEvos.get(pk2).size() > 0) { + exceededLimit = true; + break; + } + } + + fromPK.evolutionsFrom.remove(tempEvo); + pk.evolutionsTo.remove(tempEvo); + + if (exceededLimit) { + continue; + } + + // Passes everything, add as a candidate. + replacements.add(pk); + } + + // If we don't have any candidates after Step 1, severe + // failure + // exit out of this loop and try again from scratch + if (replacements.size() == 0) { + hadError = true; + break; + } + + // Step 2: filter by type, if needed + if (replacements.size() > 1 && sameType) { + Set<Pokemon> includeType = new HashSet<Pokemon>(); + for (Pokemon pk : replacements) { + if (pk.primaryType == fromPK.primaryType + || (fromPK.secondaryType != null && pk.primaryType == fromPK.secondaryType) + || (pk.secondaryType != null && pk.secondaryType == fromPK.primaryType) + || (fromPK.secondaryType != null && pk.secondaryType != null && pk.secondaryType == fromPK.secondaryType)) { + includeType.add(pk); + } + } + + if (includeType.size() != 0) { + replacements.retainAll(includeType); + } + } + + // Step 3: pick - by similar strength or otherwise + Pokemon picked = null; + + if (replacements.size() == 1) { + // Foregone conclusion. + picked = replacements.get(0); + } else if (similarStrength) { + picked = pickEvoPowerLvlReplacement(replacements, ev.to); + } else { + picked = replacements.get(this.random.nextInt(replacements.size())); + } + + // Step 4: add it to the new evos pool + Evolution newEvo = new Evolution(fromPK, picked, ev.carryStats, ev.type, ev.extraInfo); + fromPK.evolutionsFrom.add(newEvo); + picked.evolutionsTo.add(newEvo); + newEvoPairs.add(new EvolutionPair(fromPK, picked)); + } + + if (hadError) { + // No need to check the other Pokemon if we already errored + break; + } + } + + // If no error, done and return + if (!hadError) { + return; + } else { + loops++; + } + } + + // If we made it out of the loop, we weren't able to randomize evos. + throw new RuntimeException("Not able to randomize evolutions in a sane amount of retries."); + } + + private Pokemon pickEvoPowerLvlReplacement(List<Pokemon> pokemonPool, Pokemon current) { + // start with within 10% and add 5% either direction till we find + // something + int currentBST = current.bstForPowerLevels(); + int minTarget = currentBST - currentBST / 10; + int maxTarget = currentBST + currentBST / 10; + List<Pokemon> canPick = new ArrayList<Pokemon>(); + int expandRounds = 0; + while (canPick.isEmpty() || (canPick.size() < 3 && expandRounds < 3)) { + for (Pokemon pk : pokemonPool) { + if (pk.bstForPowerLevels() >= minTarget && pk.bstForPowerLevels() <= maxTarget && !canPick.contains(pk)) { + canPick.add(pk); + } + } + minTarget -= currentBST / 20; + maxTarget += currentBST / 20; + expandRounds++; + } + return canPick.get(this.random.nextInt(canPick.size())); + } + + private static class EvolutionPair { + private Pokemon from; + private Pokemon to; + + public EvolutionPair(Pokemon from, Pokemon to) { + this.from = from; + this.to = to; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((from == null) ? 0 : from.hashCode()); + result = prime * result + ((to == null) ? 0 : to.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EvolutionPair other = (EvolutionPair) obj; + if (from == null) { + if (other.from != null) + return false; + } else if (!from.equals(other.from)) + return false; + if (to == null) { + if (other.to != null) + return false; + } else if (!to.equals(other.to)) + return false; + return true; + } + } + + /** + * Check whether adding an evolution from one Pokemon to another will cause + * an evolution cycle. + * + * @param from + * @param to + * @param newEvos + * @return + */ + private boolean evoCycleCheck(Pokemon from, Pokemon to) { + Evolution tempEvo = new Evolution(from, to, false, EvolutionType.NONE, 0); + from.evolutionsFrom.add(tempEvo); + Set<Pokemon> visited = new HashSet<Pokemon>(); + Set<Pokemon> recStack = new HashSet<Pokemon>(); + boolean recur = isCyclic(from, visited, recStack); + from.evolutionsFrom.remove(tempEvo); + return recur; + } + + private boolean isCyclic(Pokemon pk, Set<Pokemon> visited, Set<Pokemon> recStack) { + if (!visited.contains(pk)) { + visited.add(pk); + recStack.add(pk); + for (Evolution ev : pk.evolutionsFrom) { + if (!visited.contains(ev.to) && isCyclic(ev.to, visited, recStack)) { + return true; + } else if (recStack.contains(ev.to)) { + return true; + } + } + } + recStack.remove(pk); + return false; + } + + // MOVE DATA + // All randomizers don't touch move ID 165 (Struggle) + // They also have other exclusions where necessary to stop things glitching. + + @Override + public void randomizeMovePowers() { + List<Move> moves = this.getMoves(); + for (Move mv : moves) { + if (mv != null && mv.internalId != 165 && mv.power >= 10) { + // "Generic" damaging move to randomize power + if (random.nextInt(3) != 2) { + // "Regular" move + mv.power = random.nextInt(11) * 5 + 50; // 50 ... 100 + } else { + // "Extreme" move + mv.power = random.nextInt(27) * 5 + 20; // 20 ... 150 + } + // Tiny chance for massive power jumps + for (int i = 0; i < 2; i++) { + if (random.nextInt(100) == 0) { + mv.power += 50; + } + } + } + } + } + + @Override + public void randomizeMovePPs() { + List<Move> moves = this.getMoves(); + for (Move mv : moves) { + if (mv != null && mv.internalId != 165) { + mv.pp = random.nextInt(8) * 5 + 5; // 5 ... 40 inclusive + } + } + } + + @Override + public void randomizeMoveAccuracies() { + List<Move> moves = this.getMoves(); + for (Move mv : moves) { + if (mv != null && mv.internalId != 165 && mv.hitratio >= 5) { + // "Sane" accuracy randomization + // Broken into three tiers based on original accuracy + // Designed to limit the chances of 100% accurate OHKO moves and + // keep a decent base of 100% accurate regular moves. + + if (mv.hitratio <= 50) { + // lowest tier (acc <= 50) + // new accuracy = rand(20...100) inclusive + mv.hitratio = random.nextInt(17) * 5 + 20; + } else if (mv.hitratio < 90) { + // middle tier (50 < acc < 90) + // count down from 100% to 20% in 5% increments with 20% + // chance to "stop" and use the current accuracy at each + // increment + // gives decent-but-not-100% accuracy most of the time + mv.hitratio = 100; + while (mv.hitratio > 20) { + if (random.nextInt(10) < 2) { + break; + } + mv.hitratio -= 5; + } + } else { + // highest tier (90 <= acc <= 100) + // count down from 100% to 20% in 5% increments with 40% + // chance to "stop" and use the current accuracy at each + // increment + // gives high accuracy most of the time + mv.hitratio = 100; + while (mv.hitratio > 20) { + if (random.nextInt(10) < 4) { + break; + } + mv.hitratio -= 5; + } + } + } + } + } + + @Override + public void randomizeMoveTypes() { + List<Move> moves = this.getMoves(); + for (Move mv : moves) { + if (mv != null && mv.internalId != 165 && mv.type != null) { + mv.type = randomType(); + } + } + } + + @Override + public void randomizeMoveCategory() { + if (!this.hasPhysicalSpecialSplit()) { + return; + } + List<Move> moves = this.getMoves(); + for (Move mv : moves) { + if (mv != null && mv.internalId != 165 && mv.category != MoveCategory.STATUS) { + if (random.nextInt(2) == 0) { + mv.category = (mv.category == MoveCategory.PHYSICAL) ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL; + } + } + } + + } + + private Map<Integer, boolean[]> moveUpdates; + + @Override + public void initMoveUpdates() { + moveUpdates = new TreeMap<Integer, boolean[]>(); + } + + @Override + public void printMoveUpdates() { + log("--Move Updates--"); + List<Move> moves = this.getMoves(); + for (int moveID : moveUpdates.keySet()) { + boolean[] changes = moveUpdates.get(moveID); + Move mv = moves.get(moveID); + List<String> nonTypeChanges = new ArrayList<String>(); + if (changes[0]) { + nonTypeChanges.add(String.format("%d power", mv.power)); + } + if (changes[1]) { + nonTypeChanges.add(String.format("%d PP", mv.pp)); + } + if (changes[2]) { + nonTypeChanges.add(String.format("%.00f%% accuracy", mv.hitratio)); + } + String logStr = "Made " + mv.name; + // type or not? + if (changes[3]) { + logStr += " be " + mv.type + "-type"; + if (nonTypeChanges.size() > 0) { + logStr += " and"; + } + } + if (nonTypeChanges.size() > 0) { + logStr += " have "; + if (nonTypeChanges.size() == 3) { + logStr += nonTypeChanges.get(0) + ", " + nonTypeChanges.get(1) + " and " + nonTypeChanges.get(2); + } else if (nonTypeChanges.size() == 2) { + logStr += nonTypeChanges.get(0) + " and " + nonTypeChanges.get(1); + } else { + logStr += nonTypeChanges.get(0); + } + } + log(logStr); + } + logBlankLine(); + } + + @Override + public void updateMovesToGen5() { + List<Move> moves = this.getMoves(); + + // gen1 + // Karate Chop => FIGHTING (gen1) + updateMoveType(moves, 2, Type.FIGHTING); + // Razor Wind => 100% accuracy (gen1/2) + updateMoveAccuracy(moves, 13, 100); + // Gust => FLYING (gen1) + updateMoveType(moves, 16, Type.FLYING); + // Wing Attack => 60 power (gen1) + updateMovePower(moves, 17, 60); + // Whirlwind => 100 accuracy + updateMoveAccuracy(moves, 18, 100); + // Fly => 90 power (gen1/2/3) + updateMovePower(moves, 19, 90); + // Bind => 85% accuracy (gen1-4) + updateMoveAccuracy(moves, 20, 85); + // Vine Whip => 15 pp (gen1/2/3) + updateMovePP(moves, 22, 15); + // Jump Kick => 10 pp, 100 power (gen1-4) + updateMovePP(moves, 26, 10); + updateMovePower(moves, 26, 100); + // Sand Attack => GROUND (gen1) + updateMoveType(moves, 28, Type.GROUND); + // Tackle => 50 power, 100% accuracy , gen1-4 + updateMovePower(moves, 33, 50); + updateMoveAccuracy(moves, 33, 100); + // Wrap => 90% accuracy (gen1-4) + updateMoveAccuracy(moves, 35, 90); + // Thrash => 120 power, 10pp (gen1-4) + updateMovePP(moves, 37, 10); + updateMovePower(moves, 37, 120); + // Double-Edge => 120 power (gen1) + updateMovePower(moves, 38, 120); + // Move 44, Bite, becomes dark (but doesn't exist anyway) + // Disable => 100% accuracy (gen1-4) + updateMoveAccuracy(moves, 50, 100); + // Blizzard => 70% accuracy (gen1) + updateMoveAccuracy(moves, 59, 70); + // Move 67, Low Kick, has weight-based power in gen3+ + // Low Kick => 100% accuracy (gen1) + updateMoveAccuracy(moves, 67, 100); + // Absorb => 25pp (gen1/2/3) + updateMovePP(moves, 71, 25); + // Mega Drain => 15pp (gen1/2/3) + updateMovePP(moves, 72, 15); + // Petal Dance => 120power, 10pp (gen1-4) + updateMovePP(moves, 80, 10); + updateMovePower(moves, 80, 120); + // Fire Spin => 35 power, 85% acc (gen1-4) + updateMoveAccuracy(moves, 83, 85); + updateMovePower(moves, 83, 35); + // Rock Throw => 90% accuracy (gen1) + updateMoveAccuracy(moves, 88, 90); + // Dig => 80 power (gen1/2/3) + updateMovePower(moves, 91, 80); + // Toxic => 90% accuracy (gen1-4) + updateMoveAccuracy(moves, 92, 90); + // Hypnosis => 60% accuracy + updateMoveAccuracy(moves, 95, 60); + // Recover => 10pp (gen1/2/3) + updateMovePP(moves, 105, 10); + // SelfDestruct => 200power (gen1) + updateMovePower(moves, 120, 200); + // Clamp => 85% acc (gen1-4) + updateMoveAccuracy(moves, 128, 85); + updateMovePP(moves, 128, 15); + // HJKick => 130 power, 10pp (gen1-4) + updateMovePP(moves, 136, 10); + updateMovePower(moves, 136, 130); + // Glare => 90% acc (gen1-4) + updateMoveAccuracy(moves, 137, 90); + // Poison Gas => 80% acc (gen1-4) + updateMoveAccuracy(moves, 139, 80); + // Flash => 100% acc (gen1/2/3) + updateMoveAccuracy(moves, 148, 100); + // Crabhammer => 90% acc (gen1-4) + updateMoveAccuracy(moves, 152, 90); + // Explosion => 250 power (gen1) + updateMovePower(moves, 153, 250); + // GEN2+ moves only from here + if (moves.size() >= 251) { + // Curse => GHOST (gen2-4) + updateMoveType(moves, 174, Type.GHOST); + // Cotton Spore => 100% acc (gen2-4) + updateMoveAccuracy(moves, 178, 100); + // Scary Face => 100% acc (gen2-4) + updateMoveAccuracy(moves, 184, 100); + // Zap Cannon => 120 power (gen2-3) + updateMovePower(moves, 192, 120); + // Bone Rush => 90% acc (gen2-4) + updateMoveAccuracy(moves, 198, 90); + // Outrage => 120 power (gen2-3) + updateMovePower(moves, 200, 120); + updateMovePP(moves, 200, 10); + // Giga Drain => 10pp (gen2-3), 75 power (gen2-4) + updateMovePP(moves, 202, 10); + updateMovePower(moves, 202, 75); + // Fury Cutter => 20 power (gen2-4) + updateMovePower(moves, 210, 20); + // Future Sight => 10 pp, 100 power, 100% acc (gen2-4) + updateMovePP(moves, 248, 10); + updateMovePower(moves, 248, 100); + updateMoveAccuracy(moves, 248, 100); + // Rock Smash => 40 power (gen2-3) + updateMovePower(moves, 249, 40); + // Whirlpool => 35 pow, 85% acc (gen2-4) + updateMovePower(moves, 250, 35); + updateMoveAccuracy(moves, 250, 85); + } + // GEN3+ only moves from here + if (moves.size() >= 354) { + // Uproar => 90 power (gen3-4) + updateMovePower(moves, 253, 90); + updateMovePP(moves, 254, 20); + updateMovePower(moves, 291, 80); + // Sand Tomb => 35 pow, 85% acc (gen3-4) + updateMovePower(moves, 328, 35); + updateMoveAccuracy(moves, 328, 85); + // Bullet Seed => 25 power (gen3-4) + updateMovePower(moves, 331, 25); + // Icicle Spear => 25 power (gen3-4) + updateMovePower(moves, 333, 25); + // Covet => 60 power (gen3-4) + updateMovePower(moves, 343, 60); + updateMovePower(moves, 348, 90); + // Rock Blast => 90% acc (gen3-4) + updateMoveAccuracy(moves, 350, 90); + // Doom Desire => 140 pow, 100% acc, gen3-4 + updateMovePower(moves, 353, 140); + updateMoveAccuracy(moves, 353, 100); + } + // GEN4+ only moves from here + if (moves.size() >= 467) { + // Feint => 30 pow + updateMovePower(moves, 364, 30); + // Last Resort => 140 pow + updateMovePower(moves, 387, 140); + // Drain Punch => 10 pp, 75 pow + updateMovePP(moves, 409, 10); + updateMovePower(moves, 409, 75); + // Magma Storm => 75% acc + updateMoveAccuracy(moves, 463, 75); + } + } + + @Override + public void updateMovesToGen6() { + List<Move> moves = this.getMoves(); + + // gen 1 + // Swords Dance 20 PP + updateMovePP(moves, 14, 20); + + // Vine Whip 25 PP, 45 Power + updateMovePP(moves, 22, 25); + updateMovePower(moves, 22, 45); + + // Pin Missile 25 Power, 95% Accuracy + updateMovePower(moves, 42, 25); + updateMoveAccuracy(moves, 42, 95); + + // Flamethrower 90 Power + updateMovePower(moves, 53, 90); + + // Hydro Pump 110 Power + updateMovePower(moves, 56, 110); + + // Surf 90 Power + updateMovePower(moves, 57, 90); + + // Ice Beam 90 Power + updateMovePower(moves, 58, 90); + + // Blizzard 110 Power + updateMovePower(moves, 59, 110); + + // Growth 20 PP + updateMovePP(moves, 74, 20); + + // Thunderbolt 90 Power + updateMovePower(moves, 85, 90); + + // Thunder 110 Power + updateMovePower(moves, 87, 110); + + // Minimize 10 PP + updateMovePP(moves, 107, 10); + + // Barrier 20 PP + updateMovePP(moves, 112, 20); + + // Lick 30 Power + updateMovePower(moves, 122, 30); + + // Smog 30 Power + updateMovePower(moves, 123, 30); + + // Fire Blast 110 Power + updateMovePower(moves, 126, 110); + + // Skull Bash 10 PP, 130 Power + updateMovePP(moves, 130, 10); + updateMovePower(moves, 130, 130); + + // Glare 100% Accuracy + updateMoveAccuracy(moves, 137, 100); + + // Poison Gas 90% Accuracy + updateMoveAccuracy(moves, 139, 90); - // Bubble 40 Power - updateMovePower(moves, 145, 40); + // Bubble 40 Power + updateMovePower(moves, 145, 40); - // Psywave 100% Accuracy - updateMoveAccuracy(moves, 149, 100); + // Psywave 100% Accuracy + updateMoveAccuracy(moves, 149, 100); - // Acid Armor 20 PP - updateMovePP(moves, 151, 20); + // Acid Armor 20 PP + updateMovePP(moves, 151, 20); - // Crabhammer 100 Power - updateMovePower(moves, 152, 100); + // Crabhammer 100 Power + updateMovePower(moves, 152, 100); - // Gen2+ only - if (moves.size() >= 251) { - // Thief 25 PP, 60 Power - updateMovePP(moves, 168, 25); - updateMovePower(moves, 168, 60); + // Gen2+ only + if (moves.size() >= 251) { + // Thief 25 PP, 60 Power + updateMovePP(moves, 168, 25); + updateMovePower(moves, 168, 60); - // Snore 50 Power - updateMovePower(moves, 173, 50); + // Snore 50 Power + updateMovePower(moves, 173, 50); - // Fury Cutter 40 Power - updateMovePower(moves, 210, 40); + // Fury Cutter 40 Power + updateMovePower(moves, 210, 40); - // Future Sight 120 Power - updateMovePower(moves, 248, 120); - } + // Future Sight 120 Power + updateMovePower(moves, 248, 120); + } - // Gen3+ only - if (moves.size() >= 354) { - // Heat Wave 95 Power - updateMovePower(moves, 257, 95); + // Gen3+ only + if (moves.size() >= 354) { + // Heat Wave 95 Power + updateMovePower(moves, 257, 95); - // Will-o-Wisp 85% Accuracy - updateMoveAccuracy(moves, 261, 85); + // Will-o-Wisp 85% Accuracy + updateMoveAccuracy(moves, 261, 85); - // Smellingsalt 70 Power - updateMovePower(moves, 265, 70); + // Smellingsalt 70 Power + updateMovePower(moves, 265, 70); - // Knock off 65 Power - updateMovePower(moves, 282, 65); + // Knock off 65 Power + updateMovePower(moves, 282, 65); - // Meteor Mash 90 Power, 90% Accuracy - updateMovePower(moves, 309, 90); - updateMoveAccuracy(moves, 309, 90); + // Meteor Mash 90 Power, 90% Accuracy + updateMovePower(moves, 309, 90); + updateMoveAccuracy(moves, 309, 90); - // Air Cutter 60 Power - updateMovePower(moves, 314, 60); + // Air Cutter 60 Power + updateMovePower(moves, 314, 60); - // Overheat 130 Power - updateMovePower(moves, 315, 130); + // Overheat 130 Power + updateMovePower(moves, 315, 130); - // Rock Tomb 15 PP, 60 Power, 95% Accuracy - updateMovePP(moves, 317, 15); - updateMovePower(moves, 317, 60); - updateMoveAccuracy(moves, 317, 95); + // Rock Tomb 15 PP, 60 Power, 95% Accuracy + updateMovePP(moves, 317, 15); + updateMovePower(moves, 317, 60); + updateMoveAccuracy(moves, 317, 95); - // Extrasensory 20 PP - updateMovePP(moves, 326, 20); + // Extrasensory 20 PP + updateMovePP(moves, 326, 20); - // Muddy Water 90 Power - updateMovePower(moves, 330, 90); + // Muddy Water 90 Power + updateMovePower(moves, 330, 90); - // Covet 25 PP - updateMovePP(moves, 343, 25); - } + // Covet 25 PP + updateMovePP(moves, 343, 25); + } - // Gen4+ only - if (moves.size() >= 467) { - // Wake-Up Slap 70 Power - updateMovePower(moves, 358, 70); + // Gen4+ only + if (moves.size() >= 467) { + // Wake-Up Slap 70 Power + updateMovePower(moves, 358, 70); - // Tailwind 15 PP - updateMovePP(moves, 366, 15); + // Tailwind 15 PP + updateMovePP(moves, 366, 15); - // Assurance 60 Power - updateMovePower(moves, 372, 60); + // Assurance 60 Power + updateMovePower(moves, 372, 60); - // Psycho Shift 100% Accuracy - updateMoveAccuracy(moves, 375, 100); + // Psycho Shift 100% Accuracy + updateMoveAccuracy(moves, 375, 100); - // Aura Sphere 80 Power - updateMovePower(moves, 396, 80); + // Aura Sphere 80 Power + updateMovePower(moves, 396, 80); - // Air Slash 15 PP - updateMovePP(moves, 403, 15); + // Air Slash 15 PP + updateMovePP(moves, 403, 15); - // Dragon Pulse 85 Power - updateMovePower(moves, 406, 85); + // Dragon Pulse 85 Power + updateMovePower(moves, 406, 85); - // Power Gem 80 Power - updateMovePower(moves, 408, 80); + // Power Gem 80 Power + updateMovePower(moves, 408, 80); - // Energy Ball 90 Power - updateMovePower(moves, 412, 90); + // Energy Ball 90 Power + updateMovePower(moves, 412, 90); - // Draco Meteor 130 Power - updateMovePower(moves, 434, 130); + // Draco Meteor 130 Power + updateMovePower(moves, 434, 130); - // Leaf Storm 130 Power - updateMovePower(moves, 437, 130); + // Leaf Storm 130 Power + updateMovePower(moves, 437, 130); - // Gunk Shot 80% Accuracy - updateMoveAccuracy(moves, 441, 80); - - // Chatter 65 Power - updateMovePower(moves, 448, 65); - - // Magma Storm 100 Power - updateMovePower(moves, 463, 100); - } - - // Gen5+ only - if (moves.size() >= 559) { - // Storm Throw 60 Power - updateMovePower(moves, 480, 60); - - // Synchronoise 120 Power - updateMovePower(moves, 485, 120); - - // Low Sweep 65 Power - updateMovePower(moves, 490, 65); - - // Hex 65 Power - updateMovePower(moves, 506, 65); - - // Incinerate 60 Power - updateMovePower(moves, 510, 60); - - // Pledges 80 Power - updateMovePower(moves, 518, 80); - updateMovePower(moves, 519, 80); - updateMovePower(moves, 520, 80); - - // Struggle Bug 50 Power - updateMovePower(moves, 522, 50); - - // Frost Breath 45 Power - // crits are 2x in these games - updateMovePower(moves, 524, 45); - - // Sacred Sword 15 PP - updateMovePP(moves, 533, 15); - - // Hurricane 110 Power - updateMovePower(moves, 542, 110); - - // Techno Blast 120 Power - updateMovePower(moves, 546, 120); - } - } - - private void updateMovePower(List<Move> moves, int moveNum, int power) { - Move mv = moves.get(moveNum); - if (mv.power != power) { - mv.power = power; - addMoveUpdate(moveNum, 0); - } - } - - private void updateMovePP(List<Move> moves, int moveNum, int pp) { - Move mv = moves.get(moveNum); - if (mv.pp != pp) { - mv.pp = pp; - addMoveUpdate(moveNum, 1); - } - } - - private void updateMoveAccuracy(List<Move> moves, int moveNum, int accuracy) { - Move mv = moves.get(moveNum); - if (Math.abs(mv.hitratio - accuracy) >= 1) { - mv.setAccuracy(accuracy); - addMoveUpdate(moveNum, 2); - } - } - - private void updateMoveType(List<Move> moves, int moveNum, Type type) { - Move mv = moves.get(moveNum); - if (mv.type != type) { - mv.type = type; - addMoveUpdate(moveNum, 3); - } - } - - private void addMoveUpdate(int moveNum, int updateType) { - if (!moveUpdates.containsKey(moveNum)) { - boolean[] updateField = new boolean[4]; - updateField[updateType] = true; - moveUpdates.put(moveNum, updateField); - } else { - moveUpdates.get(moveNum)[updateType] = true; - } - } - - private int pickMove(Pokemon pkmn, boolean typeThemed, boolean damaging, - List<Integer> bannedForThisGame) { - - // If damaging, we want a move with at least 80% accuracy and 2 power - List<Move> allMoves = this.getMoves(); - Type typeOfMove = null; - double picked = this.random.nextDouble(); - // Type? - if (typeThemed) { - if (pkmn.primaryType == Type.NORMAL - || pkmn.secondaryType == Type.NORMAL) { - if (pkmn.secondaryType == null) { - // Pure NORMAL: 75% normal, 25% random - if (picked < 0.75) { - typeOfMove = Type.NORMAL; - } - // else random - } else { - // Find the other type - // Normal/OTHER: 30% normal, 55% other, 15% random - Type otherType = pkmn.primaryType; - if (otherType == Type.NORMAL) { - otherType = pkmn.secondaryType; - } - if (picked < 0.3) { - typeOfMove = Type.NORMAL; - } else if (picked < 0.85) { - typeOfMove = otherType; - } - // else random - } - } else if (pkmn.secondaryType != null) { - // Primary/Secondary: 50% primary, 30% secondary, 5% normal, 15% - // random - if (picked < 0.5) { - typeOfMove = pkmn.primaryType; - } else if (picked < 0.8) { - typeOfMove = pkmn.secondaryType; - } else if (picked < 0.85) { - typeOfMove = Type.NORMAL; - } - // else random - } else { - // Primary/None: 60% primary, 20% normal, 20% random - if (picked < 0.6) { - typeOfMove = pkmn.primaryType; - } else if (picked < 0.8) { - typeOfMove = Type.NORMAL; - } - // else random - } - } - // Filter by type, and if necessary, by damage - List<Move> canPick = new ArrayList<Move>(); - for (Move mv : allMoves) { - if (mv != null && !RomFunctions.bannedRandomMoves[mv.number] - && !bannedForThisGame.contains(mv.number) - && (mv.type == typeOfMove || typeOfMove == null)) { - if (!damaging - || (mv.power > 1 && mv.hitratio > 79 && !RomFunctions.bannedForDamagingMove[mv.number])) { - canPick.add(mv); - } - } - } - // If we ended up with no results, reroll - if (canPick.size() == 0) { - return pickMove(pkmn, typeThemed, damaging, bannedForThisGame); - } else { - // pick a random one - return canPick.get(this.random.nextInt(canPick.size())).number; - } - } - - private List<Pokemon> pokemonOfType(Type type, boolean noLegendaries) { - List<Pokemon> typedPokes = new ArrayList<Pokemon>(); - for (Pokemon pk : mainPokemonList) { - if (pk != null && (!noLegendaries || !pk.isLegendary())) { - if (pk.primaryType == type || pk.secondaryType == type) { - typedPokes.add(pk); - } - } - } - return typedPokes; - } - - private List<Pokemon> allPokemonWithoutNull() { - List<Pokemon> allPokes = new ArrayList<Pokemon>(this.getPokemon()); - allPokes.remove(0); - return allPokes; - } - - private Set<Pokemon> pokemonInArea(EncounterSet area) { - Set<Pokemon> inArea = new TreeSet<Pokemon>(); - for (Encounter enc : area.encounters) { - inArea.add(enc.pokemon); - } - return inArea; - } - - private Map<Type, Integer> typeWeightings; - private int totalTypeWeighting; - - private Type pickType(boolean weightByFrequency, boolean noLegendaries) { - if (totalTypeWeighting == 0) { - // Determine weightings - for (Type t : Type.values()) { - if (typeInGame(t)) { - int pkWithTyping = pokemonOfType(t, noLegendaries).size(); - typeWeightings.put(t, pkWithTyping); - totalTypeWeighting += pkWithTyping; - } - } - } - - if (weightByFrequency) { - int typePick = this.random.nextInt(totalTypeWeighting); - int typePos = 0; - for (Type t : typeWeightings.keySet()) { - int weight = typeWeightings.get(t); - if (typePos + weight > typePick) { - return t; - } - typePos += weight; - } - return null; - } else { - return randomType(); - } - } - - private void rivalCarriesStarterUpdate(List<Trainer> currentTrainers, - String prefix, int pokemonOffset) { - // Find the highest rival battle # - int highestRivalNum = 0; - for (Trainer t : currentTrainers) { - if (t.tag != null && t.tag.startsWith(prefix)) { - highestRivalNum = Math.max( - highestRivalNum, - Integer.parseInt(t.tag.substring(prefix.length(), - t.tag.indexOf('-')))); - } - } - - if (highestRivalNum == 0) { - // This rival type not used in this game - return; - } - - // Get the starters - // us 0 1 2 => them 0+n 1+n 2+n - List<Pokemon> starters = this.getStarters(); - - // Yellow needs its own case, unfortunately. - if (isYellow()) { - // The rival's starter is index 1 - Pokemon rivalStarter = starters.get(1); - int timesEvolves = numEvolutions(rivalStarter, 2); - // Apply evolutions as appropriate - if (timesEvolves == 0) { - for (int j = 1; j <= 3; j++) { - changeStarterWithTag(currentTrainers, prefix + j + "-0", - rivalStarter); - } - for (int j = 4; j <= 7; j++) { - for (int i = 0; i < 3; i++) { - changeStarterWithTag(currentTrainers, prefix + j + "-" - + i, rivalStarter); - } - } - } else if (timesEvolves == 1) { - for (int j = 1; j <= 3; j++) { - changeStarterWithTag(currentTrainers, prefix + j + "-0", - rivalStarter); - } - rivalStarter = pickRandomEvolutionOf(rivalStarter, false); - for (int j = 4; j <= 7; j++) { - for (int i = 0; i < 3; i++) { - changeStarterWithTag(currentTrainers, prefix + j + "-" - + i, rivalStarter); - } - } - } else if (timesEvolves == 2) { - for (int j = 1; j <= 2; j++) { - changeStarterWithTag(currentTrainers, prefix + j + "-" + 0, - rivalStarter); - } - rivalStarter = pickRandomEvolutionOf(rivalStarter, true); - changeStarterWithTag(currentTrainers, prefix + "3-0", - rivalStarter); - for (int i = 0; i < 3; i++) { - changeStarterWithTag(currentTrainers, prefix + "4-" + i, - rivalStarter); - } - rivalStarter = pickRandomEvolutionOf(rivalStarter, false); - for (int j = 5; j <= 7; j++) { - for (int i = 0; i < 3; i++) { - changeStarterWithTag(currentTrainers, prefix + j + "-" - + i, rivalStarter); - } - } - } - } else { - // Replace each starter as appropriate - // Use level to determine when to evolve, not number anymore - for (int i = 0; i < 3; i++) { - // Rival's starters are pokemonOffset over from each of ours - int starterToUse = (i + pokemonOffset) % 3; - Pokemon thisStarter = starters.get(starterToUse); - int timesEvolves = numEvolutions(thisStarter, 2); - // If a fully evolved pokemon, use throughout - // Otherwise split by evolutions as appropriate - if (timesEvolves == 0) { - for (int j = 1; j <= highestRivalNum; j++) { - changeStarterWithTag(currentTrainers, prefix + j + "-" - + i, thisStarter); - } - } else if (timesEvolves == 1) { - int j = 1; - for (; j <= highestRivalNum / 2; j++) { - if (getLevelOfStarter(currentTrainers, prefix + j + "-" - + i) >= 30) { - break; - } - changeStarterWithTag(currentTrainers, prefix + j + "-" - + i, thisStarter); - } - thisStarter = pickRandomEvolutionOf(thisStarter, false); - for (; j <= highestRivalNum; j++) { - changeStarterWithTag(currentTrainers, prefix + j + "-" - + i, thisStarter); - } - } else if (timesEvolves == 2) { - int j = 1; - for (; j <= highestRivalNum; j++) { - if (getLevelOfStarter(currentTrainers, prefix + j + "-" - + i) >= 16) { - break; - } - changeStarterWithTag(currentTrainers, prefix + j + "-" - + i, thisStarter); - } - thisStarter = pickRandomEvolutionOf(thisStarter, true); - for (; j <= highestRivalNum; j++) { - if (getLevelOfStarter(currentTrainers, prefix + j + "-" - + i) >= 36) { - break; - } - changeStarterWithTag(currentTrainers, prefix + j + "-" - + i, thisStarter); - } - thisStarter = pickRandomEvolutionOf(thisStarter, false); - for (; j <= highestRivalNum; j++) { - changeStarterWithTag(currentTrainers, prefix + j + "-" - + i, thisStarter); - } - } - } - } - - } - - private Pokemon pickRandomEvolutionOf(Pokemon base, boolean mustEvolveItself) { - // Used for "rival carries starter" - // Pick a random evolution of base Pokemon, subject to - // "must evolve itself" if appropriate. - List<Pokemon> candidates = new ArrayList<Pokemon>(); - for (Evolution ev : base.evolutionsFrom) { - if (!mustEvolveItself || ev.to.evolutionsFrom.size() > 0) { - candidates.add(ev.to); - } - } - - if (candidates.size() == 0) { - throw new RuntimeException( - "Random evolution called on a Pokemon without any usable evolutions."); - } - - return candidates.get(random.nextInt(candidates.size())); - } - - private int getLevelOfStarter(List<Trainer> currentTrainers, String tag) { - for (Trainer t : currentTrainers) { - if (t.tag != null && t.tag.equals(tag)) { - // Bingo, get highest level - // last pokemon is given priority +2 but equal priority - // = first pokemon wins, so its effectively +1 - // If it's tagged the same we can assume it's the same team - // just the opposite gender or something like that... - // So no need to check other trainers with same tag. - int highestLevel = t.pokemon.get(0).level; - int trainerPkmnCount = t.pokemon.size(); - for (int i = 1; i < trainerPkmnCount; i++) { - int levelBonus = (i == trainerPkmnCount - 1) ? 2 : 0; - if (t.pokemon.get(i).level + levelBonus > highestLevel) { - highestLevel = t.pokemon.get(i).level; - } - } - return highestLevel; - } - } - return 0; - } - - private void changeStarterWithTag(List<Trainer> currentTrainers, - String tag, Pokemon starter) { - for (Trainer t : currentTrainers) { - if (t.tag != null && t.tag.equals(tag)) { - // Bingo - // Change the highest level pokemon, not the last. - // BUT: last gets +2 lvl priority (effectively +1) - // same as above, equal priority = earlier wins - TrainerPokemon bestPoke = t.pokemon.get(0); - int trainerPkmnCount = t.pokemon.size(); - for (int i = 1; i < trainerPkmnCount; i++) { - int levelBonus = (i == trainerPkmnCount - 1) ? 2 : 0; - if (t.pokemon.get(i).level + levelBonus > bestPoke.level) { - bestPoke = t.pokemon.get(i); - } - } - bestPoke.pokemon = starter; - } - } - - } - - // Return the max depth of pre-evolutions a Pokemon has - private int numPreEvolutions(Pokemon pk, int maxInterested) { - return numPreEvolutions(pk, 0, maxInterested); - } - - private int numPreEvolutions(Pokemon pk, int depth, int maxInterested) { - if (pk.evolutionsTo.size() == 0) { - return 0; - } else { - if (depth == maxInterested - 1) { - return 1; - } else { - int maxPreEvos = 0; - for (Evolution ev : pk.evolutionsTo) { - maxPreEvos = Math - .max(maxPreEvos, - numPreEvolutions(ev.from, depth + 1, - maxInterested) + 1); - } - return maxPreEvos; - } - } - } - - private int numEvolutions(Pokemon pk, int maxInterested) { - return numEvolutions(pk, 0, maxInterested); - } - - private int numEvolutions(Pokemon pk, int depth, int maxInterested) { - if (pk.evolutionsFrom.size() == 0) { - return 0; - } else { - if (depth == maxInterested - 1) { - return 1; - } else { - int maxEvos = 0; - for (Evolution ev : pk.evolutionsFrom) { - maxEvos = Math.max(maxEvos, - numEvolutions(ev.to, depth + 1, maxInterested) + 1); - } - return maxEvos; - } - } - } - - private Set<Pokemon> relatedPokemon(Pokemon original) { - Set<Pokemon> results = new HashSet<Pokemon>(); - results.add(original); - Queue<Pokemon> toCheck = new LinkedList<Pokemon>(); - toCheck.add(original); - while (!toCheck.isEmpty()) { - Pokemon check = toCheck.poll(); - for (Evolution ev : check.evolutionsFrom) { - if (!results.contains(ev.to)) { - results.add(ev.to); - toCheck.add(ev.to); - } - } - for (Evolution ev : check.evolutionsTo) { - if (!results.contains(ev.from)) { - results.add(ev.from); - toCheck.add(ev.from); - } - } - } - return results; - } - - private Map<Type, List<Pokemon>> cachedReplacementLists; - private List<Pokemon> cachedAllList; - - private Pokemon pickReplacement(Pokemon current, boolean usePowerLevels, - Type type, boolean noLegendaries, boolean wonderGuardAllowed) { - List<Pokemon> pickFrom = cachedAllList; - if (type != null) { - if (!cachedReplacementLists.containsKey(type)) { - cachedReplacementLists.put(type, - pokemonOfType(type, noLegendaries)); - } - pickFrom = cachedReplacementLists.get(type); - } - - if (usePowerLevels) { - // start with within 10% and add 5% either direction till we find - // something - int currentBST = current.bstForPowerLevels(); - int minTarget = currentBST - currentBST / 10; - int maxTarget = currentBST + currentBST / 10; - List<Pokemon> canPick = new ArrayList<Pokemon>(); - int expandRounds = 0; - while (canPick.isEmpty() - || (canPick.size() < 3 && expandRounds < 2)) { - for (Pokemon pk : pickFrom) { - if (pk.bstForPowerLevels() >= minTarget - && pk.bstForPowerLevels() <= maxTarget - && (wonderGuardAllowed || (pk.ability1 != WONDER_GUARD_INDEX - && pk.ability2 != WONDER_GUARD_INDEX && pk.ability3 != WONDER_GUARD_INDEX))) { - canPick.add(pk); - } - } - minTarget -= currentBST / 20; - maxTarget += currentBST / 20; - expandRounds++; - } - return canPick.get(this.random.nextInt(canPick.size())); - } else { - if (wonderGuardAllowed) { - return pickFrom.get(this.random.nextInt(pickFrom.size())); - } else { - Pokemon pk = pickFrom.get(this.random.nextInt(pickFrom.size())); - while (pk.ability1 == WONDER_GUARD_INDEX - || pk.ability2 == WONDER_GUARD_INDEX - || pk.ability3 == WONDER_GUARD_INDEX) { - pk = pickFrom.get(this.random.nextInt(pickFrom.size())); - } - return pk; - } - } - } - - private Pokemon pickWildPowerLvlReplacement(List<Pokemon> pokemonPool, - Pokemon current, boolean banSamePokemon, List<Pokemon> usedUp) { - // start with within 10% and add 5% either direction till we find - // something - int currentBST = current.bstForPowerLevels(); - int minTarget = currentBST - currentBST / 10; - int maxTarget = currentBST + currentBST / 10; - List<Pokemon> canPick = new ArrayList<Pokemon>(); - int expandRounds = 0; - while (canPick.isEmpty() || (canPick.size() < 3 && expandRounds < 3)) { - for (Pokemon pk : pokemonPool) { - if (pk.bstForPowerLevels() >= minTarget - && pk.bstForPowerLevels() <= maxTarget - && (!banSamePokemon || pk != current) - && (usedUp == null || !usedUp.contains(pk)) - && !canPick.contains(pk)) { - canPick.add(pk); - } - } - minTarget -= currentBST / 20; - maxTarget += currentBST / 20; - expandRounds++; - } - return canPick.get(this.random.nextInt(canPick.size())); - } - - /* Helper methods used by subclasses */ - - public void setLog(PrintStream logStream) { - this.logStream = logStream; - } - - protected void log(String log) { - if (logStream != null) { - logStream.println(log); - } - } - - protected void logBlankLine() { - if (logStream != null) { - logStream.println(); - } - } - - protected void logEvoChangeLevel(String pkFrom, String pkTo, int level) { - if (logStream != null) { - logStream.printf("Made %s evolve into %s at level %d", pkFrom, - pkTo, level); - logStream.println(); - } - } - - protected void logEvoChangeLevelWithItem(String pkFrom, String pkTo, - String itemName) { - if (logStream != null) { - logStream.printf( - "Made %s evolve into %s by leveling up holding %s", pkFrom, - pkTo, itemName); - logStream.println(); - } - } - - protected void logEvoChangeStone(String pkFrom, String pkTo, String itemName) { - if (logStream != null) { - logStream.printf("Made %s evolve into %s using a %s", pkFrom, pkTo, - itemName); - logStream.println(); - } - } - - protected void logEvoChangeLevelWithPkmn(String pkFrom, String pkTo, - String otherRequired) { - if (logStream != null) { - logStream - .printf("Made %s evolve into %s by leveling up with %s in the party", - pkFrom, pkTo, otherRequired); - logStream.println(); - } - } - - /* Default Implementations */ - /* Used when a subclass doesn't override */ - - @Override - public boolean hasTimeBasedEncounters() { - // DEFAULT: no - return false; - } - - @Override - public boolean typeInGame(Type type) { - return type.isHackOnly == false; - } - - @Override - public String abilityName(int number) { - return ""; - } - - @Override - public Type randomType() { - Type t = Type.randomType(this.random); - while (!typeInGame(t)) { - t = Type.randomType(this.random); - } - return t; - } - - @Override - public boolean isYellow() { - return false; - } - - @Override - public boolean canChangeStarters() { - return true; - } - - @SuppressWarnings("unchecked") - @Override - public List<Pokemon> bannedForWildEncounters() { - return (List<Pokemon>) Collections.EMPTY_LIST; - } - - @SuppressWarnings("unchecked") - @Override - public List<Pokemon> bannedForStaticPokemon() { - return (List<Pokemon>) Collections.EMPTY_LIST; - } - - @Override - public int miscTweaksAvailable() { - // default: none - return 0; - } - - @Override - public void applyMiscTweak(MiscTweak tweak) { - // default: do nothing - } - - @Override - public List<Integer> getGameBreakingMoves() { - // Sonicboom & drage - return Arrays.asList(49, 82); - } - - @SuppressWarnings("unchecked") - @Override - public List<Integer> getMovesBannedFromLevelup() { - return (List<Integer>) Collections.EMPTY_LIST; - } - - @Override - public boolean isROMHack() { - // override until detection implemented - return false; - } + // Gunk Shot 80% Accuracy + updateMoveAccuracy(moves, 441, 80); + + // Chatter 65 Power + updateMovePower(moves, 448, 65); + + // Magma Storm 100 Power + updateMovePower(moves, 463, 100); + } + + // Gen5+ only + if (moves.size() >= 559) { + // Storm Throw 60 Power + updateMovePower(moves, 480, 60); + + // Synchronoise 120 Power + updateMovePower(moves, 485, 120); + + // Low Sweep 65 Power + updateMovePower(moves, 490, 65); + + // Hex 65 Power + updateMovePower(moves, 506, 65); + + // Incinerate 60 Power + updateMovePower(moves, 510, 60); + + // Pledges 80 Power + updateMovePower(moves, 518, 80); + updateMovePower(moves, 519, 80); + updateMovePower(moves, 520, 80); + + // Struggle Bug 50 Power + updateMovePower(moves, 522, 50); + + // Frost Breath 45 Power + // crits are 2x in these games + updateMovePower(moves, 524, 45); + + // Sacred Sword 15 PP + updateMovePP(moves, 533, 15); + + // Hurricane 110 Power + updateMovePower(moves, 542, 110); + + // Techno Blast 120 Power + updateMovePower(moves, 546, 120); + } + } + + private void updateMovePower(List<Move> moves, int moveNum, int power) { + Move mv = moves.get(moveNum); + if (mv.power != power) { + mv.power = power; + addMoveUpdate(moveNum, 0); + } + } + + private void updateMovePP(List<Move> moves, int moveNum, int pp) { + Move mv = moves.get(moveNum); + if (mv.pp != pp) { + mv.pp = pp; + addMoveUpdate(moveNum, 1); + } + } + + private void updateMoveAccuracy(List<Move> moves, int moveNum, int accuracy) { + Move mv = moves.get(moveNum); + if (Math.abs(mv.hitratio - accuracy) >= 1) { + mv.setAccuracy(accuracy); + addMoveUpdate(moveNum, 2); + } + } + + private void updateMoveType(List<Move> moves, int moveNum, Type type) { + Move mv = moves.get(moveNum); + if (mv.type != type) { + mv.type = type; + addMoveUpdate(moveNum, 3); + } + } + + private void addMoveUpdate(int moveNum, int updateType) { + if (!moveUpdates.containsKey(moveNum)) { + boolean[] updateField = new boolean[4]; + updateField[updateType] = true; + moveUpdates.put(moveNum, updateField); + } else { + moveUpdates.get(moveNum)[updateType] = true; + } + } + + private int pickMove(Pokemon pkmn, boolean typeThemed, boolean damaging, List<Integer> bannedForThisGame) { + + // If damaging, we want a move with at least 80% accuracy and 2 power + List<Move> allMoves = this.getMoves(); + Type typeOfMove = null; + double picked = this.random.nextDouble(); + // Type? + if (typeThemed) { + if (pkmn.primaryType == Type.NORMAL || pkmn.secondaryType == Type.NORMAL) { + if (pkmn.secondaryType == null) { + // Pure NORMAL: 75% normal, 25% random + if (picked < 0.75) { + typeOfMove = Type.NORMAL; + } + // else random + } else { + // Find the other type + // Normal/OTHER: 30% normal, 55% other, 15% random + Type otherType = pkmn.primaryType; + if (otherType == Type.NORMAL) { + otherType = pkmn.secondaryType; + } + if (picked < 0.3) { + typeOfMove = Type.NORMAL; + } else if (picked < 0.85) { + typeOfMove = otherType; + } + // else random + } + } else if (pkmn.secondaryType != null) { + // Primary/Secondary: 50% primary, 30% secondary, 5% normal, 15% + // random + if (picked < 0.5) { + typeOfMove = pkmn.primaryType; + } else if (picked < 0.8) { + typeOfMove = pkmn.secondaryType; + } else if (picked < 0.85) { + typeOfMove = Type.NORMAL; + } + // else random + } else { + // Primary/None: 60% primary, 20% normal, 20% random + if (picked < 0.6) { + typeOfMove = pkmn.primaryType; + } else if (picked < 0.8) { + typeOfMove = Type.NORMAL; + } + // else random + } + } + // Filter by type, and if necessary, by damage + List<Move> canPick = new ArrayList<Move>(); + for (Move mv : allMoves) { + if (mv != null && !RomFunctions.bannedRandomMoves[mv.number] && !bannedForThisGame.contains(mv.number) + && (mv.type == typeOfMove || typeOfMove == null)) { + if (!damaging || (mv.power > 1 && mv.hitratio > 79 && !RomFunctions.bannedForDamagingMove[mv.number])) { + canPick.add(mv); + } + } + } + // If we ended up with no results, reroll + if (canPick.size() == 0) { + return pickMove(pkmn, typeThemed, damaging, bannedForThisGame); + } else { + // pick a random one + return canPick.get(this.random.nextInt(canPick.size())).number; + } + } + + private List<Pokemon> pokemonOfType(Type type, boolean noLegendaries) { + List<Pokemon> typedPokes = new ArrayList<Pokemon>(); + for (Pokemon pk : mainPokemonList) { + if (pk != null && (!noLegendaries || !pk.isLegendary())) { + if (pk.primaryType == type || pk.secondaryType == type) { + typedPokes.add(pk); + } + } + } + return typedPokes; + } + + private List<Pokemon> allPokemonWithoutNull() { + List<Pokemon> allPokes = new ArrayList<Pokemon>(this.getPokemon()); + allPokes.remove(0); + return allPokes; + } + + private Set<Pokemon> pokemonInArea(EncounterSet area) { + Set<Pokemon> inArea = new TreeSet<Pokemon>(); + for (Encounter enc : area.encounters) { + inArea.add(enc.pokemon); + } + return inArea; + } + + private Map<Type, Integer> typeWeightings; + private int totalTypeWeighting; + + private Type pickType(boolean weightByFrequency, boolean noLegendaries) { + if (totalTypeWeighting == 0) { + // Determine weightings + for (Type t : Type.values()) { + if (typeInGame(t)) { + int pkWithTyping = pokemonOfType(t, noLegendaries).size(); + typeWeightings.put(t, pkWithTyping); + totalTypeWeighting += pkWithTyping; + } + } + } + + if (weightByFrequency) { + int typePick = this.random.nextInt(totalTypeWeighting); + int typePos = 0; + for (Type t : typeWeightings.keySet()) { + int weight = typeWeightings.get(t); + if (typePos + weight > typePick) { + return t; + } + typePos += weight; + } + return null; + } else { + return randomType(); + } + } + + private void rivalCarriesStarterUpdate(List<Trainer> currentTrainers, String prefix, int pokemonOffset) { + // Find the highest rival battle # + int highestRivalNum = 0; + for (Trainer t : currentTrainers) { + if (t.tag != null && t.tag.startsWith(prefix)) { + highestRivalNum = Math.max(highestRivalNum, + Integer.parseInt(t.tag.substring(prefix.length(), t.tag.indexOf('-')))); + } + } + + if (highestRivalNum == 0) { + // This rival type not used in this game + return; + } + + // Get the starters + // us 0 1 2 => them 0+n 1+n 2+n + List<Pokemon> starters = this.getStarters(); + + // Yellow needs its own case, unfortunately. + if (isYellow()) { + // The rival's starter is index 1 + Pokemon rivalStarter = starters.get(1); + int timesEvolves = numEvolutions(rivalStarter, 2); + // Apply evolutions as appropriate + if (timesEvolves == 0) { + for (int j = 1; j <= 3; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-0", rivalStarter); + } + for (int j = 4; j <= 7; j++) { + for (int i = 0; i < 3; i++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, rivalStarter); + } + } + } else if (timesEvolves == 1) { + for (int j = 1; j <= 3; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-0", rivalStarter); + } + rivalStarter = pickRandomEvolutionOf(rivalStarter, false); + for (int j = 4; j <= 7; j++) { + for (int i = 0; i < 3; i++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, rivalStarter); + } + } + } else if (timesEvolves == 2) { + for (int j = 1; j <= 2; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + 0, rivalStarter); + } + rivalStarter = pickRandomEvolutionOf(rivalStarter, true); + changeStarterWithTag(currentTrainers, prefix + "3-0", rivalStarter); + for (int i = 0; i < 3; i++) { + changeStarterWithTag(currentTrainers, prefix + "4-" + i, rivalStarter); + } + rivalStarter = pickRandomEvolutionOf(rivalStarter, false); + for (int j = 5; j <= 7; j++) { + for (int i = 0; i < 3; i++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, rivalStarter); + } + } + } + } else { + // Replace each starter as appropriate + // Use level to determine when to evolve, not number anymore + for (int i = 0; i < 3; i++) { + // Rival's starters are pokemonOffset over from each of ours + int starterToUse = (i + pokemonOffset) % 3; + Pokemon thisStarter = starters.get(starterToUse); + int timesEvolves = numEvolutions(thisStarter, 2); + // If a fully evolved pokemon, use throughout + // Otherwise split by evolutions as appropriate + if (timesEvolves == 0) { + for (int j = 1; j <= highestRivalNum; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter); + } + } else if (timesEvolves == 1) { + int j = 1; + for (; j <= highestRivalNum / 2; j++) { + if (getLevelOfStarter(currentTrainers, prefix + j + "-" + i) >= 30) { + break; + } + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter); + } + thisStarter = pickRandomEvolutionOf(thisStarter, false); + for (; j <= highestRivalNum; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter); + } + } else if (timesEvolves == 2) { + int j = 1; + for (; j <= highestRivalNum; j++) { + if (getLevelOfStarter(currentTrainers, prefix + j + "-" + i) >= 16) { + break; + } + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter); + } + thisStarter = pickRandomEvolutionOf(thisStarter, true); + for (; j <= highestRivalNum; j++) { + if (getLevelOfStarter(currentTrainers, prefix + j + "-" + i) >= 36) { + break; + } + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter); + } + thisStarter = pickRandomEvolutionOf(thisStarter, false); + for (; j <= highestRivalNum; j++) { + changeStarterWithTag(currentTrainers, prefix + j + "-" + i, thisStarter); + } + } + } + } + + } + + private Pokemon pickRandomEvolutionOf(Pokemon base, boolean mustEvolveItself) { + // Used for "rival carries starter" + // Pick a random evolution of base Pokemon, subject to + // "must evolve itself" if appropriate. + List<Pokemon> candidates = new ArrayList<Pokemon>(); + for (Evolution ev : base.evolutionsFrom) { + if (!mustEvolveItself || ev.to.evolutionsFrom.size() > 0) { + candidates.add(ev.to); + } + } + + if (candidates.size() == 0) { + throw new RuntimeException("Random evolution called on a Pokemon without any usable evolutions."); + } + + return candidates.get(random.nextInt(candidates.size())); + } + + private int getLevelOfStarter(List<Trainer> currentTrainers, String tag) { + for (Trainer t : currentTrainers) { + if (t.tag != null && t.tag.equals(tag)) { + // Bingo, get highest level + // last pokemon is given priority +2 but equal priority + // = first pokemon wins, so its effectively +1 + // If it's tagged the same we can assume it's the same team + // just the opposite gender or something like that... + // So no need to check other trainers with same tag. + int highestLevel = t.pokemon.get(0).level; + int trainerPkmnCount = t.pokemon.size(); + for (int i = 1; i < trainerPkmnCount; i++) { + int levelBonus = (i == trainerPkmnCount - 1) ? 2 : 0; + if (t.pokemon.get(i).level + levelBonus > highestLevel) { + highestLevel = t.pokemon.get(i).level; + } + } + return highestLevel; + } + } + return 0; + } + + private void changeStarterWithTag(List<Trainer> currentTrainers, String tag, Pokemon starter) { + for (Trainer t : currentTrainers) { + if (t.tag != null && t.tag.equals(tag)) { + // Bingo + // Change the highest level pokemon, not the last. + // BUT: last gets +2 lvl priority (effectively +1) + // same as above, equal priority = earlier wins + TrainerPokemon bestPoke = t.pokemon.get(0); + int trainerPkmnCount = t.pokemon.size(); + for (int i = 1; i < trainerPkmnCount; i++) { + int levelBonus = (i == trainerPkmnCount - 1) ? 2 : 0; + if (t.pokemon.get(i).level + levelBonus > bestPoke.level) { + bestPoke = t.pokemon.get(i); + } + } + bestPoke.pokemon = starter; + } + } + + } + + // Return the max depth of pre-evolutions a Pokemon has + private int numPreEvolutions(Pokemon pk, int maxInterested) { + return numPreEvolutions(pk, 0, maxInterested); + } + + private int numPreEvolutions(Pokemon pk, int depth, int maxInterested) { + if (pk.evolutionsTo.size() == 0) { + return 0; + } else { + if (depth == maxInterested - 1) { + return 1; + } else { + int maxPreEvos = 0; + for (Evolution ev : pk.evolutionsTo) { + maxPreEvos = Math.max(maxPreEvos, numPreEvolutions(ev.from, depth + 1, maxInterested) + 1); + } + return maxPreEvos; + } + } + } + + private int numEvolutions(Pokemon pk, int maxInterested) { + return numEvolutions(pk, 0, maxInterested); + } + + private int numEvolutions(Pokemon pk, int depth, int maxInterested) { + if (pk.evolutionsFrom.size() == 0) { + return 0; + } else { + if (depth == maxInterested - 1) { + return 1; + } else { + int maxEvos = 0; + for (Evolution ev : pk.evolutionsFrom) { + maxEvos = Math.max(maxEvos, numEvolutions(ev.to, depth + 1, maxInterested) + 1); + } + return maxEvos; + } + } + } + + private Set<Pokemon> relatedPokemon(Pokemon original) { + Set<Pokemon> results = new HashSet<Pokemon>(); + results.add(original); + Queue<Pokemon> toCheck = new LinkedList<Pokemon>(); + toCheck.add(original); + while (!toCheck.isEmpty()) { + Pokemon check = toCheck.poll(); + for (Evolution ev : check.evolutionsFrom) { + if (!results.contains(ev.to)) { + results.add(ev.to); + toCheck.add(ev.to); + } + } + for (Evolution ev : check.evolutionsTo) { + if (!results.contains(ev.from)) { + results.add(ev.from); + toCheck.add(ev.from); + } + } + } + return results; + } + + private Map<Type, List<Pokemon>> cachedReplacementLists; + private List<Pokemon> cachedAllList; + + private Pokemon pickReplacement(Pokemon current, boolean usePowerLevels, Type type, boolean noLegendaries, + boolean wonderGuardAllowed) { + List<Pokemon> pickFrom = cachedAllList; + if (type != null) { + if (!cachedReplacementLists.containsKey(type)) { + cachedReplacementLists.put(type, pokemonOfType(type, noLegendaries)); + } + pickFrom = cachedReplacementLists.get(type); + } + + if (usePowerLevels) { + // start with within 10% and add 5% either direction till we find + // something + int currentBST = current.bstForPowerLevels(); + int minTarget = currentBST - currentBST / 10; + int maxTarget = currentBST + currentBST / 10; + List<Pokemon> canPick = new ArrayList<Pokemon>(); + int expandRounds = 0; + while (canPick.isEmpty() || (canPick.size() < 3 && expandRounds < 2)) { + for (Pokemon pk : pickFrom) { + if (pk.bstForPowerLevels() >= minTarget + && pk.bstForPowerLevels() <= maxTarget + && (wonderGuardAllowed || (pk.ability1 != WONDER_GUARD_INDEX + && pk.ability2 != WONDER_GUARD_INDEX && pk.ability3 != WONDER_GUARD_INDEX))) { + canPick.add(pk); + } + } + minTarget -= currentBST / 20; + maxTarget += currentBST / 20; + expandRounds++; + } + return canPick.get(this.random.nextInt(canPick.size())); + } else { + if (wonderGuardAllowed) { + return pickFrom.get(this.random.nextInt(pickFrom.size())); + } else { + Pokemon pk = pickFrom.get(this.random.nextInt(pickFrom.size())); + while (pk.ability1 == WONDER_GUARD_INDEX || pk.ability2 == WONDER_GUARD_INDEX + || pk.ability3 == WONDER_GUARD_INDEX) { + pk = pickFrom.get(this.random.nextInt(pickFrom.size())); + } + return pk; + } + } + } + + private Pokemon pickWildPowerLvlReplacement(List<Pokemon> pokemonPool, Pokemon current, boolean banSamePokemon, + List<Pokemon> usedUp) { + // start with within 10% and add 5% either direction till we find + // something + int currentBST = current.bstForPowerLevels(); + int minTarget = currentBST - currentBST / 10; + int maxTarget = currentBST + currentBST / 10; + List<Pokemon> canPick = new ArrayList<Pokemon>(); + int expandRounds = 0; + while (canPick.isEmpty() || (canPick.size() < 3 && expandRounds < 3)) { + for (Pokemon pk : pokemonPool) { + if (pk.bstForPowerLevels() >= minTarget && pk.bstForPowerLevels() <= maxTarget + && (!banSamePokemon || pk != current) && (usedUp == null || !usedUp.contains(pk)) + && !canPick.contains(pk)) { + canPick.add(pk); + } + } + minTarget -= currentBST / 20; + maxTarget += currentBST / 20; + expandRounds++; + } + return canPick.get(this.random.nextInt(canPick.size())); + } + + /* Helper methods used by subclasses */ + + public void setLog(PrintStream logStream) { + this.logStream = logStream; + } + + protected void log(String log) { + if (logStream != null) { + logStream.println(log); + } + } + + protected void logBlankLine() { + if (logStream != null) { + logStream.println(); + } + } + + protected void logEvoChangeLevel(String pkFrom, String pkTo, int level) { + if (logStream != null) { + logStream.printf("Made %s evolve into %s at level %d", pkFrom, pkTo, level); + logStream.println(); + } + } + + protected void logEvoChangeLevelWithItem(String pkFrom, String pkTo, String itemName) { + if (logStream != null) { + logStream.printf("Made %s evolve into %s by leveling up holding %s", pkFrom, pkTo, itemName); + logStream.println(); + } + } + + protected void logEvoChangeStone(String pkFrom, String pkTo, String itemName) { + if (logStream != null) { + logStream.printf("Made %s evolve into %s using a %s", pkFrom, pkTo, itemName); + logStream.println(); + } + } + + protected void logEvoChangeLevelWithPkmn(String pkFrom, String pkTo, String otherRequired) { + if (logStream != null) { + logStream.printf("Made %s evolve into %s by leveling up with %s in the party", pkFrom, pkTo, otherRequired); + logStream.println(); + } + } + + /* Default Implementations */ + /* Used when a subclass doesn't override */ + + @Override + public boolean hasTimeBasedEncounters() { + // DEFAULT: no + return false; + } + + @Override + public boolean typeInGame(Type type) { + return type.isHackOnly == false; + } + + @Override + public String abilityName(int number) { + return ""; + } + + @Override + public Type randomType() { + Type t = Type.randomType(this.random); + while (!typeInGame(t)) { + t = Type.randomType(this.random); + } + return t; + } + + @Override + public boolean isYellow() { + return false; + } + + @Override + public boolean canChangeStarters() { + return true; + } + + @SuppressWarnings("unchecked") + @Override + public List<Pokemon> bannedForWildEncounters() { + return (List<Pokemon>) Collections.EMPTY_LIST; + } + + @SuppressWarnings("unchecked") + @Override + public List<Pokemon> bannedForStaticPokemon() { + return (List<Pokemon>) Collections.EMPTY_LIST; + } + + @Override + public int miscTweaksAvailable() { + // default: none + return 0; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + // default: do nothing + } + + @Override + public List<Integer> getGameBreakingMoves() { + // Sonicboom & drage + return Arrays.asList(49, 82); + } + + @SuppressWarnings("unchecked") + @Override + public List<Integer> getMovesBannedFromLevelup() { + return (List<Integer>) Collections.EMPTY_LIST; + } + + @Override + public boolean isROMHack() { + // override until detection implemented + return false; + } } diff --git a/src/com/dabomstew/pkrandom/romhandlers/Gen1RomHandler.java b/src/com/dabomstew/pkrandom/romhandlers/Gen1RomHandler.java index 3bfae56..39e7da1 100755 --- a/src/com/dabomstew/pkrandom/romhandlers/Gen1RomHandler.java +++ b/src/com/dabomstew/pkrandom/romhandlers/Gen1RomHandler.java @@ -63,2579 +63,2445 @@ import compressors.Gen1Decmp; public class Gen1RomHandler extends AbstractGBRomHandler { - public static class Factory extends RomHandler.Factory { - - @Override - public Gen1RomHandler create(Random random, PrintStream logStream) { - return new Gen1RomHandler(random, logStream); - } - - public boolean isLoadable(String filename) { - long fileLength = new File(filename).length(); - if (fileLength > 8 * 1024 * 1024) { - return false; - } - byte[] loaded = loadFilePartial(filename, 0x1000); - if (loaded.length == 0) { - // nope - return false; - } - return detectRomInner(loaded, (int) fileLength); - } - } - - public Gen1RomHandler(Random random) { - super(random, null); - } - - public Gen1RomHandler(Random random, PrintStream logStream) { - super(random, logStream); - } - - // Important RBY Data Structures - - private int[] pokeNumToRBYTable; - private int[] pokeRBYToNumTable; - private int[] moveNumToRomTable; - private int[] moveRomToNumTable; - private int pokedexCount; - - private Type idToType(int value) { - if (Gen1Constants.typeTable[value] != null) { - return Gen1Constants.typeTable[value]; - } - if (romEntry.extraTypeLookup.containsKey(value)) { - return romEntry.extraTypeLookup.get(value); - } - return null; - } - - private byte typeToByte(Type type) { - if (type == null) { - return 0x00; // revert to normal - } - if (romEntry.extraTypeReverse.containsKey(type)) { - return romEntry.extraTypeReverse.get(type).byteValue(); - } - return Gen1Constants.typeToByte(type); - } - - private static class RomEntry { - private String name; - private String romName; - private int version, nonJapanese; - private String extraTableFile; - private boolean isYellow; - private int crcInHeader = -1; - private Map<String, String> tweakFiles = new HashMap<String, String>(); - private List<TMTextEntry> tmTexts = new ArrayList<TMTextEntry>(); - private Map<String, Integer> entries = new HashMap<String, Integer>(); - private Map<String, int[]> arrayEntries = new HashMap<String, int[]>(); - private List<Integer> staticPokemonSingle = new ArrayList<Integer>(); - private List<GameCornerPokemon> staticPokemonGameCorner = new ArrayList<GameCornerPokemon>(); - private int[] ghostMarowakOffsets = new int[0]; - private Map<Integer, Type> extraTypeLookup = new HashMap<Integer, Type>(); - private Map<Type, Integer> extraTypeReverse = new HashMap<Type, Integer>(); - - private int getValue(String key) { - if (!entries.containsKey(key)) { - entries.put(key, 0); - } - return entries.get(key); - } - } - - private static List<RomEntry> roms; - - static { - loadROMInfo(); - } - - private static class GameCornerPokemon { - private int[] offsets; - - public String toString() { - return Arrays.toString(offsets); - } - } - - private static class TMTextEntry { - private int number; - private int offset; - private String template; - } - - private static void loadROMInfo() { - roms = new ArrayList<RomEntry>(); - RomEntry current = null; - try { - Scanner sc = new Scanner( - FileFunctions.openConfig("gen1_offsets.ini"), "UTF-8"); - while (sc.hasNextLine()) { - String q = sc.nextLine().trim(); - if (q.contains("//")) { - q = q.substring(0, q.indexOf("//")).trim(); - } - if (!q.isEmpty()) { - if (q.startsWith("[") && q.endsWith("]")) { - // New rom - current = new RomEntry(); - current.name = q.substring(1, q.length() - 1); - roms.add(current); - } else { - String[] r = q.split("=", 2); - if (r.length == 1) { - System.err.println("invalid entry " + q); - continue; - } - if (r[1].endsWith("\r\n")) { - r[1] = r[1].substring(0, r[1].length() - 2); - } - r[1] = r[1].trim(); - r[0] = r[0].trim(); - // Static Pokemon? - if (r[0].equals("StaticPokemonGameCorner[]")) { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - int[] offs = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - offs[c++] = parseRIInt(off); - } - GameCornerPokemon gc = new GameCornerPokemon(); - gc.offsets = offs; - current.staticPokemonGameCorner.add(gc); - } else { - int offs = parseRIInt(r[1]); - GameCornerPokemon gc = new GameCornerPokemon(); - gc.offsets = new int[] { offs }; - current.staticPokemonGameCorner.add(gc); - } - } else if (r[0].equals("StaticPokemonGhostMarowak")) { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - int[] offs = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - offs[c++] = parseRIInt(off); - } - current.ghostMarowakOffsets = offs; - } else { - } - } else if (r[0].equals("TMText[]")) { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] parts = r[1].substring(1, - r[1].length() - 1).split(",", 3); - TMTextEntry tte = new TMTextEntry(); - tte.number = parseRIInt(parts[0]); - tte.offset = parseRIInt(parts[1]); - tte.template = parts[2]; - current.tmTexts.add(tte); - } - } else if (r[0].equals("Game")) { - current.romName = r[1]; - } else if (r[0].equals("Version")) { - current.version = parseRIInt(r[1]); - } else if (r[0].equals("NonJapanese")) { - current.nonJapanese = parseRIInt(r[1]); - } else if (r[0].equals("Type")) { - if (r[1].equalsIgnoreCase("Yellow")) { - current.isYellow = true; - } else { - current.isYellow = false; - } - } else if (r[0].equals("ExtraTableFile")) { - current.extraTableFile = r[1]; - } else if (r[0].equals("CRCInHeader")) { - current.crcInHeader = parseRIInt(r[1]); - } else if (r[0].endsWith("Tweak")) { - current.tweakFiles.put(r[0], r[1]); - } else if (r[0].equals("ExtraTypes")) { - // remove the containers - r[1] = r[1].substring(1, r[1].length() - 1); - String[] parts = r[1].split(","); - for (String part : parts) { - String[] iParts = part.split("="); - int typeId = Integer.parseInt(iParts[0], 16); - String typeName = iParts[1].trim(); - Type theType = Type.valueOf(typeName); - current.extraTypeLookup.put(typeId, theType); - current.extraTypeReverse.put(theType, typeId); - } - } else if (r[0].equals("CopyFrom")) { - for (RomEntry otherEntry : roms) { - if (r[1].equalsIgnoreCase(otherEntry.name)) { - // copy from here - boolean cSP = (current - .getValue("CopyStaticPokemon") == 1); - boolean cTT = (current - .getValue("CopyTMText") == 1); - current.arrayEntries - .putAll(otherEntry.arrayEntries); - current.entries.putAll(otherEntry.entries); - if (cSP) { - current.staticPokemonSingle - .addAll(otherEntry.staticPokemonSingle); - current.staticPokemonGameCorner - .addAll(otherEntry.staticPokemonGameCorner); - current.ghostMarowakOffsets = otherEntry.ghostMarowakOffsets; - current.entries.put( - "StaticPokemonSupport", 1); - } else { - current.entries.put( - "StaticPokemonSupport", 0); - } - if (cTT) { - current.tmTexts - .addAll(otherEntry.tmTexts); - } - current.extraTableFile = otherEntry.extraTableFile; - } - } - } else { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - if (offsets.length == 1 - && offsets[0].trim().isEmpty()) { - current.arrayEntries.put(r[0], new int[0]); - } else { - int[] offs = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - offs[c++] = parseRIInt(off); - } - if (r[0].startsWith("StaticPokemon")) { - for (int off : offs) { - current.staticPokemonSingle - .add(off); - } - } else { - current.arrayEntries.put(r[0], offs); - } - } - - } else { - int offs = parseRIInt(r[1]); - current.entries.put(r[0], offs); - } - } - } - } - } - sc.close(); - } catch (FileNotFoundException e) { - } - - } - - 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; - } - } - - // This ROM's data - private Pokemon[] pokes; - private List<Pokemon> pokemonList; - private RomEntry romEntry; - private Move[] moves; - private String[] tb; - private Map<String, Byte> d; - private int longestTableToken; - private String[] itemNames; - private String[] mapNames; - private SubMap[] maps; - private boolean xAccNerfed; - - @Override - public boolean detectRom(byte[] rom) { - return detectRomInner(rom, rom.length); - } - - public static boolean detectRomInner(byte[] rom, int romSize) { - if (romSize < GBConstants.minRomSize - || romSize > GBConstants.maxRomSize) { - return false; // size check - } - return checkRomEntry(rom) != null; // so it's OK if it's a valid ROM - } - - @Override - public void loadedRom() { - romEntry = checkRomEntry(this.rom); - pokeNumToRBYTable = new int[256]; - pokeRBYToNumTable = new int[256]; - moveNumToRomTable = new int[256]; - moveRomToNumTable = new int[256]; - tb = new String[256]; - d = new HashMap<String, Byte>(); - maps = new SubMap[256]; - xAccNerfed = false; - clearTextTables(); - readTextTable("gameboy_jap"); - if (romEntry.extraTableFile != null - && romEntry.extraTableFile.equalsIgnoreCase("none") == false) { - readTextTable(romEntry.extraTableFile); - } - loadPokedexOrder(); - loadPokemonStats(); - pokemonList = Arrays.asList(pokes); - loadMoves(); - preloadMaps(); - loadItemNames(); - loadMapNames(); - } - - private void loadPokedexOrder() { - int pkmnCount = romEntry.getValue("InternalPokemonCount"); - int orderOffset = romEntry.getValue("PokedexOrder"); - pokedexCount = 0; - for (int i = 1; i <= pkmnCount; i++) { - int pokedexNum = rom[orderOffset + i - 1] & 0xFF; - pokeRBYToNumTable[i] = pokedexNum; - if (pokedexNum != 0 && pokeNumToRBYTable[pokedexNum] == 0) { - pokeNumToRBYTable[pokedexNum] = i; - } - pokedexCount = Math.max(pokedexCount, pokedexNum); - } - } - - private static RomEntry checkRomEntry(byte[] rom) { - int version = rom[GBConstants.versionOffset] & 0xFF; - int nonjap = rom[GBConstants.jpFlagOffset] & 0xFF; - // Check for specific CRC first - int crcInHeader = ((rom[GBConstants.crcOffset] & 0xFF) << 8) - | (rom[GBConstants.crcOffset + 1] & 0xFF); - for (RomEntry re : roms) { - if (romSig(rom, re.romName) && re.version == version - && re.nonJapanese == nonjap - && re.crcInHeader == crcInHeader) { - return re; - } - } - // Now check for non-specific-CRC entries - for (RomEntry re : roms) { - if (romSig(rom, re.romName) && re.version == version - && re.nonJapanese == nonjap && re.crcInHeader == -1) { - return re; - } - } - // Not found - return null; - } - - private void clearTextTables() { - tb = new String[256]; - d.clear(); - longestTableToken = 0; - } - - private void readTextTable(String name) { - try { - Scanner sc = new Scanner(FileFunctions.openConfig(name + ".tbl"), - "UTF-8"); - while (sc.hasNextLine()) { - String q = sc.nextLine(); - if (!q.trim().isEmpty()) { - String[] r = q.split("=", 2); - if (r[1].endsWith("\r\n")) { - r[1] = r[1].substring(0, r[1].length() - 2); - } - int hexcode = Integer.parseInt(r[0], 16); - if (tb[hexcode] != null) { - String oldMatch = tb[hexcode]; - tb[hexcode] = null; - if (d.get(oldMatch) == hexcode) { - d.remove(oldMatch); - } - } - tb[hexcode] = r[1]; - longestTableToken = Math.max(longestTableToken, - r[1].length()); - d.put(r[1], (byte) hexcode); - } - } - sc.close(); - } catch (FileNotFoundException e) { - } - - } - - @Override - public void savingRom() { - savePokemonStats(); - saveMoves(); - } - - private String[] readMoveNames() { - int moveCount = romEntry.getValue("MoveCount"); - int offset = romEntry.getValue("MoveNamesOffset"); - String[] moveNames = new String[moveCount + 1]; - for (int i = 1; i <= moveCount; i++) { - moveNames[i] = readVariableLengthString(offset); - offset += lengthOfStringAt(offset) + 1; - } - return moveNames; - } - - private void loadMoves() { - String[] moveNames = readMoveNames(); - int moveCount = romEntry.getValue("MoveCount"); - int movesOffset = romEntry.getValue("MoveDataOffset"); - // check real move count - int trueMoveCount = 0; - for (int i = 1; i <= moveCount; i++) { - // temp hack for Brown - if (rom[movesOffset + (i - 1) * 6] != 0 - && moveNames[i].equals("Nothing") == false) { - trueMoveCount++; - } - } - moves = new Move[trueMoveCount + 1]; - int trueMoveIndex = 0; - - for (int i = 1; i <= moveCount; i++) { - int anim = rom[movesOffset + (i - 1) * 6] & 0xFF; - // another temp hack for brown - if (anim > 0 && moveNames[i].equals("Nothing") == false) { - trueMoveIndex++; - moveNumToRomTable[trueMoveIndex] = i; - moveRomToNumTable[i] = trueMoveIndex; - moves[trueMoveIndex] = new Move(); - moves[trueMoveIndex].name = moveNames[i]; - moves[trueMoveIndex].internalId = i; - moves[trueMoveIndex].number = trueMoveIndex; - moves[trueMoveIndex].effectIndex = rom[movesOffset + (i - 1) - * 6 + 1] & 0xFF; - moves[trueMoveIndex].hitratio = ((rom[movesOffset + (i - 1) * 6 - + 4] & 0xFF) + 0) / 255.0 * 100; - moves[trueMoveIndex].power = rom[movesOffset + (i - 1) * 6 + 2] & 0xFF; - moves[trueMoveIndex].pp = rom[movesOffset + (i - 1) * 6 + 5] & 0xFF; - moves[trueMoveIndex].type = idToType(rom[movesOffset + (i - 1) - * 6 + 3] & 0xFF); - } - } - - } - - private void saveMoves() { - int movesOffset = romEntry.getValue("MoveDataOffset"); - for (Move m : moves) { - if (m != null) { - int i = m.internalId; - rom[movesOffset + (i - 1) * 6 + 1] = (byte) m.effectIndex; - rom[movesOffset + (i - 1) * 6 + 2] = (byte) m.power; - rom[movesOffset + (i - 1) * 6 + 3] = typeToByte(m.type); - int hitratio = (int) Math.round(m.hitratio * 2.55); - if (hitratio < 0) { - hitratio = 0; - } - if (hitratio > 255) { - hitratio = 255; - } - rom[movesOffset + (i - 1) * 6 + 4] = (byte) hitratio; - rom[movesOffset + (i - 1) * 6 + 5] = (byte) m.pp; - } - } - } - - public List<Move> getMoves() { - return Arrays.asList(moves); - } - - private void loadPokemonStats() { - pokes = new Pokemon[pokedexCount + 1]; - // Fetch our names - String[] pokeNames = readPokemonNames(); - // Get base stats - int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); - for (int i = 1; i <= pokedexCount; i++) { - pokes[i] = new Pokemon(); - pokes[i].number = i; - if (i != Gen1Constants.mewIndex || romEntry.isYellow) { - loadBasicPokeStats(pokes[i], pokeStatsOffset + (i - 1) - * Gen1Constants.baseStatsEntrySize); - } - // Name? - pokes[i].name = pokeNames[pokeNumToRBYTable[i]]; - } - - // Mew override for R/B - if (!romEntry.isYellow) { - loadBasicPokeStats(pokes[Gen1Constants.mewIndex], - romEntry.getValue("MewStatsOffset")); - } - - // Evolutions - populateEvolutions(); - - } - - private void savePokemonStats() { - // Write pokemon names - int offs = romEntry.getValue("PokemonNamesOffset"); - int nameLength = romEntry.getValue("PokemonNamesLength"); - for (int i = 1; i <= pokedexCount; i++) { - int rbynum = pokeNumToRBYTable[i]; - int stringOffset = offs + (rbynum - 1) * nameLength; - writeFixedLengthString(pokes[i].name, stringOffset, nameLength); - } - // Write pokemon stats - int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); - for (int i = 1; i <= pokedexCount; i++) { - if (i == Gen1Constants.mewIndex) { - continue; - } - saveBasicPokeStats(pokes[i], pokeStatsOffset + (i - 1) - * Gen1Constants.baseStatsEntrySize); - } - // Write MEW - int mewOffset = romEntry.isYellow ? pokeStatsOffset - + (Gen1Constants.mewIndex - 1) - * Gen1Constants.baseStatsEntrySize : romEntry - .getValue("MewStatsOffset"); - saveBasicPokeStats(pokes[Gen1Constants.mewIndex], mewOffset); - - // Write evolutions - writeEvosAndMovesLearnt(true, null); - } - - private void loadBasicPokeStats(Pokemon pkmn, int offset) { - pkmn.hp = rom[offset + Gen1Constants.bsHPOffset] & 0xFF; - pkmn.attack = rom[offset + Gen1Constants.bsAttackOffset] & 0xFF; - pkmn.defense = rom[offset + Gen1Constants.bsDefenseOffset] & 0xFF; - pkmn.speed = rom[offset + Gen1Constants.bsSpeedOffset] & 0xFF; - pkmn.special = rom[offset + Gen1Constants.bsSpecialOffset] & 0xFF; - pkmn.spatk = pkmn.special; - pkmn.spdef = pkmn.special; - // Type - pkmn.primaryType = idToType(rom[offset - + Gen1Constants.bsPrimaryTypeOffset] & 0xFF); - pkmn.secondaryType = idToType(rom[offset - + Gen1Constants.bsSecondaryTypeOffset] & 0xFF); - // Only one type? - if (pkmn.secondaryType == pkmn.primaryType) { - pkmn.secondaryType = null; - } - - pkmn.catchRate = rom[offset + Gen1Constants.bsCatchRateOffset] & 0xFF; - pkmn.growthCurve = ExpCurve.fromByte(rom[offset - + Gen1Constants.bsGrowthCurveOffset]); - pkmn.frontSpritePointer = readWord(offset - + Gen1Constants.bsFrontSpriteOffset); - - pkmn.guaranteedHeldItem = -1; - pkmn.commonHeldItem = -1; - pkmn.rareHeldItem = -1; - pkmn.darkGrassHeldItem = -1; - } - - private void saveBasicPokeStats(Pokemon pkmn, int offset) { - rom[offset + Gen1Constants.bsHPOffset] = (byte) pkmn.hp; - rom[offset + Gen1Constants.bsAttackOffset] = (byte) pkmn.attack; - rom[offset + Gen1Constants.bsDefenseOffset] = (byte) pkmn.defense; - rom[offset + Gen1Constants.bsSpeedOffset] = (byte) pkmn.speed; - rom[offset + Gen1Constants.bsSpecialOffset] = (byte) pkmn.special; - rom[offset + Gen1Constants.bsPrimaryTypeOffset] = typeToByte(pkmn.primaryType); - if (pkmn.secondaryType == null) { - rom[offset + Gen1Constants.bsSecondaryTypeOffset] = rom[offset - + Gen1Constants.bsPrimaryTypeOffset]; - } else { - rom[offset + Gen1Constants.bsSecondaryTypeOffset] = typeToByte(pkmn.secondaryType); - } - rom[offset + Gen1Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; - rom[offset + Gen1Constants.bsGrowthCurveOffset] = pkmn.growthCurve - .toByte(); - } - - private String[] readPokemonNames() { - int offs = romEntry.getValue("PokemonNamesOffset"); - int nameLength = romEntry.getValue("PokemonNamesLength"); - int pkmnCount = romEntry.getValue("InternalPokemonCount"); - String[] names = new String[pkmnCount + 1]; - for (int i = 1; i <= pkmnCount; i++) { - names[i] = readFixedLengthString(offs + (i - 1) * nameLength, - nameLength); - } - return names; - } - - private String readString(int offset, int maxLength) { - StringBuilder string = new StringBuilder(); - for (int c = 0; c < maxLength; c++) { - int currChar = rom[offset + c] & 0xFF; - if (tb[currChar] != null) { - string.append(tb[currChar]); - } else { - if (currChar == GBConstants.stringTerminator - || currChar == GBConstants.stringNull) { - break; - } else { - string.append("\\x" + String.format("%02X", currChar)); - } - } - } - return string.toString(); - } - - public byte[] translateString(String text) { - List<Byte> data = new ArrayList<Byte>(); - while (text.length() != 0) { - int i = Math.max(0, longestTableToken - text.length()); - if (text.charAt(0) == '\\' && text.charAt(1) == 'x') { - data.add((byte) Integer.parseInt(text.substring(2, 4), 16)); - text = text.substring(4); - } else { - while (!(d - .containsKey(text.substring(0, longestTableToken - i)) || (i == longestTableToken))) { - i++; - } - if (i == longestTableToken) { - text = text.substring(1); - } else { - data.add(d.get(text.substring(0, longestTableToken - i))); - text = text.substring(longestTableToken - i); - } - } - } - byte[] ret = new byte[data.size()]; - for (int i = 0; i < ret.length; i++) { - ret[i] = data.get(i); - } - return ret; - } - - private String readFixedLengthString(int offset, int length) { - return readString(offset, length); - } - - private void writeFixedLengthString(String str, int offset, int length) { - byte[] translated = translateString(str); - int len = Math.min(translated.length, length); - System.arraycopy(translated, 0, rom, offset, len); - while (len < length) { - rom[offset + len] = GBConstants.stringTerminator; - len++; - } - } - - private int makeGBPointer(int offset) { - if (offset < GBConstants.bankSize) { - return offset; - } else { - return (offset % GBConstants.bankSize) + GBConstants.bankSize; - } - } - - private int bankOf(int offset) { - return (offset / GBConstants.bankSize); - } - - private int calculateOffset(int bank, int pointer) { - if (pointer < GBConstants.bankSize) { - return pointer; - } else { - return (pointer % GBConstants.bankSize) + bank - * GBConstants.bankSize; - } - } - - public String readVariableLengthString(int offset) { - return readString(offset, Integer.MAX_VALUE); - } - - public byte[] traduire(String str) { - return translateString(str); - } - - public String readVariableLengthScriptString(int offset) { - return readString(offset, Integer.MAX_VALUE); - } - - private void writeFixedLengthScriptString(String str, int offset, int length) { - byte[] translated = translateString(str); - int len = Math.min(translated.length, length); - System.arraycopy(translated, 0, rom, offset, len); - while (len < length) { - rom[offset + len] = GBConstants.stringNull; - len++; - } - } - - private int lengthOfStringAt(int offset) { - int len = 0; - while (rom[offset + len] != GBConstants.stringTerminator - && rom[offset + len] != GBConstants.stringNull) { - len++; - } - return len; - } - - private static boolean romSig(byte[] rom, String sig) { - try { - int sigOffset = GBConstants.romSigOffset; - byte[] sigBytes = sig.getBytes("US-ASCII"); - for (int i = 0; i < sigBytes.length; i++) { - if (rom[sigOffset + i] != sigBytes[i]) { - return false; - } - } - return true; - } catch (UnsupportedEncodingException ex) { - return false; - } - - } - - @Override - public List<Pokemon> getStarters() { - // Get the starters - List<Pokemon> starters = new ArrayList<Pokemon>(); - starters.add(pokes[pokeRBYToNumTable[rom[romEntry.arrayEntries - .get("StarterOffsets1")[0]] & 0xFF]]); - starters.add(pokes[pokeRBYToNumTable[rom[romEntry.arrayEntries - .get("StarterOffsets2")[0]] & 0xFF]]); - if (!romEntry.isYellow) { - starters.add(pokes[pokeRBYToNumTable[rom[romEntry.arrayEntries - .get("StarterOffsets3")[0]] & 0xFF]]); - } - return starters; - } - - @Override - public boolean setStarters(List<Pokemon> newStarters) { - // Amount? - int starterAmount = 2; - if (!romEntry.isYellow) { - starterAmount = 3; - } - - // Basic checks - if (newStarters.size() != starterAmount) { - return false; - } - - // Patch starter bytes - for (int i = 0; i < starterAmount; i++) { - byte starter = (byte) pokeNumToRBYTable[newStarters.get(i).number]; - int[] offsets = romEntry.arrayEntries.get("StarterOffsets" - + (i + 1)); - for (int offset : offsets) { - rom[offset] = starter; - } - } - - // Special stuff for non-Yellow only - - if (!romEntry.isYellow) { - - // Starter text - if (romEntry.getValue("CanChangeStarterText") > 0) { - List<Integer> starterTextOffsets = RomFunctions.search(rom, - traduire("So! You want the")); - for (int i = 0; i < 3 && i < starterTextOffsets.size(); i++) { - writeFixedLengthScriptString("So! You want\\n" - + newStarters.get(i).name + "?\\e", - starterTextOffsets.get(i), - lengthOfStringAt(starterTextOffsets.get(i)) + 1); - } - } - - // Patch starter pokedex routine? - // Can only do in 1M roms because of size concerns - if (romEntry.getValue("PatchPokedex") > 0) { - - // Starter pokedex required RAM values - // RAM offset => value - // Allows for multiple starters in the same RAM byte - Map<Integer, Integer> onValues = new TreeMap<Integer, Integer>(); - for (int i = 0; i < 3; i++) { - int pkDexNum = newStarters.get(i).number; - int ramOffset = (pkDexNum - 1) / 8 - + romEntry.getValue("PokedexRamOffset"); - int bitShift = (pkDexNum - 1) % 8; - int writeValue = 1 << bitShift; - if (onValues.containsKey(ramOffset)) { - onValues.put(ramOffset, onValues.get(ramOffset) - | writeValue); - } else { - onValues.put(ramOffset, writeValue); - } - } - - // Starter pokedex offset/pointer calculations - - int pkDexOnOffset = romEntry.getValue("StarterPokedexOnOffset"); - int pkDexOffOffset = romEntry - .getValue("StarterPokedexOffOffset"); - - int sizeForOnRoutine = 5 * onValues.size() + 3; - int writeOnRoutineTo = romEntry - .getValue("StarterPokedexBranchOffset"); - int writeOffRoutineTo = writeOnRoutineTo + sizeForOnRoutine; - int offsetForOnRoutine = makeGBPointer(writeOnRoutineTo); - int offsetForOffRoutine = makeGBPointer(writeOffRoutineTo); - int retOnOffset = makeGBPointer(pkDexOnOffset + 5); - int retOffOffset = makeGBPointer(pkDexOffOffset + 4); - - // Starter pokedex - // Branch to our new routine(s) - - // Turn bytes on - rom[pkDexOnOffset] = GBConstants.gbZ80Jump; - writeWord(pkDexOnOffset + 1, offsetForOnRoutine); - rom[pkDexOnOffset + 3] = GBConstants.gbZ80Nop; - rom[pkDexOnOffset + 4] = GBConstants.gbZ80Nop; - - // Turn bytes off - rom[pkDexOffOffset] = GBConstants.gbZ80Jump; - writeWord(pkDexOffOffset + 1, offsetForOffRoutine); - rom[pkDexOffOffset + 3] = GBConstants.gbZ80Nop; - - // Put together the two scripts - rom[writeOffRoutineTo] = GBConstants.gbZ80XorA; - int turnOnOffset = writeOnRoutineTo; - int turnOffOffset = writeOffRoutineTo + 1; - for (int ramOffset : onValues.keySet()) { - int onValue = onValues.get(ramOffset); - // Turn on code - rom[turnOnOffset++] = GBConstants.gbZ80LdA; - rom[turnOnOffset++] = (byte) onValue; - // Turn on code for ram writing - rom[turnOnOffset++] = GBConstants.gbZ80LdAToFar; - rom[turnOnOffset++] = (byte) (ramOffset % 0x100); - rom[turnOnOffset++] = (byte) (ramOffset / 0x100); - // Turn off code for ram writing - rom[turnOffOffset++] = GBConstants.gbZ80LdAToFar; - rom[turnOffOffset++] = (byte) (ramOffset % 0x100); - rom[turnOffOffset++] = (byte) (ramOffset / 0x100); - } - // Jump back - rom[turnOnOffset++] = GBConstants.gbZ80Jump; - writeWord(turnOnOffset, retOnOffset); - - rom[turnOffOffset++] = GBConstants.gbZ80Jump; - writeWord(turnOffOffset, retOffOffset); - } - - } - - return true; - - } - - @Override - public List<Integer> getStarterHeldItems() { - // do nothing - return new ArrayList<Integer>(); - } - - @Override - public void setStarterHeldItems(List<Integer> items) { - // do nothing - } - - @Override - public void shufflePokemonStats() { - for (int i = 1; i <= pokedexCount; i++) { - pokes[i].shuffleStats(this.random); - } - } - - @Override - public List<EncounterSet> getEncounters(boolean useTimeOfDay) { - List<EncounterSet> encounters = new ArrayList<EncounterSet>(); - - Pokemon ghostMarowak = pokes[Gen1Constants.marowakIndex]; - if (canChangeStaticPokemon()) { - ghostMarowak = pokes[pokeRBYToNumTable[rom[romEntry.ghostMarowakOffsets[0]] & 0xFF]]; - } - - // grass & water - List<Integer> usedOffsets = new ArrayList<Integer>(); - int tableOffset = romEntry.getValue("WildPokemonTableOffset"); - int tableBank = bankOf(tableOffset); - int mapID = -1; - - while (readWord(tableOffset) != Gen1Constants.encounterTableEnd) { - mapID++; - int offset = calculateOffset(tableBank, readWord(tableOffset)); - int rootOffset = offset; - if (!usedOffsets.contains(offset)) { - usedOffsets.add(offset); - // grass and water are exactly the same - for (int a = 0; a < 2; a++) { - int rate = rom[offset++] & 0xFF; - if (rate > 0) { - // there is data here - EncounterSet thisSet = new EncounterSet(); - thisSet.rate = rate; - thisSet.offset = rootOffset; - thisSet.displayName = (a == 1 ? "Surfing" - : "Grass/Cave") + " on " + mapNames[mapID]; - if (mapID >= Gen1Constants.towerMapsStartIndex - && mapID <= Gen1Constants.towerMapsEndIndex) { - thisSet.bannedPokemon.add(ghostMarowak); - } - for (int slot = 0; slot < Gen1Constants.encounterTableSize; slot++) { - Encounter enc = new Encounter(); - enc.level = rom[offset] & 0xFF; - enc.pokemon = pokes[pokeRBYToNumTable[rom[offset + 1] & 0xFF]]; - thisSet.encounters.add(enc); - offset += 2; - } - encounters.add(thisSet); - } - } - } else { - for (EncounterSet es : encounters) { - if (es.offset == offset) { - es.displayName += ", " + mapNames[mapID]; - } - } - } - tableOffset += 2; - } - - // old rod - int oldRodOffset = romEntry.getValue("OldRodOffset"); - EncounterSet oldRodSet = new EncounterSet(); - oldRodSet.displayName = "Old Rod Fishing"; - Encounter oldRodEnc = new Encounter(); - oldRodEnc.level = rom[oldRodOffset + 2] & 0xFF; - oldRodEnc.pokemon = pokes[pokeRBYToNumTable[rom[oldRodOffset + 1] & 0xFF]]; - oldRodSet.encounters.add(oldRodEnc); - encounters.add(oldRodSet); - - // good rod - int goodRodOffset = romEntry.getValue("GoodRodOffset"); - EncounterSet goodRodSet = new EncounterSet(); - goodRodSet.displayName = "Good Rod Fishing"; - for (int grSlot = 0; grSlot < 2; grSlot++) { - Encounter enc = new Encounter(); - enc.level = rom[goodRodOffset + grSlot * 2] & 0xFF; - enc.pokemon = pokes[pokeRBYToNumTable[rom[goodRodOffset + grSlot - * 2 + 1] & 0xFF]]; - goodRodSet.encounters.add(enc); - } - encounters.add(goodRodSet); - - // super rod - if (romEntry.isYellow) { - int superRodOffset = romEntry.getValue("SuperRodTableOffset"); - while ((rom[superRodOffset] & 0xFF) != 0xFF) { - int map = rom[superRodOffset++] & 0xFF; - EncounterSet thisSet = new EncounterSet(); - thisSet.displayName = "Super Rod Fishing on " + mapNames[map]; - for (int encN = 0; encN < Gen1Constants.yellowSuperRodTableSize; encN++) { - Encounter enc = new Encounter(); - enc.level = rom[superRodOffset + 1] & 0xFF; - enc.pokemon = pokes[pokeRBYToNumTable[rom[superRodOffset] & 0xFF]]; - thisSet.encounters.add(enc); - superRodOffset += 2; - } - encounters.add(thisSet); - } - } else { - // red/blue - int superRodOffset = romEntry.getValue("SuperRodTableOffset"); - int superRodBank = bankOf(superRodOffset); - List<Integer> usedSROffsets = new ArrayList<Integer>(); - while ((rom[superRodOffset] & 0xFF) != 0xFF) { - int map = rom[superRodOffset++] & 0xFF; - int setOffset = calculateOffset(superRodBank, - readWord(superRodOffset)); - superRodOffset += 2; - if (!usedSROffsets.contains(setOffset)) { - usedSROffsets.add(setOffset); - EncounterSet thisSet = new EncounterSet(); - thisSet.displayName = "Super Rod Fishing on " - + mapNames[map]; - thisSet.offset = setOffset; - int pokesInSet = rom[setOffset++] & 0xFF; - for (int encN = 0; encN < pokesInSet; encN++) { - Encounter enc = new Encounter(); - enc.level = rom[setOffset] & 0xFF; - enc.pokemon = pokes[pokeRBYToNumTable[rom[setOffset + 1] & 0xFF]]; - thisSet.encounters.add(enc); - setOffset += 2; - } - encounters.add(thisSet); - } else { - for (EncounterSet es : encounters) { - if (es.offset == setOffset) { - es.displayName += ", " + mapNames[map]; - } - } - } - } - } - - return encounters; - } - - @Override - public void setEncounters(boolean useTimeOfDay, - List<EncounterSet> encounters) { - Iterator<EncounterSet> encsetit = encounters.iterator(); - - // grass & water - List<Integer> usedOffsets = new ArrayList<Integer>(); - int tableOffset = romEntry.getValue("WildPokemonTableOffset"); - int tableBank = bankOf(tableOffset); - - while (readWord(tableOffset) != Gen1Constants.encounterTableEnd) { - int offset = calculateOffset(tableBank, readWord(tableOffset)); - if (!usedOffsets.contains(offset)) { - usedOffsets.add(offset); - // grass and water are exactly the same - for (int a = 0; a < 2; a++) { - int rate = rom[offset++] & 0xFF; - if (rate > 0) { - // there is data here - EncounterSet thisSet = encsetit.next(); - for (int slot = 0; slot < Gen1Constants.encounterTableSize; slot++) { - Encounter enc = thisSet.encounters.get(slot); - rom[offset] = (byte) enc.level; - rom[offset + 1] = (byte) pokeNumToRBYTable[enc.pokemon.number]; - offset += 2; - } - } - } - } - tableOffset += 2; - } - - // old rod - int oldRodOffset = romEntry.getValue("OldRodOffset"); - EncounterSet oldRodSet = encsetit.next(); - Encounter oldRodEnc = oldRodSet.encounters.get(0); - rom[oldRodOffset + 2] = (byte) oldRodEnc.level; - rom[oldRodOffset + 1] = (byte) pokeNumToRBYTable[oldRodEnc.pokemon.number]; - - // good rod - int goodRodOffset = romEntry.getValue("GoodRodOffset"); - EncounterSet goodRodSet = encsetit.next(); - for (int grSlot = 0; grSlot < 2; grSlot++) { - Encounter enc = goodRodSet.encounters.get(grSlot); - rom[goodRodOffset + grSlot * 2] = (byte) enc.level; - rom[goodRodOffset + grSlot * 2 + 1] = (byte) pokeNumToRBYTable[enc.pokemon.number]; - } - - // super rod - if (romEntry.isYellow) { - int superRodOffset = romEntry.getValue("SuperRodTableOffset"); - while ((rom[superRodOffset] & 0xFF) != 0xFF) { - superRodOffset++; - EncounterSet thisSet = encsetit.next(); - for (int encN = 0; encN < Gen1Constants.yellowSuperRodTableSize; encN++) { - Encounter enc = thisSet.encounters.get(encN); - rom[superRodOffset + 1] = (byte) enc.level; - rom[superRodOffset] = (byte) pokeNumToRBYTable[enc.pokemon.number]; - superRodOffset += 2; - } - } - } else { - // red/blue - int superRodOffset = romEntry.getValue("SuperRodTableOffset"); - int superRodBank = bankOf(superRodOffset); - List<Integer> usedSROffsets = new ArrayList<Integer>(); - while ((rom[superRodOffset] & 0xFF) != 0xFF) { - superRodOffset++; - int setOffset = calculateOffset(superRodBank, - readWord(superRodOffset)); - superRodOffset += 2; - if (!usedSROffsets.contains(setOffset)) { - usedSROffsets.add(setOffset); - int pokesInSet = rom[setOffset++] & 0xFF; - EncounterSet thisSet = encsetit.next(); - for (int encN = 0; encN < pokesInSet; encN++) { - Encounter enc = thisSet.encounters.get(encN); - rom[setOffset] = (byte) enc.level; - rom[setOffset + 1] = (byte) pokeNumToRBYTable[enc.pokemon.number]; - setOffset += 2; - } - } - } - } - } - - @Override - public List<Pokemon> getPokemon() { - return pokemonList; - } - - public List<Trainer> getTrainers() { - int traineroffset = romEntry.getValue("TrainerDataTableOffset"); - int traineramount = Gen1Constants.trainerClassCount; - int[] trainerclasslimits = romEntry.arrayEntries - .get("TrainerDataClassCounts"); - - int[] pointers = new int[traineramount + 1]; - for (int i = 1; i <= traineramount; i++) { - int tPointer = readWord(traineroffset + (i - 1) * 2); - pointers[i] = calculateOffset(bankOf(traineroffset), tPointer); - } - - List<String> tcnames = getTrainerClassesForText(); - - List<Trainer> allTrainers = new ArrayList<Trainer>(); - for (int i = 1; i <= traineramount; i++) { - int offs = pointers[i]; - int limit = trainerclasslimits[i]; - String tcname = tcnames.get(i - 1); - for (int trnum = 0; trnum < limit; trnum++) { - Trainer tr = new Trainer(); - tr.offset = offs; - tr.trainerclass = i; - tr.fullDisplayName = tcname; - int dataType = rom[offs] & 0xFF; - if (dataType == 0xFF) { - // "Special" trainer - tr.poketype = 1; - offs++; - while (rom[offs] != 0x0) { - TrainerPokemon tpk = new TrainerPokemon(); - tpk.level = rom[offs] & 0xFF; - tpk.pokemon = pokes[pokeRBYToNumTable[rom[offs + 1] & 0xFF]]; - tr.pokemon.add(tpk); - offs += 2; - } - } else { - tr.poketype = 0; - int fixedLevel = dataType; - offs++; - while (rom[offs] != 0x0) { - TrainerPokemon tpk = new TrainerPokemon(); - tpk.level = fixedLevel; - tpk.pokemon = pokes[pokeRBYToNumTable[rom[offs] & 0xFF]]; - tr.pokemon.add(tpk); - offs++; - } - } - offs++; - allTrainers.add(tr); - } - } - Gen1Constants.tagTrainersUniversal(allTrainers); - if (romEntry.isYellow) { - Gen1Constants.tagTrainersYellow(allTrainers); - } else { - Gen1Constants.tagTrainersRB(allTrainers); - } - return allTrainers; - } - - public void setTrainers(List<Trainer> trainerData) { - int traineroffset = romEntry.getValue("TrainerDataTableOffset"); - int traineramount = Gen1Constants.trainerClassCount; - int[] trainerclasslimits = romEntry.arrayEntries - .get("TrainerDataClassCounts"); - - int[] pointers = new int[traineramount + 1]; - for (int i = 1; i <= traineramount; i++) { - int tPointer = readWord(traineroffset + (i - 1) * 2); - pointers[i] = calculateOffset(bankOf(traineroffset), tPointer); - } - - Iterator<Trainer> allTrainers = trainerData.iterator(); - for (int i = 1; i <= traineramount; i++) { - int offs = pointers[i]; - int limit = trainerclasslimits[i]; - for (int trnum = 0; trnum < limit; trnum++) { - Trainer tr = allTrainers.next(); - if (tr.trainerclass != i) { - System.err.println("Trainer mismatch: " + tr.name); - } - Iterator<TrainerPokemon> tPokes = tr.pokemon.iterator(); - // Write their pokemon based on poketype - if (tr.poketype == 0) { - // Regular trainer - int fixedLevel = tr.pokemon.get(0).level; - rom[offs] = (byte) fixedLevel; - offs++; - while (tPokes.hasNext()) { - TrainerPokemon tpk = tPokes.next(); - rom[offs] = (byte) pokeNumToRBYTable[tpk.pokemon.number]; - offs++; - } - } else { - // Special trainer - rom[offs] = (byte) 0xFF; - offs++; - while (tPokes.hasNext()) { - TrainerPokemon tpk = tPokes.next(); - rom[offs] = (byte) tpk.level; - rom[offs + 1] = (byte) pokeNumToRBYTable[tpk.pokemon.number]; - offs += 2; - } - } - rom[offs] = 0; - offs++; - } - } - - // Custom Moves AI Table - // Zero it out entirely. - rom[romEntry.getValue("ExtraTrainerMovesTableOffset")] = (byte) 0xFF; - - // Champion Rival overrides in Red/Blue - if (!isYellow()) { - // hacky relative offset (very likely to work but maybe not always) - int champRivalJump = romEntry.getValue("GymLeaderMovesTableOffset") - - Gen1Constants.champRivalOffsetFromGymLeaderMoves; - // nop out this jump - rom[champRivalJump] = GBConstants.gbZ80Nop; - rom[champRivalJump + 1] = GBConstants.gbZ80Nop; - } - - } - - @Override - public boolean isYellow() { - return romEntry.isYellow; - } - - @Override - public boolean typeInGame(Type type) { - if (type.isHackOnly == false - && (type != Type.DARK && type != Type.STEEL)) { - return true; - } - if (romEntry.extraTypeReverse.containsKey(type)) { - return true; - } - return false; - } - - private void fixTypeEffectiveness() { - // TODO rewrite to use table properly - int base = romEntry.getValue("TypeEffectivenessOffset"); - log("--Fixing Type Effectiveness--"); - // Change Poison SE to bug (should be neutral) - // to Ice NE to Fire (is currently neutral) - log("Replaced: Poison super effective vs Bug => Ice not very effective vs Fire"); - rom[base + 135] = typeToByte(Type.ICE); - rom[base + 136] = typeToByte(Type.FIRE); - rom[base + 137] = 5; // Not very effective - // Change BUG SE to Poison to Bug NE to Poison - log("Changed: Bug super effective vs Poison => Bug not very effective vs Poison"); - rom[base + 203] = 5; // Not very effective - // Change Ghost 0E to Psychic to Ghost SE to Psychic - log("Changed: Psychic immune to Ghost => Ghost super effective vs Psychic"); - rom[base + 227] = 20; // Super effective - logBlankLine(); - } - - @Override - public Map<Pokemon, List<MoveLearnt>> getMovesLearnt() { - Map<Pokemon, List<MoveLearnt>> movesets = new TreeMap<Pokemon, List<MoveLearnt>>(); - int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); - int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); - int pkmnCount = romEntry.getValue("InternalPokemonCount"); - for (int i = 1; i <= pkmnCount; i++) { - int pointer = readWord(pointersOffset + (i - 1) * 2); - int realPointer = calculateOffset(bankOf(pointersOffset), pointer); - if (pokeRBYToNumTable[i] != 0) { - Pokemon pkmn = pokes[pokeRBYToNumTable[i]]; - int statsOffset = 0; - if (pokeRBYToNumTable[i] == Gen1Constants.mewIndex - && !romEntry.isYellow) { - // Mewww - statsOffset = romEntry.getValue("MewStatsOffset"); - } else { - statsOffset = (pokeRBYToNumTable[i] - 1) * 0x1C - + pokeStatsOffset; - } - List<MoveLearnt> ourMoves = new ArrayList<MoveLearnt>(); - for (int delta = Gen1Constants.bsLevel1MovesOffset; delta < Gen1Constants.bsLevel1MovesOffset + 4; delta++) { - if (rom[statsOffset + delta] != 0x00) { - MoveLearnt learnt = new MoveLearnt(); - learnt.level = 1; - learnt.move = moveRomToNumTable[rom[statsOffset + delta] & 0xFF]; - ourMoves.add(learnt); - } - } - // Skip over evolution data - while (rom[realPointer] != 0) { - if (rom[realPointer] == 1) { - realPointer += 3; - } else if (rom[realPointer] == 2) { - realPointer += 4; - } else if (rom[realPointer] == 3) { - realPointer += 3; - } - } - realPointer++; - while (rom[realPointer] != 0) { - MoveLearnt learnt = new MoveLearnt(); - learnt.level = rom[realPointer] & 0xFF; - learnt.move = moveRomToNumTable[rom[realPointer + 1] & 0xFF]; - ourMoves.add(learnt); - realPointer += 2; - } - movesets.put(pkmn, ourMoves); - } - } - return movesets; - } - - @Override - public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets) { - // new method for moves learnt - writeEvosAndMovesLearnt(false, movesets); - } - - @Override - public List<Pokemon> getStaticPokemon() { - List<Pokemon> statics = new ArrayList<Pokemon>(); - if (romEntry.getValue("StaticPokemonSupport") > 0) { - for (int offset : romEntry.staticPokemonSingle) { - statics.add(pokes[pokeRBYToNumTable[rom[offset] & 0xFF]]); - } - for (GameCornerPokemon gcp : romEntry.staticPokemonGameCorner) { - statics.add(pokes[pokeRBYToNumTable[rom[gcp.offsets[0]] & 0xFF]]); - } - // Ghost Marowak - statics.add(pokes[pokeRBYToNumTable[rom[romEntry.ghostMarowakOffsets[0]] & 0xFF]]); - } - return statics; - } - - @Override - public boolean setStaticPokemon(List<Pokemon> staticPokemon) { - if (romEntry.getValue("StaticPokemonSupport") == 0) { - return false; - } - // Checks - int singleSize = romEntry.staticPokemonSingle.size(); - int gcSize = romEntry.staticPokemonGameCorner.size(); - if (staticPokemon.size() != singleSize + gcSize + 1) { - return false; - } - - // Singular entries - for (int i = 0; i < singleSize; i++) { - rom[romEntry.staticPokemonSingle.get(i)] = (byte) pokeNumToRBYTable[staticPokemon - .get(i).number]; - } - - // Game corner - for (int i = 0; i < gcSize; i++) { - byte pokeNum = (byte) pokeNumToRBYTable[staticPokemon.get(i - + singleSize).number]; - int[] offsets = romEntry.staticPokemonGameCorner.get(i).offsets; - for (int offset : offsets) { - rom[offset] = pokeNum; - } - } - - // Ghost Marowak - byte maroNum = (byte) pokeNumToRBYTable[staticPokemon.get(singleSize - + gcSize).number]; - for (int maroOffset : romEntry.ghostMarowakOffsets) { - rom[maroOffset] = maroNum; - } - - return true; - } - - @Override - public boolean canChangeStaticPokemon() { - return (romEntry.getValue("StaticPokemonSupport") > 0); - } - - @Override - public List<Integer> getTMMoves() { - List<Integer> tms = new ArrayList<Integer>(); - int offset = romEntry.getValue("TMMovesOffset"); - for (int i = 1; i <= Gen1Constants.tmCount; i++) { - tms.add(moveRomToNumTable[rom[offset + (i - 1)] & 0xFF]); - } - return tms; - } - - @Override - public List<Integer> getHMMoves() { - List<Integer> hms = new ArrayList<Integer>(); - int offset = romEntry.getValue("TMMovesOffset"); - for (int i = 1; i <= Gen1Constants.hmCount; i++) { - hms.add(moveRomToNumTable[rom[offset + Gen1Constants.tmCount - + (i - 1)] & 0xFF]); - } - return hms; - } - - @Override - public void setTMMoves(List<Integer> moveIndexes) { - int offset = romEntry.getValue("TMMovesOffset"); - for (int i = 1; i <= Gen1Constants.tmCount; i++) { - rom[offset + (i - 1)] = (byte) moveNumToRomTable[moveIndexes - .get(i - 1)]; - } - - // Gym Leader TM Moves (RB only) - if (!romEntry.isYellow) { - int[] tms = Gen1Constants.gymLeaderTMs; - int glMovesOffset = romEntry.getValue("GymLeaderMovesTableOffset"); - for (int i = 0; i < tms.length; i++) { - // Set the special move used by gym (i+1) to - // the move we just wrote to TM tms[i] - rom[glMovesOffset + i * 2] = (byte) moveNumToRomTable[moveIndexes - .get(tms[i] - 1)]; - } - } - - // TM Text - String[] moveNames = readMoveNames(); - for (TMTextEntry tte : romEntry.tmTexts) { - String moveName = moveNames[moveNumToRomTable[moveIndexes - .get(tte.number - 1)]]; - String text = tte.template.replace("%m", moveName); - writeFixedLengthScriptString(text, tte.offset, - lengthOfStringAt(tte.offset)); - } - } - - @Override - public int getTMCount() { - return Gen1Constants.tmCount; - } - - @Override - public int getHMCount() { - return Gen1Constants.hmCount; - } - - @Override - public Map<Pokemon, boolean[]> getTMHMCompatibility() { - Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); - int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); - for (int i = 1; i <= pokedexCount; i++) { - int baseStatsOffset = (romEntry.isYellow || i != Gen1Constants.mewIndex) ? (pokeStatsOffset + (i - 1) - * Gen1Constants.baseStatsEntrySize) - : romEntry.getValue("MewStatsOffset"); - Pokemon pkmn = pokes[i]; - boolean[] flags = new boolean[Gen1Constants.tmCount - + Gen1Constants.hmCount + 1]; - for (int j = 0; j < 7; j++) { - readByteIntoFlags(flags, j * 8 + 1, baseStatsOffset - + Gen1Constants.bsTMHMCompatOffset + j); - } - compat.put(pkmn, flags); - } - return compat; - } - - @Override - public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData) { - int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); - for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { - Pokemon pkmn = compatEntry.getKey(); - boolean[] flags = compatEntry.getValue(); - int baseStatsOffset = (romEntry.isYellow || pkmn.number != Gen1Constants.mewIndex) ? (pokeStatsOffset + (pkmn.number - 1) - * Gen1Constants.baseStatsEntrySize) - : romEntry.getValue("MewStatsOffset"); - for (int j = 0; j < 7; j++) { - rom[baseStatsOffset + Gen1Constants.bsTMHMCompatOffset + j] = getByteFromFlags( - flags, j * 8 + 1); - } - } - } - - @Override - public boolean hasMoveTutors() { - return false; - } - - @Override - public List<Integer> getMoveTutorMoves() { - return new ArrayList<Integer>(); - } - - @Override - public void setMoveTutorMoves(List<Integer> moves) { - // Do nothing - } - - @Override - public Map<Pokemon, boolean[]> getMoveTutorCompatibility() { - return new TreeMap<Pokemon, boolean[]>(); - } - - @Override - public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData) { - // Do nothing - } - - @Override - public String getROMName() { - return "Pokemon " + romEntry.name; - } - - @Override - public String getROMCode() { - return romEntry.romName + " (" + romEntry.version + "/" - + romEntry.nonJapanese + ")"; - } - - @Override - public String getSupportLevel() { - return (romEntry.getValue("StaticPokemonSupport") > 0) ? "Complete" - : "No Static Pokemon"; - } - - private void populateEvolutions() { - for (Pokemon pkmn : pokes) { - if (pkmn != null) { - pkmn.evolutionsFrom.clear(); - pkmn.evolutionsTo.clear(); - } - } - - int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); - - int pkmnCount = romEntry.getValue("InternalPokemonCount"); - for (int i = 1; i <= pkmnCount; i++) { - int pointer = readWord(pointersOffset + (i - 1) * 2); - int realPointer = calculateOffset(bankOf(pointersOffset), pointer); - if (pokeRBYToNumTable[i] != 0) { - int thisPoke = pokeRBYToNumTable[i]; - Pokemon pkmn = pokes[thisPoke]; - while (rom[realPointer] != 0) { - int method = rom[realPointer]; - EvolutionType type = EvolutionType.fromIndex(1, method); - int otherPoke = pokeRBYToNumTable[rom[realPointer + 2 - + (type == EvolutionType.STONE ? 1 : 0)] & 0xFF]; - int extraInfo = rom[realPointer + 1] & 0xFF; - Evolution evo = new Evolution(pkmn, pokes[otherPoke], true, - type, extraInfo); - if (!pkmn.evolutionsFrom.contains(evo)) { - pkmn.evolutionsFrom.add(evo); - pokes[otherPoke].evolutionsTo.add(evo); - } - realPointer += (type == EvolutionType.STONE ? 4 : 3); - } - // split evos don't carry stats - if (pkmn.evolutionsFrom.size() > 1) { - for (Evolution e : pkmn.evolutionsFrom) { - e.carryStats = false; - } - } - } - } - } - - @Override - public void removeTradeEvolutions(boolean changeMoveEvos) { - // Gen 1: only regular trade evos - // change them all to evolve at level 37 - log("--Removing Trade Evolutions--"); - for (Pokemon pkmn : pokes) { - if (pkmn != null) { - for (Evolution evo : pkmn.evolutionsFrom) { - if (evo.type == EvolutionType.TRADE) { - // change - evo.type = EvolutionType.LEVEL; - evo.extraInfo = 37; - logEvoChangeLevel(evo.from.name, evo.to.name, 37); - } - } - } - } - logBlankLine(); - } - - private List<String> getTrainerClassesForText() { - int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); - List<String> tcNames = new ArrayList<String>(); - int offset = offsets[offsets.length - 1]; - for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { - String name = readVariableLengthString(offset); - offset += (internalStringLength(name) + 1); - tcNames.add(name); - } - return tcNames; - } - - @Override - public List<String> getTrainerNames() { - int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); - List<String> trainerNames = new ArrayList<String>(); - int offset = offsets[offsets.length - 1]; - for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { - String name = readVariableLengthString(offset); - offset += (internalStringLength(name) + 1); - if (Gen1Constants.singularTrainers.contains(j)) { - trainerNames.add(name); - } - } - return trainerNames; - } - - @Override - public void setTrainerNames(List<String> trainerNames) { - if (romEntry.getValue("CanChangeTrainerText") > 0) { - int[] offsets = romEntry.arrayEntries - .get("TrainerClassNamesOffsets"); - Iterator<String> trainerNamesI = trainerNames.iterator(); - int offset = offsets[offsets.length - 1]; - for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { - String name = readVariableLengthString(offset); - if (Gen1Constants.singularTrainers.contains(j)) { - String newName = trainerNamesI.next(); - writeFixedLengthString(newName, offset, - internalStringLength(name) + 1); - } - offset += (internalStringLength(name) + 1); - } - } - } - - @Override - public TrainerNameMode trainerNameMode() { - return TrainerNameMode.SAME_LENGTH; - } - - @Override - public List<Integer> getTCNameLengthsByTrainer() { - // not needed - return new ArrayList<Integer>(); - } - - @Override - public List<String> getTrainerClassNames() { - int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); - List<String> trainerClassNames = new ArrayList<String>(); - if (offsets.length == 2) { - for (int i = 0; i < offsets.length; i++) { - int offset = offsets[i]; - for (int j = 0; j < Gen1Constants.tclassesCounts[i]; j++) { - String name = readVariableLengthString(offset); - offset += (internalStringLength(name) + 1); - if (i == 0 || !Gen1Constants.singularTrainers.contains(j)) { - trainerClassNames.add(name); - } - } - } - } else { - int offset = offsets[0]; - for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { - String name = readVariableLengthString(offset); - offset += (internalStringLength(name) + 1); - if (!Gen1Constants.singularTrainers.contains(j)) { - trainerClassNames.add(name); - } - } - } - return trainerClassNames; - } - - @Override - public void setTrainerClassNames(List<String> trainerClassNames) { - if (romEntry.getValue("CanChangeTrainerText") > 0) { - int[] offsets = romEntry.arrayEntries - .get("TrainerClassNamesOffsets"); - Iterator<String> tcNamesIter = trainerClassNames.iterator(); - if (offsets.length == 2) { - for (int i = 0; i < offsets.length; i++) { - int offset = offsets[i]; - for (int j = 0; j < Gen1Constants.tclassesCounts[i]; j++) { - String name = readVariableLengthString(offset); - if (i == 0 - || !Gen1Constants.singularTrainers.contains(j)) { - String newName = tcNamesIter.next(); - writeFixedLengthString(newName, offset, - internalStringLength(name) + 1); - } - offset += (internalStringLength(name) + 1); - } - } - } else { - int offset = offsets[0]; - for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { - String name = readVariableLengthString(offset); - if (!Gen1Constants.singularTrainers.contains(j)) { - String newName = tcNamesIter.next(); - writeFixedLengthString(newName, offset, - internalStringLength(name) + 1); - } - offset += (internalStringLength(name) + 1); - } - } - } - - } - - @Override - public boolean fixedTrainerClassNamesLength() { - return true; - } - - @Override - public String getDefaultExtension() { - if (((rom[GBConstants.isGBCOffset] & 0xFF) & 0x80) > 0) { - return "gbc"; - } - return "sgb"; - } - - @Override - public int abilitiesPerPokemon() { - return 0; - } - - @Override - public int highestAbilityIndex() { - return 0; - } - - @Override - public int internalStringLength(String string) { - return translateString(string).length; - } - - @Override - public int miscTweaksAvailable() { - int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); - available |= MiscTweak.UPDATE_TYPE_EFFECTIVENESS.getValue(); - - if (romEntry.tweakFiles.get("BWXPTweak") != null) { - available |= MiscTweak.BW_EXP_PATCH.getValue(); - } - if (romEntry.tweakFiles.get("XAccNerfTweak") != null) { - available |= MiscTweak.NERF_X_ACCURACY.getValue(); - } - if (romEntry.tweakFiles.get("CritRateTweak") != null) { - available |= MiscTweak.FIX_CRIT_RATE.getValue(); - } - if (romEntry.getValue("TextDelayFunctionOffset") != 0) { - available |= MiscTweak.FASTEST_TEXT.getValue(); - } - if (romEntry.getValue("PCPotionOffset") != 0) { - available |= MiscTweak.RANDOMIZE_PC_POTION.getValue(); - } - if (romEntry.getValue("PikachuEvoJumpOffset") != 0) { - available |= MiscTweak.ALLOW_PIKACHU_EVOLUTION.getValue(); - } - if (romEntry.getValue("CatchingTutorialMonOffset") != 0) { - available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); - } - return available; - } - - @Override - public void applyMiscTweak(MiscTweak tweak) { - if (tweak == MiscTweak.BW_EXP_PATCH) { - applyBWEXPPatch(); - } else if (tweak == MiscTweak.NERF_X_ACCURACY) { - applyXAccNerfPatch(); - } else if (tweak == MiscTweak.FIX_CRIT_RATE) { - applyCritRatePatch(); - } else if (tweak == MiscTweak.FASTEST_TEXT) { - applyFastestTextPatch(); - } else if (tweak == MiscTweak.RANDOMIZE_PC_POTION) { - randomizePCPotion(); - } else if (tweak == MiscTweak.ALLOW_PIKACHU_EVOLUTION) { - applyPikachuEvoPatch(); - } else if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { - applyCamelCaseNames(); - } else if (tweak == MiscTweak.UPDATE_TYPE_EFFECTIVENESS) { - fixTypeEffectiveness(); - } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { - randomizeCatchingTutorial(); - } - } - - private void applyBWEXPPatch() { - genericIPSPatch("BWXPTweak"); - } - - private void applyXAccNerfPatch() { - xAccNerfed = genericIPSPatch("XAccNerfTweak"); - } - - private void applyCritRatePatch() { - genericIPSPatch("CritRateTweak"); - } - - private void applyFastestTextPatch() { - if (romEntry.getValue("TextDelayFunctionOffset") != 0) { - rom[romEntry.getValue("TextDelayFunctionOffset")] = GBConstants.gbZ80Ret; - } - } - - private void randomizePCPotion() { - if (romEntry.getValue("PCPotionOffset") != 0) { - rom[romEntry.getValue("PCPotionOffset")] = (byte) this - .getNonBadItems().randomNonTM(this.random); - } - } - - private void applyPikachuEvoPatch() { - if (romEntry.getValue("PikachuEvoJumpOffset") != 0) { - rom[romEntry.getValue("PikachuEvoJumpOffset")] = GBConstants.gbZ80JumpRelative; - } - } - - private void randomizeCatchingTutorial() { - if (romEntry.getValue("CatchingTutorialMonOffset") != 0) { - rom[romEntry.getValue("CatchingTutorialMonOffset")] = (byte) pokeNumToRBYTable[this - .randomPokemon().number]; - } - } - - private boolean genericIPSPatch(String ctName) { - String patchName = romEntry.tweakFiles.get(ctName); - if (patchName == null) { - return false; - } - - try { - FileFunctions.applyPatch(rom, patchName); - return true; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public List<Integer> getGameBreakingMoves() { - // Sonicboom & drage & OHKO moves - // 160 add spore - // also remove OHKO if xacc nerfed - if (xAccNerfed) { - return Gen1Constants.bannedMovesWithXAccBanned; - } else { - return Gen1Constants.bannedMovesWithoutXAccBanned; - } - } - - @Override - public List<Integer> getFieldMoves() { - // cut, fly, surf, strength, flash, - // dig, teleport (NOT softboiled) - return Gen1Constants.fieldMoves; - } - - @Override - public List<Integer> getEarlyRequiredHMMoves() { - // just cut - return Gen1Constants.earlyRequiredHMs; - } - - @Override - public void applySignature() { - // First off, intro Pokemon - // 160 add yellow intro random - int introPokemon = pokeNumToRBYTable[this.randomPokemon().number]; - rom[romEntry.getValue("IntroPokemonOffset")] = (byte) introPokemon; - rom[romEntry.getValue("IntroCryOffset")] = (byte) introPokemon; - - } - - @Override - public ItemList getAllowedItems() { - return Gen1Constants.allowedItems; - } - - @Override - public ItemList getNonBadItems() { - // Gen 1 has no bad items Kappa - return Gen1Constants.allowedItems; - } - - private void loadItemNames() { - itemNames = new String[256]; - itemNames[0] = "glitch"; - // trying to emulate pretty much what the game does here - // normal items - int origOffset = romEntry.getValue("ItemNamesOffset"); - int itemNameOffset = origOffset; - for (int index = 1; index <= 0x100; index++) { - if (itemNameOffset / GBConstants.bankSize > origOffset - / GBConstants.bankSize) { - // the game would continue making its merry way into VRAM here, - // but we don't have VRAM to simulate. - // just give up. - break; - } - int startOfText = itemNameOffset; - while ((rom[itemNameOffset] & 0xFF) != GBConstants.stringTerminator) { - itemNameOffset++; - } - itemNameOffset++; - itemNames[index % 256] = readFixedLengthString(startOfText, 20); - } - // hms override - for (int index = Gen1Constants.hmsStartIndex; index < Gen1Constants.tmsStartIndex; index++) { - itemNames[index] = String.format("HM%02d", index - - Gen1Constants.hmsStartIndex + 1); - } - // tms override - for (int index = Gen1Constants.tmsStartIndex; index < 0x100; index++) { - itemNames[index] = String.format("TM%02d", index - - Gen1Constants.tmsStartIndex + 1); - } - } - - @Override - public String[] getItemNames() { - return itemNames; - } - - private static class SubMap { - private int id; - private int addr; - private int bank; - private MapHeader header; - private Connection[] cons; - private int n_cons; - private int obj_addr; - private List<Integer> itemOffsets; - } - - private static class MapHeader { - private int tileset_id; // u8 - private int map_h, map_w; // u8 - private int map_ptr, text_ptr, script_ptr; // u16 - private int connect_byte; // u8 - // 10 bytes - } - - private static class Connection { - private int index; // u8 - private int connected_map; // u16 - private int current_map; // u16 - private int bigness; // u8 - private int map_width; // u8 - private int y_align; // u8 - private int x_align; // u8 - private int window; // u16 - // 11 bytes - } - - private void preloadMaps() { - int mapBanks = romEntry.getValue("MapBanks"); - int mapAddresses = romEntry.getValue("MapAddresses"); - - preloadMap(mapBanks, mapAddresses, 0); - } - - private void preloadMap(int mapBanks, int mapAddresses, int mapID) { - - if (maps[mapID] != null || mapID == 0xED || mapID == 0xFF) { - return; - } - - SubMap map = new SubMap(); - maps[mapID] = map; - - map.id = mapID; - map.addr = calculateOffset(rom[mapBanks + mapID] & 0xFF, - readWord(mapAddresses + mapID * 2)); - map.bank = bankOf(map.addr); - - map.header = new MapHeader(); - map.header.tileset_id = rom[map.addr] & 0xFF; - map.header.map_h = rom[map.addr + 1] & 0xFF; - map.header.map_w = rom[map.addr + 2] & 0xFF; - map.header.map_ptr = calculateOffset(map.bank, readWord(map.addr + 3)); - map.header.text_ptr = calculateOffset(map.bank, readWord(map.addr + 5)); - map.header.script_ptr = calculateOffset(map.bank, - readWord(map.addr + 7)); - map.header.connect_byte = rom[map.addr + 9] & 0xFF; - - int cb = map.header.connect_byte; - map.n_cons = ((cb & 8) >> 3) + ((cb & 4) >> 2) + ((cb & 2) >> 1) - + (cb & 1); - - int cons_offset = map.addr + 10; - - map.cons = new Connection[map.n_cons]; - for (int i = 0; i < map.n_cons; i++) { - int tcon_offs = cons_offset + i * 11; - Connection con = new Connection(); - con.index = rom[tcon_offs] & 0xFF; - con.connected_map = readWord(tcon_offs + 1); - con.current_map = readWord(tcon_offs + 3); - con.bigness = rom[tcon_offs + 5] & 0xFF; - con.map_width = rom[tcon_offs + 6] & 0xFF; - con.y_align = rom[tcon_offs + 7] & 0xFF; - con.x_align = rom[tcon_offs + 8] & 0xFF; - con.window = readWord(tcon_offs + 9); - map.cons[i] = con; - preloadMap(mapBanks, mapAddresses, con.index); - } - map.obj_addr = calculateOffset(map.bank, readWord(cons_offset - + map.n_cons * 11)); - - // Read objects - // +0 is the border tile (ignore) - // +1 is warp count - - int n_warps = rom[map.obj_addr + 1] & 0xFF; - int offs = map.obj_addr + 2; - for (int i = 0; i < n_warps; i++) { - // track this warp - int to_map = rom[offs + 3] & 0xFF; - preloadMap(mapBanks, mapAddresses, to_map); - offs += 4; - } - - // Now we're pointing to sign count - int n_signs = rom[offs++] & 0xFF; - offs += n_signs * 3; - - // Finally, entities, which contain the items - map.itemOffsets = new ArrayList<Integer>(); - int n_entities = rom[offs++] & 0xFF; - for (int i = 0; i < n_entities; i++) { - // Read text ID - int tid = rom[offs + 5] & 0xFF; - if ((tid & (1 << 6)) > 0) { - // trainer - offs += 8; - } else if ((tid & (1 << 7)) > 0 && (rom[offs + 6] != 0x00)) { - // item - map.itemOffsets.add(offs + 6); - offs += 7; - } else { - // generic - offs += 6; - } - } - } - - private void loadMapNames() { - mapNames = new String[256]; - int mapNameTableOffset = romEntry.getValue("MapNameTableOffset"); - int mapNameBank = bankOf(mapNameTableOffset); - // external names - List<Integer> usedExternal = new ArrayList<Integer>(); - for (int i = 0; i < 0x25; i++) { - int externalOffset = calculateOffset(mapNameBank, - readWord(mapNameTableOffset + 1)); - usedExternal.add(externalOffset); - mapNames[i] = readVariableLengthString(externalOffset); - mapNameTableOffset += 3; - } - - // internal names - int lastMaxMap = 0x25; - Map<Integer, Integer> previousMapCounts = new HashMap<Integer, Integer>(); - while ((rom[mapNameTableOffset] & 0xFF) != 0xFF) { - int maxMap = rom[mapNameTableOffset] & 0xFF; - int nameOffset = calculateOffset(mapNameBank, - readWord(mapNameTableOffset + 2)); - String actualName = readVariableLengthString(nameOffset).trim(); - if (usedExternal.contains(nameOffset)) { - for (int i = lastMaxMap; i < maxMap; i++) { - if (maps[i] != null) { - mapNames[i] = actualName + " (Building)"; - } - } - } else { - int mapCount = 0; - if (previousMapCounts.containsKey(nameOffset)) { - mapCount = previousMapCounts.get(nameOffset); - } - for (int i = lastMaxMap; i < maxMap; i++) { - if (maps[i] != null) { - mapCount++; - mapNames[i] = actualName + " (" + mapCount + ")"; - } - } - previousMapCounts.put(nameOffset, mapCount); - } - lastMaxMap = maxMap; - mapNameTableOffset += 4; - } - } - - private List<Integer> getItemOffsets() { - - List<Integer> itemOffs = new ArrayList<Integer>(); - - for (int i = 0; i < maps.length; i++) { - if (maps[i] != null) { - itemOffs.addAll(maps[i].itemOffsets); - } - } - - int hiRoutine = romEntry.getValue("HiddenItemRoutine"); - int spclTable = romEntry.getValue("SpecialMapPointerTable"); - int spclBank = bankOf(spclTable); - - if (!isYellow()) { - - int spclList = romEntry.getValue("SpecialMapList"); - - int lOffs = spclList; - int idx = 0; - - while ((rom[lOffs] & 0xFF) != 0xFF) { - - int spclOffset = calculateOffset(spclBank, readWord(spclTable - + idx)); - - while ((rom[spclOffset] & 0xFF) != 0xFF) { - if (calculateOffset(rom[spclOffset + 3] & 0xFF, - readWord(spclOffset + 4)) == hiRoutine) { - itemOffs.add(spclOffset + 2); - } - spclOffset += 6; - } - lOffs++; - idx += 2; - } - } else { - - int lOffs = spclTable; - - while ((rom[lOffs] & 0xFF) != 0xFF) { - - int spclOffset = calculateOffset(spclBank, readWord(lOffs + 1)); - - while ((rom[spclOffset] & 0xFF) != 0xFF) { - if (calculateOffset(rom[spclOffset + 3] & 0xFF, - readWord(spclOffset + 4)) == hiRoutine) { - itemOffs.add(spclOffset + 2); - } - spclOffset += 6; - } - lOffs += 3; - } - } - - return itemOffs; - } - - @Override - public List<Integer> getRequiredFieldTMs() { - return Gen1Constants.requiredFieldTMs; - } - - @Override - public List<Integer> getCurrentFieldTMs() { - List<Integer> itemOffsets = getItemOffsets(); - List<Integer> fieldTMs = new ArrayList<Integer>(); - - for (int offset : itemOffsets) { - int itemHere = rom[offset] & 0xFF; - if (Gen1Constants.allowedItems.isTM(itemHere)) { - fieldTMs.add(itemHere - Gen1Constants.tmsStartIndex + 1); // TM - // offset - } - } - return fieldTMs; - } - - @Override - public void setFieldTMs(List<Integer> fieldTMs) { - List<Integer> itemOffsets = getItemOffsets(); - Iterator<Integer> iterTMs = fieldTMs.iterator(); - - for (int offset : itemOffsets) { - int itemHere = rom[offset] & 0xFF; - if (Gen1Constants.allowedItems.isTM(itemHere)) { - // Replace this with a TM from the list - rom[offset] = (byte) (iterTMs.next() - + Gen1Constants.tmsStartIndex - 1); - } - } - } - - @Override - public List<Integer> getRegularFieldItems() { - List<Integer> itemOffsets = getItemOffsets(); - List<Integer> fieldItems = new ArrayList<Integer>(); - - for (int offset : itemOffsets) { - int itemHere = rom[offset] & 0xFF; - if (Gen1Constants.allowedItems.isAllowed(itemHere) - && !(Gen1Constants.allowedItems.isTM(itemHere))) { - fieldItems.add(itemHere); - } - } - return fieldItems; - } - - @Override - public void setRegularFieldItems(List<Integer> items) { - List<Integer> itemOffsets = getItemOffsets(); - Iterator<Integer> iterItems = items.iterator(); - - for (int offset : itemOffsets) { - int itemHere = rom[offset] & 0xFF; - if (Gen1Constants.allowedItems.isAllowed(itemHere) - && !(Gen1Constants.allowedItems.isTM(itemHere))) { - // Replace it - rom[offset] = (byte) (iterItems.next().intValue()); - } - } - - } - - @Override - public List<IngameTrade> getIngameTrades() { - List<IngameTrade> trades = new ArrayList<IngameTrade>(); - - // info - int tableOffset = romEntry.getValue("TradeTableOffset"); - int tableSize = romEntry.getValue("TradeTableSize"); - int nicknameLength = romEntry.getValue("TradeNameLength"); - int[] unused = romEntry.arrayEntries.get("TradesUnused"); - int unusedOffset = 0; - int entryLength = nicknameLength + 3; - - for (int entry = 0; entry < tableSize; entry++) { - if (unusedOffset < unused.length && unused[unusedOffset] == entry) { - unusedOffset++; - continue; - } - IngameTrade trade = new IngameTrade(); - int entryOffset = tableOffset + entry * entryLength; - trade.requestedPokemon = pokes[pokeRBYToNumTable[rom[entryOffset] & 0xFF]]; - trade.givenPokemon = pokes[pokeRBYToNumTable[rom[entryOffset + 1] & 0xFF]]; - trade.nickname = readString(entryOffset + 3, nicknameLength); - trades.add(trade); - } - - return trades; - } - - @Override - public void setIngameTrades(List<IngameTrade> trades) { - - // info - int tableOffset = romEntry.getValue("TradeTableOffset"); - int tableSize = romEntry.getValue("TradeTableSize"); - int nicknameLength = romEntry.getValue("TradeNameLength"); - int[] unused = romEntry.arrayEntries.get("TradesUnused"); - int unusedOffset = 0; - int entryLength = nicknameLength + 3; - int tradeOffset = 0; - - for (int entry = 0; entry < tableSize; entry++) { - if (unusedOffset < unused.length && unused[unusedOffset] == entry) { - unusedOffset++; - continue; - } - IngameTrade trade = trades.get(tradeOffset++); - int entryOffset = tableOffset + entry * entryLength; - rom[entryOffset] = (byte) pokeNumToRBYTable[trade.requestedPokemon.number]; - rom[entryOffset + 1] = (byte) pokeNumToRBYTable[trade.givenPokemon.number]; - if (romEntry.getValue("CanChangeTrainerText") > 0) { - writeFixedLengthString(trade.nickname, entryOffset + 3, - nicknameLength); - } - } - } - - @Override - public boolean hasDVs() { - return true; - } - - @Override - public int generationOfPokemon() { - return 1; - } - - @Override - public void removeEvosForPokemonPool() { - // gen1 doesn't have this functionality anyway - } - - @Override - public boolean supportsFourStartingMoves() { - return true; - } - - private void writeEvosAndMovesLearnt(boolean writeEvos, - Map<Pokemon, List<MoveLearnt>> movesets) { - // we assume a few things here: - // 1) evos & moves learnt are stored directly after their pointer table - // 2) PokemonMovesetsExtraSpaceOffset is in the same bank, and - // points to the start of the free space at the end of the bank - // (if set to 0, disabled from being used) - // 3) PokemonMovesetsDataSize is from the start of actual data to - // the start of engine/battle/e_2.asm in pokered (aka code we can't - // overwrite) - // it appears that in yellow, this code is moved - // so we can write the evos/movesets in one continuous block - // until the end of the bank. - // so for yellow, extraspace is disabled. - // specify null to either argument to copy old values - int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); - int movesEvosStart = romEntry.getValue("PokemonMovesetsTableOffset"); - int movesEvosBank = bankOf(movesEvosStart); - int pkmnCount = romEntry.getValue("InternalPokemonCount"); - byte[] pointerTable = new byte[pkmnCount * 2]; - int mainDataBlockSize = romEntry.getValue("PokemonMovesetsDataSize"); - int mainDataBlockOffset = movesEvosStart + pointerTable.length; - byte[] mainDataBlock = new byte[mainDataBlockSize]; - int offsetInMainData = 0; - int extraSpaceOffset = romEntry - .getValue("PokemonMovesetsExtraSpaceOffset"); - int extraSpaceBank = bankOf(extraSpaceOffset); - boolean extraSpaceEnabled = false; - byte[] extraDataBlock = null; - int offsetInExtraData = 0; - int extraSpaceSize = 0; - if (movesEvosBank == extraSpaceBank && extraSpaceOffset != 0) { - extraSpaceEnabled = true; - int startOfNextBank = ((extraSpaceOffset / GBConstants.bankSize) + 1) - * GBConstants.bankSize; - extraSpaceSize = startOfNextBank - extraSpaceOffset; - extraDataBlock = new byte[extraSpaceSize]; - } - int nullEntryPointer = -1; - - for (int i = 1; i <= pkmnCount; i++) { - byte[] writeData = null; - int oldDataOffset = calculateOffset(movesEvosBank, - readWord(movesEvosStart + (i - 1) * 2)); - boolean setNullEntryPointerHere = false; - if (pokeRBYToNumTable[i] == 0) { - // null entry - if (nullEntryPointer == -1) { - // make the null entry - writeData = new byte[] { 0, 0 }; - setNullEntryPointerHere = true; - } else { - writeWord(pointerTable, (i - 1) * 2, nullEntryPointer); - } - } else { - int pokeNum = pokeRBYToNumTable[i]; - Pokemon pkmn = pokes[pokeNum]; - ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); - // Evolutions - if (!writeEvos) { - // copy old - int evoOffset = oldDataOffset; - while (rom[evoOffset] != 0x00) { - int method = rom[evoOffset] & 0xFF; - int limiter = (method == 2) ? 4 : 3; - for (int b = 0; b < limiter; b++) { - dataStream.write(rom[evoOffset++] & 0xFF); - } - } - } else { - for (Evolution evo : pkmn.evolutionsFrom) { - // write evos for this poke - dataStream.write(evo.type.toIndex(1)); - if (evo.type == EvolutionType.LEVEL) { - dataStream.write(evo.extraInfo); // min lvl - } else if (evo.type == EvolutionType.STONE) { - dataStream.write(evo.extraInfo); // stone item - dataStream.write(1); // minimum level - } else if (evo.type == EvolutionType.TRADE) { - dataStream.write(1); // minimum level - } - int pokeIndexTo = pokeNumToRBYTable[evo.to.number]; - dataStream.write(pokeIndexTo); // species - } - } - // write terminator for evos - dataStream.write(0); - - // Movesets - if (movesets == null) { - // copy old - int movesOffset = oldDataOffset; - // move past evos - while (rom[movesOffset] != 0x00) { - int method = rom[movesOffset] & 0xFF; - movesOffset += (method == 2) ? 4 : 3; - } - movesOffset++; - // copy moves - while (rom[movesOffset] != 0x00) { - dataStream.write(rom[movesOffset++] & 0xFF); - dataStream.write(rom[movesOffset++] & 0xFF); - } - } else { - List<MoveLearnt> ourMoves = movesets.get(pkmn); - int statsOffset = 0; - if (pokeNum == Gen1Constants.mewIndex && !romEntry.isYellow) { - // Mewww - statsOffset = romEntry.getValue("MewStatsOffset"); - } else { - statsOffset = (pokeNum - 1) - * Gen1Constants.baseStatsEntrySize - + pokeStatsOffset; - } - int movenum = 0; - while (movenum < 4 && ourMoves.size() > movenum - && ourMoves.get(movenum).level == 1) { - rom[statsOffset + Gen1Constants.bsLevel1MovesOffset - + movenum] = (byte) moveNumToRomTable[ourMoves - .get(movenum).move]; - movenum++; - } - // Write out the rest of zeroes - for (int mn = movenum; mn < 4; mn++) { - rom[statsOffset + Gen1Constants.bsLevel1MovesOffset - + mn] = 0; - } - // Add the non level 1 moves to the data stream - while (movenum < ourMoves.size()) { - dataStream.write(ourMoves.get(movenum).level); - dataStream.write(moveNumToRomTable[ourMoves - .get(movenum).move]); - movenum++; - } - } - // terminator - dataStream.write(0); - - // done, set writeData - writeData = dataStream.toByteArray(); - try { - dataStream.close(); - } catch (IOException e) { - } - } - - // write data and set pointer? - if (writeData != null) { - int lengthToFit = writeData.length; - int pointerToWrite = -1; - // compression of leading & trailing 0s: - // every entry ends in a 0 (end of move list). - // if a block already has data in it, and the data - // we want to write starts with a 0 (no evolutions) - // we can compress it into the end of the last entry - // this saves a decent amount of space overall. - if ((offsetInMainData + lengthToFit <= mainDataBlockSize) - || (writeData[0] == 0 && offsetInMainData > 0 && offsetInMainData - + lengthToFit == mainDataBlockSize + 1)) { - // place in main storage - if (writeData[0] == 0 && offsetInMainData > 0) { - int writtenDataOffset = mainDataBlockOffset - + offsetInMainData - 1; - pointerToWrite = makeGBPointer(writtenDataOffset); - System.arraycopy(writeData, 1, mainDataBlock, - offsetInMainData, lengthToFit - 1); - offsetInMainData += lengthToFit - 1; - } else { - int writtenDataOffset = mainDataBlockOffset - + offsetInMainData; - pointerToWrite = makeGBPointer(writtenDataOffset); - System.arraycopy(writeData, 0, mainDataBlock, - offsetInMainData, lengthToFit); - offsetInMainData += lengthToFit; - } - } else if (extraSpaceEnabled - && ((offsetInExtraData + lengthToFit <= extraSpaceSize) || (writeData[0] == 0 - && offsetInExtraData > 0 && offsetInExtraData - + lengthToFit == extraSpaceSize + 1))) { - // place in extra space - if (writeData[0] == 0 && offsetInExtraData > 0) { - int writtenDataOffset = extraSpaceOffset - + offsetInExtraData - 1; - pointerToWrite = makeGBPointer(writtenDataOffset); - System.arraycopy(writeData, 1, extraDataBlock, - offsetInExtraData, lengthToFit - 1); - offsetInExtraData += lengthToFit - 1; - } else { - int writtenDataOffset = extraSpaceOffset - + offsetInExtraData; - pointerToWrite = makeGBPointer(writtenDataOffset); - System.arraycopy(writeData, 0, extraDataBlock, - offsetInExtraData, lengthToFit); - offsetInExtraData += lengthToFit; - } - } else { - // this should never happen, but if not, uh oh - throw new RuntimeException( - "Unable to save moves/evolutions, out of space"); - } - if (pointerToWrite >= 0) { - writeWord(pointerTable, (i - 1) * 2, pointerToWrite); - if (setNullEntryPointerHere) { - nullEntryPointer = pointerToWrite; - } - } - } - } - - // Done, write final results to ROM - System.arraycopy(pointerTable, 0, rom, movesEvosStart, - pointerTable.length); - System.arraycopy(mainDataBlock, 0, rom, mainDataBlockOffset, - mainDataBlock.length); - if (extraSpaceEnabled) { - System.arraycopy(extraDataBlock, 0, rom, extraSpaceOffset, - extraDataBlock.length); - } - } - - @Override - public BufferedImage getMascotImage() { - Pokemon mascot = randomPokemon(); - int idx = pokeNumToRBYTable[mascot.number]; - int fsBank; - // define (by index number) the bank that a pokemon's image is in - // using pokered code - if (mascot.number == 151 && !romEntry.isYellow) { - // Mew - fsBank = 1; - } else if (idx < 0x1F) { - fsBank = 0x9; - } else if (idx < 0x4A) { - fsBank = 0xA; - } else if (idx < 0x74) { - fsBank = 0xB; - } else if (idx < 0x99) { - fsBank = 0xC; - } else { - fsBank = 0xD; - } - - int fsOffset = calculateOffset(fsBank, mascot.frontSpritePointer); - Gen1Decmp mscSprite = new Gen1Decmp(rom, fsOffset); - mscSprite.decompress(); - mscSprite.transpose(); - int w = mscSprite.getWidth(); - int h = mscSprite.getHeight(); - - // Palette? - int[] palette; - if (romEntry.getValue("MonPaletteIndicesOffset") > 0 - && romEntry.getValue("SGBPalettesOffset") > 0) { - int palIndex = rom[romEntry.getValue("MonPaletteIndicesOffset") - + mascot.number] & 0xFF; - int palOffset = romEntry.getValue("SGBPalettesOffset") + palIndex - * 8; - palette = new int[4]; - for (int i = 0; i < 4; i++) { - palette[i] = GFXFunctions - .conv16BitColorToARGB(readWord(palOffset + i * 2)); - } - } else { - palette = new int[] { 0xFFFFFFFF, 0xFFAAAAAA, 0xFF666666, - 0xFF000000 }; - } - - byte[] data = mscSprite.getFlattenedData(); - - BufferedImage bim = GFXFunctions.drawTiledImage(data, palette, w, h, 8); - - return bim; - } + public static class Factory extends RomHandler.Factory { + + @Override + public Gen1RomHandler create(Random random, PrintStream logStream) { + return new Gen1RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + long fileLength = new File(filename).length(); + if (fileLength > 8 * 1024 * 1024) { + return false; + } + byte[] loaded = loadFilePartial(filename, 0x1000); + if (loaded.length == 0) { + // nope + return false; + } + return detectRomInner(loaded, (int) fileLength); + } + } + + public Gen1RomHandler(Random random) { + super(random, null); + } + + public Gen1RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + // Important RBY Data Structures + + private int[] pokeNumToRBYTable; + private int[] pokeRBYToNumTable; + private int[] moveNumToRomTable; + private int[] moveRomToNumTable; + private int pokedexCount; + + private Type idToType(int value) { + if (Gen1Constants.typeTable[value] != null) { + return Gen1Constants.typeTable[value]; + } + if (romEntry.extraTypeLookup.containsKey(value)) { + return romEntry.extraTypeLookup.get(value); + } + return null; + } + + private byte typeToByte(Type type) { + if (type == null) { + return 0x00; // revert to normal + } + if (romEntry.extraTypeReverse.containsKey(type)) { + return romEntry.extraTypeReverse.get(type).byteValue(); + } + return Gen1Constants.typeToByte(type); + } + + private static class RomEntry { + private String name; + private String romName; + private int version, nonJapanese; + private String extraTableFile; + private boolean isYellow; + private int crcInHeader = -1; + private Map<String, String> tweakFiles = new HashMap<String, String>(); + private List<TMTextEntry> tmTexts = new ArrayList<TMTextEntry>(); + private Map<String, Integer> entries = new HashMap<String, Integer>(); + private Map<String, int[]> arrayEntries = new HashMap<String, int[]>(); + private List<Integer> staticPokemonSingle = new ArrayList<Integer>(); + private List<GameCornerPokemon> staticPokemonGameCorner = new ArrayList<GameCornerPokemon>(); + private int[] ghostMarowakOffsets = new int[0]; + private Map<Integer, Type> extraTypeLookup = new HashMap<Integer, Type>(); + private Map<Type, Integer> extraTypeReverse = new HashMap<Type, Integer>(); + + private int getValue(String key) { + if (!entries.containsKey(key)) { + entries.put(key, 0); + } + return entries.get(key); + } + } + + private static List<RomEntry> roms; + + static { + loadROMInfo(); + } + + private static class GameCornerPokemon { + private int[] offsets; + + public String toString() { + return Arrays.toString(offsets); + } + } + + private static class TMTextEntry { + private int number; + private int offset; + private String template; + } + + private static void loadROMInfo() { + roms = new ArrayList<RomEntry>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen1_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + r[0] = r[0].trim(); + // Static Pokemon? + if (r[0].equals("StaticPokemonGameCorner[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + GameCornerPokemon gc = new GameCornerPokemon(); + gc.offsets = offs; + current.staticPokemonGameCorner.add(gc); + } else { + int offs = parseRIInt(r[1]); + GameCornerPokemon gc = new GameCornerPokemon(); + gc.offsets = new int[] { offs }; + current.staticPokemonGameCorner.add(gc); + } + } else if (r[0].equals("StaticPokemonGhostMarowak")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.ghostMarowakOffsets = offs; + } else { + } + } else if (r[0].equals("TMText[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] parts = r[1].substring(1, r[1].length() - 1).split(",", 3); + TMTextEntry tte = new TMTextEntry(); + tte.number = parseRIInt(parts[0]); + tte.offset = parseRIInt(parts[1]); + tte.template = parts[2]; + current.tmTexts.add(tte); + } + } else if (r[0].equals("Game")) { + current.romName = r[1]; + } else if (r[0].equals("Version")) { + current.version = parseRIInt(r[1]); + } else if (r[0].equals("NonJapanese")) { + current.nonJapanese = parseRIInt(r[1]); + } else if (r[0].equals("Type")) { + if (r[1].equalsIgnoreCase("Yellow")) { + current.isYellow = true; + } else { + current.isYellow = false; + } + } else if (r[0].equals("ExtraTableFile")) { + current.extraTableFile = r[1]; + } else if (r[0].equals("CRCInHeader")) { + current.crcInHeader = parseRIInt(r[1]); + } else if (r[0].endsWith("Tweak")) { + current.tweakFiles.put(r[0], r[1]); + } else if (r[0].equals("ExtraTypes")) { + // remove the containers + r[1] = r[1].substring(1, r[1].length() - 1); + String[] parts = r[1].split(","); + for (String part : parts) { + String[] iParts = part.split("="); + int typeId = Integer.parseInt(iParts[0], 16); + String typeName = iParts[1].trim(); + Type theType = Type.valueOf(typeName); + current.extraTypeLookup.put(typeId, theType); + current.extraTypeReverse.put(theType, typeId); + } + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.name)) { + // copy from here + boolean cSP = (current.getValue("CopyStaticPokemon") == 1); + boolean cTT = (current.getValue("CopyTMText") == 1); + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.entries.putAll(otherEntry.entries); + if (cSP) { + current.staticPokemonSingle.addAll(otherEntry.staticPokemonSingle); + current.staticPokemonGameCorner.addAll(otherEntry.staticPokemonGameCorner); + current.ghostMarowakOffsets = otherEntry.ghostMarowakOffsets; + current.entries.put("StaticPokemonSupport", 1); + } else { + current.entries.put("StaticPokemonSupport", 0); + } + if (cTT) { + current.tmTexts.addAll(otherEntry.tmTexts); + } + current.extraTableFile = otherEntry.extraTableFile; + } + } + } else { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + if (r[0].startsWith("StaticPokemon")) { + for (int off : offs) { + current.staticPokemonSingle.add(off); + } + } else { + current.arrayEntries.put(r[0], offs); + } + } + + } else { + int offs = parseRIInt(r[1]); + current.entries.put(r[0], offs); + } + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + } + + } + + 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; + } + } + + // This ROM's data + private Pokemon[] pokes; + private List<Pokemon> pokemonList; + private RomEntry romEntry; + private Move[] moves; + private String[] tb; + private Map<String, Byte> d; + private int longestTableToken; + private String[] itemNames; + private String[] mapNames; + private SubMap[] maps; + private boolean xAccNerfed; + + @Override + public boolean detectRom(byte[] rom) { + return detectRomInner(rom, rom.length); + } + + public static boolean detectRomInner(byte[] rom, int romSize) { + if (romSize < GBConstants.minRomSize || romSize > GBConstants.maxRomSize) { + return false; // size check + } + return checkRomEntry(rom) != null; // so it's OK if it's a valid ROM + } + + @Override + public void loadedRom() { + romEntry = checkRomEntry(this.rom); + pokeNumToRBYTable = new int[256]; + pokeRBYToNumTable = new int[256]; + moveNumToRomTable = new int[256]; + moveRomToNumTable = new int[256]; + tb = new String[256]; + d = new HashMap<String, Byte>(); + maps = new SubMap[256]; + xAccNerfed = false; + clearTextTables(); + readTextTable("gameboy_jap"); + if (romEntry.extraTableFile != null && romEntry.extraTableFile.equalsIgnoreCase("none") == false) { + readTextTable(romEntry.extraTableFile); + } + loadPokedexOrder(); + loadPokemonStats(); + pokemonList = Arrays.asList(pokes); + loadMoves(); + preloadMaps(); + loadItemNames(); + loadMapNames(); + } + + private void loadPokedexOrder() { + int pkmnCount = romEntry.getValue("InternalPokemonCount"); + int orderOffset = romEntry.getValue("PokedexOrder"); + pokedexCount = 0; + for (int i = 1; i <= pkmnCount; i++) { + int pokedexNum = rom[orderOffset + i - 1] & 0xFF; + pokeRBYToNumTable[i] = pokedexNum; + if (pokedexNum != 0 && pokeNumToRBYTable[pokedexNum] == 0) { + pokeNumToRBYTable[pokedexNum] = i; + } + pokedexCount = Math.max(pokedexCount, pokedexNum); + } + } + + private static RomEntry checkRomEntry(byte[] rom) { + int version = rom[GBConstants.versionOffset] & 0xFF; + int nonjap = rom[GBConstants.jpFlagOffset] & 0xFF; + // Check for specific CRC first + int crcInHeader = ((rom[GBConstants.crcOffset] & 0xFF) << 8) | (rom[GBConstants.crcOffset + 1] & 0xFF); + for (RomEntry re : roms) { + if (romSig(rom, re.romName) && re.version == version && re.nonJapanese == nonjap + && re.crcInHeader == crcInHeader) { + return re; + } + } + // Now check for non-specific-CRC entries + for (RomEntry re : roms) { + if (romSig(rom, re.romName) && re.version == version && re.nonJapanese == nonjap && re.crcInHeader == -1) { + return re; + } + } + // Not found + return null; + } + + private void clearTextTables() { + tb = new String[256]; + d.clear(); + longestTableToken = 0; + } + + private void readTextTable(String name) { + try { + Scanner sc = new Scanner(FileFunctions.openConfig(name + ".tbl"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine(); + if (!q.trim().isEmpty()) { + String[] r = q.split("=", 2); + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + int hexcode = Integer.parseInt(r[0], 16); + if (tb[hexcode] != null) { + String oldMatch = tb[hexcode]; + tb[hexcode] = null; + if (d.get(oldMatch) == hexcode) { + d.remove(oldMatch); + } + } + tb[hexcode] = r[1]; + longestTableToken = Math.max(longestTableToken, r[1].length()); + d.put(r[1], (byte) hexcode); + } + } + sc.close(); + } catch (FileNotFoundException e) { + } + + } + + @Override + public void savingRom() { + savePokemonStats(); + saveMoves(); + } + + private String[] readMoveNames() { + int moveCount = romEntry.getValue("MoveCount"); + int offset = romEntry.getValue("MoveNamesOffset"); + String[] moveNames = new String[moveCount + 1]; + for (int i = 1; i <= moveCount; i++) { + moveNames[i] = readVariableLengthString(offset); + offset += lengthOfStringAt(offset) + 1; + } + return moveNames; + } + + private void loadMoves() { + String[] moveNames = readMoveNames(); + int moveCount = romEntry.getValue("MoveCount"); + int movesOffset = romEntry.getValue("MoveDataOffset"); + // check real move count + int trueMoveCount = 0; + for (int i = 1; i <= moveCount; i++) { + // temp hack for Brown + if (rom[movesOffset + (i - 1) * 6] != 0 && moveNames[i].equals("Nothing") == false) { + trueMoveCount++; + } + } + moves = new Move[trueMoveCount + 1]; + int trueMoveIndex = 0; + + for (int i = 1; i <= moveCount; i++) { + int anim = rom[movesOffset + (i - 1) * 6] & 0xFF; + // another temp hack for brown + if (anim > 0 && moveNames[i].equals("Nothing") == false) { + trueMoveIndex++; + moveNumToRomTable[trueMoveIndex] = i; + moveRomToNumTable[i] = trueMoveIndex; + moves[trueMoveIndex] = new Move(); + moves[trueMoveIndex].name = moveNames[i]; + moves[trueMoveIndex].internalId = i; + moves[trueMoveIndex].number = trueMoveIndex; + moves[trueMoveIndex].effectIndex = rom[movesOffset + (i - 1) * 6 + 1] & 0xFF; + moves[trueMoveIndex].hitratio = ((rom[movesOffset + (i - 1) * 6 + 4] & 0xFF) + 0) / 255.0 * 100; + moves[trueMoveIndex].power = rom[movesOffset + (i - 1) * 6 + 2] & 0xFF; + moves[trueMoveIndex].pp = rom[movesOffset + (i - 1) * 6 + 5] & 0xFF; + moves[trueMoveIndex].type = idToType(rom[movesOffset + (i - 1) * 6 + 3] & 0xFF); + } + } + + } + + private void saveMoves() { + int movesOffset = romEntry.getValue("MoveDataOffset"); + for (Move m : moves) { + if (m != null) { + int i = m.internalId; + rom[movesOffset + (i - 1) * 6 + 1] = (byte) m.effectIndex; + rom[movesOffset + (i - 1) * 6 + 2] = (byte) m.power; + rom[movesOffset + (i - 1) * 6 + 3] = typeToByte(m.type); + int hitratio = (int) Math.round(m.hitratio * 2.55); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 255) { + hitratio = 255; + } + rom[movesOffset + (i - 1) * 6 + 4] = (byte) hitratio; + rom[movesOffset + (i - 1) * 6 + 5] = (byte) m.pp; + } + } + } + + public List<Move> getMoves() { + return Arrays.asList(moves); + } + + private void loadPokemonStats() { + pokes = new Pokemon[pokedexCount + 1]; + // Fetch our names + String[] pokeNames = readPokemonNames(); + // Get base stats + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + for (int i = 1; i <= pokedexCount; i++) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + if (i != Gen1Constants.mewIndex || romEntry.isYellow) { + loadBasicPokeStats(pokes[i], pokeStatsOffset + (i - 1) * Gen1Constants.baseStatsEntrySize); + } + // Name? + pokes[i].name = pokeNames[pokeNumToRBYTable[i]]; + } + + // Mew override for R/B + if (!romEntry.isYellow) { + loadBasicPokeStats(pokes[Gen1Constants.mewIndex], romEntry.getValue("MewStatsOffset")); + } + + // Evolutions + populateEvolutions(); + + } + + private void savePokemonStats() { + // Write pokemon names + int offs = romEntry.getValue("PokemonNamesOffset"); + int nameLength = romEntry.getValue("PokemonNamesLength"); + for (int i = 1; i <= pokedexCount; i++) { + int rbynum = pokeNumToRBYTable[i]; + int stringOffset = offs + (rbynum - 1) * nameLength; + writeFixedLengthString(pokes[i].name, stringOffset, nameLength); + } + // Write pokemon stats + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + for (int i = 1; i <= pokedexCount; i++) { + if (i == Gen1Constants.mewIndex) { + continue; + } + saveBasicPokeStats(pokes[i], pokeStatsOffset + (i - 1) * Gen1Constants.baseStatsEntrySize); + } + // Write MEW + int mewOffset = romEntry.isYellow ? pokeStatsOffset + (Gen1Constants.mewIndex - 1) + * Gen1Constants.baseStatsEntrySize : romEntry.getValue("MewStatsOffset"); + saveBasicPokeStats(pokes[Gen1Constants.mewIndex], mewOffset); + + // Write evolutions + writeEvosAndMovesLearnt(true, null); + } + + private void loadBasicPokeStats(Pokemon pkmn, int offset) { + pkmn.hp = rom[offset + Gen1Constants.bsHPOffset] & 0xFF; + pkmn.attack = rom[offset + Gen1Constants.bsAttackOffset] & 0xFF; + pkmn.defense = rom[offset + Gen1Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = rom[offset + Gen1Constants.bsSpeedOffset] & 0xFF; + pkmn.special = rom[offset + Gen1Constants.bsSpecialOffset] & 0xFF; + pkmn.spatk = pkmn.special; + pkmn.spdef = pkmn.special; + // Type + pkmn.primaryType = idToType(rom[offset + Gen1Constants.bsPrimaryTypeOffset] & 0xFF); + pkmn.secondaryType = idToType(rom[offset + Gen1Constants.bsSecondaryTypeOffset] & 0xFF); + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + + pkmn.catchRate = rom[offset + Gen1Constants.bsCatchRateOffset] & 0xFF; + pkmn.growthCurve = ExpCurve.fromByte(rom[offset + Gen1Constants.bsGrowthCurveOffset]); + pkmn.frontSpritePointer = readWord(offset + Gen1Constants.bsFrontSpriteOffset); + + pkmn.guaranteedHeldItem = -1; + pkmn.commonHeldItem = -1; + pkmn.rareHeldItem = -1; + pkmn.darkGrassHeldItem = -1; + } + + private void saveBasicPokeStats(Pokemon pkmn, int offset) { + rom[offset + Gen1Constants.bsHPOffset] = (byte) pkmn.hp; + rom[offset + Gen1Constants.bsAttackOffset] = (byte) pkmn.attack; + rom[offset + Gen1Constants.bsDefenseOffset] = (byte) pkmn.defense; + rom[offset + Gen1Constants.bsSpeedOffset] = (byte) pkmn.speed; + rom[offset + Gen1Constants.bsSpecialOffset] = (byte) pkmn.special; + rom[offset + Gen1Constants.bsPrimaryTypeOffset] = typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + rom[offset + Gen1Constants.bsSecondaryTypeOffset] = rom[offset + Gen1Constants.bsPrimaryTypeOffset]; + } else { + rom[offset + Gen1Constants.bsSecondaryTypeOffset] = typeToByte(pkmn.secondaryType); + } + rom[offset + Gen1Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + rom[offset + Gen1Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + } + + private String[] readPokemonNames() { + int offs = romEntry.getValue("PokemonNamesOffset"); + int nameLength = romEntry.getValue("PokemonNamesLength"); + int pkmnCount = romEntry.getValue("InternalPokemonCount"); + String[] names = new String[pkmnCount + 1]; + for (int i = 1; i <= pkmnCount; i++) { + names[i] = readFixedLengthString(offs + (i - 1) * nameLength, nameLength); + } + return names; + } + + private String readString(int offset, int maxLength) { + StringBuilder string = new StringBuilder(); + for (int c = 0; c < maxLength; c++) { + int currChar = rom[offset + c] & 0xFF; + if (tb[currChar] != null) { + string.append(tb[currChar]); + } else { + if (currChar == GBConstants.stringTerminator || currChar == GBConstants.stringNull) { + break; + } else { + string.append("\\x" + String.format("%02X", currChar)); + } + } + } + return string.toString(); + } + + public byte[] translateString(String text) { + List<Byte> data = new ArrayList<Byte>(); + while (text.length() != 0) { + int i = Math.max(0, longestTableToken - text.length()); + if (text.charAt(0) == '\\' && text.charAt(1) == 'x') { + data.add((byte) Integer.parseInt(text.substring(2, 4), 16)); + text = text.substring(4); + } else { + while (!(d.containsKey(text.substring(0, longestTableToken - i)) || (i == longestTableToken))) { + i++; + } + if (i == longestTableToken) { + text = text.substring(1); + } else { + data.add(d.get(text.substring(0, longestTableToken - i))); + text = text.substring(longestTableToken - i); + } + } + } + byte[] ret = new byte[data.size()]; + for (int i = 0; i < ret.length; i++) { + ret[i] = data.get(i); + } + return ret; + } + + private String readFixedLengthString(int offset, int length) { + return readString(offset, length); + } + + private void writeFixedLengthString(String str, int offset, int length) { + byte[] translated = translateString(str); + int len = Math.min(translated.length, length); + System.arraycopy(translated, 0, rom, offset, len); + while (len < length) { + rom[offset + len] = GBConstants.stringTerminator; + len++; + } + } + + private int makeGBPointer(int offset) { + if (offset < GBConstants.bankSize) { + return offset; + } else { + return (offset % GBConstants.bankSize) + GBConstants.bankSize; + } + } + + private int bankOf(int offset) { + return (offset / GBConstants.bankSize); + } + + private int calculateOffset(int bank, int pointer) { + if (pointer < GBConstants.bankSize) { + return pointer; + } else { + return (pointer % GBConstants.bankSize) + bank * GBConstants.bankSize; + } + } + + public String readVariableLengthString(int offset) { + return readString(offset, Integer.MAX_VALUE); + } + + public byte[] traduire(String str) { + return translateString(str); + } + + public String readVariableLengthScriptString(int offset) { + return readString(offset, Integer.MAX_VALUE); + } + + private void writeFixedLengthScriptString(String str, int offset, int length) { + byte[] translated = translateString(str); + int len = Math.min(translated.length, length); + System.arraycopy(translated, 0, rom, offset, len); + while (len < length) { + rom[offset + len] = GBConstants.stringNull; + len++; + } + } + + private int lengthOfStringAt(int offset) { + int len = 0; + while (rom[offset + len] != GBConstants.stringTerminator && rom[offset + len] != GBConstants.stringNull) { + len++; + } + return len; + } + + private static boolean romSig(byte[] rom, String sig) { + try { + int sigOffset = GBConstants.romSigOffset; + byte[] sigBytes = sig.getBytes("US-ASCII"); + for (int i = 0; i < sigBytes.length; i++) { + if (rom[sigOffset + i] != sigBytes[i]) { + return false; + } + } + return true; + } catch (UnsupportedEncodingException ex) { + return false; + } + + } + + @Override + public List<Pokemon> getStarters() { + // Get the starters + List<Pokemon> starters = new ArrayList<Pokemon>(); + starters.add(pokes[pokeRBYToNumTable[rom[romEntry.arrayEntries.get("StarterOffsets1")[0]] & 0xFF]]); + starters.add(pokes[pokeRBYToNumTable[rom[romEntry.arrayEntries.get("StarterOffsets2")[0]] & 0xFF]]); + if (!romEntry.isYellow) { + starters.add(pokes[pokeRBYToNumTable[rom[romEntry.arrayEntries.get("StarterOffsets3")[0]] & 0xFF]]); + } + return starters; + } + + @Override + public boolean setStarters(List<Pokemon> newStarters) { + // Amount? + int starterAmount = 2; + if (!romEntry.isYellow) { + starterAmount = 3; + } + + // Basic checks + if (newStarters.size() != starterAmount) { + return false; + } + + // Patch starter bytes + for (int i = 0; i < starterAmount; i++) { + byte starter = (byte) pokeNumToRBYTable[newStarters.get(i).number]; + int[] offsets = romEntry.arrayEntries.get("StarterOffsets" + (i + 1)); + for (int offset : offsets) { + rom[offset] = starter; + } + } + + // Special stuff for non-Yellow only + + if (!romEntry.isYellow) { + + // Starter text + if (romEntry.getValue("CanChangeStarterText") > 0) { + List<Integer> starterTextOffsets = RomFunctions.search(rom, traduire("So! You want the")); + for (int i = 0; i < 3 && i < starterTextOffsets.size(); i++) { + writeFixedLengthScriptString("So! You want\\n" + newStarters.get(i).name + "?\\e", + starterTextOffsets.get(i), lengthOfStringAt(starterTextOffsets.get(i)) + 1); + } + } + + // Patch starter pokedex routine? + // Can only do in 1M roms because of size concerns + if (romEntry.getValue("PatchPokedex") > 0) { + + // Starter pokedex required RAM values + // RAM offset => value + // Allows for multiple starters in the same RAM byte + Map<Integer, Integer> onValues = new TreeMap<Integer, Integer>(); + for (int i = 0; i < 3; i++) { + int pkDexNum = newStarters.get(i).number; + int ramOffset = (pkDexNum - 1) / 8 + romEntry.getValue("PokedexRamOffset"); + int bitShift = (pkDexNum - 1) % 8; + int writeValue = 1 << bitShift; + if (onValues.containsKey(ramOffset)) { + onValues.put(ramOffset, onValues.get(ramOffset) | writeValue); + } else { + onValues.put(ramOffset, writeValue); + } + } + + // Starter pokedex offset/pointer calculations + + int pkDexOnOffset = romEntry.getValue("StarterPokedexOnOffset"); + int pkDexOffOffset = romEntry.getValue("StarterPokedexOffOffset"); + + int sizeForOnRoutine = 5 * onValues.size() + 3; + int writeOnRoutineTo = romEntry.getValue("StarterPokedexBranchOffset"); + int writeOffRoutineTo = writeOnRoutineTo + sizeForOnRoutine; + int offsetForOnRoutine = makeGBPointer(writeOnRoutineTo); + int offsetForOffRoutine = makeGBPointer(writeOffRoutineTo); + int retOnOffset = makeGBPointer(pkDexOnOffset + 5); + int retOffOffset = makeGBPointer(pkDexOffOffset + 4); + + // Starter pokedex + // Branch to our new routine(s) + + // Turn bytes on + rom[pkDexOnOffset] = GBConstants.gbZ80Jump; + writeWord(pkDexOnOffset + 1, offsetForOnRoutine); + rom[pkDexOnOffset + 3] = GBConstants.gbZ80Nop; + rom[pkDexOnOffset + 4] = GBConstants.gbZ80Nop; + + // Turn bytes off + rom[pkDexOffOffset] = GBConstants.gbZ80Jump; + writeWord(pkDexOffOffset + 1, offsetForOffRoutine); + rom[pkDexOffOffset + 3] = GBConstants.gbZ80Nop; + + // Put together the two scripts + rom[writeOffRoutineTo] = GBConstants.gbZ80XorA; + int turnOnOffset = writeOnRoutineTo; + int turnOffOffset = writeOffRoutineTo + 1; + for (int ramOffset : onValues.keySet()) { + int onValue = onValues.get(ramOffset); + // Turn on code + rom[turnOnOffset++] = GBConstants.gbZ80LdA; + rom[turnOnOffset++] = (byte) onValue; + // Turn on code for ram writing + rom[turnOnOffset++] = GBConstants.gbZ80LdAToFar; + rom[turnOnOffset++] = (byte) (ramOffset % 0x100); + rom[turnOnOffset++] = (byte) (ramOffset / 0x100); + // Turn off code for ram writing + rom[turnOffOffset++] = GBConstants.gbZ80LdAToFar; + rom[turnOffOffset++] = (byte) (ramOffset % 0x100); + rom[turnOffOffset++] = (byte) (ramOffset / 0x100); + } + // Jump back + rom[turnOnOffset++] = GBConstants.gbZ80Jump; + writeWord(turnOnOffset, retOnOffset); + + rom[turnOffOffset++] = GBConstants.gbZ80Jump; + writeWord(turnOffOffset, retOffOffset); + } + + } + + return true; + + } + + @Override + public List<Integer> getStarterHeldItems() { + // do nothing + return new ArrayList<Integer>(); + } + + @Override + public void setStarterHeldItems(List<Integer> items) { + // do nothing + } + + @Override + public void shufflePokemonStats() { + for (int i = 1; i <= pokedexCount; i++) { + pokes[i].shuffleStats(this.random); + } + } + + @Override + public List<EncounterSet> getEncounters(boolean useTimeOfDay) { + List<EncounterSet> encounters = new ArrayList<EncounterSet>(); + + Pokemon ghostMarowak = pokes[Gen1Constants.marowakIndex]; + if (canChangeStaticPokemon()) { + ghostMarowak = pokes[pokeRBYToNumTable[rom[romEntry.ghostMarowakOffsets[0]] & 0xFF]]; + } + + // grass & water + List<Integer> usedOffsets = new ArrayList<Integer>(); + int tableOffset = romEntry.getValue("WildPokemonTableOffset"); + int tableBank = bankOf(tableOffset); + int mapID = -1; + + while (readWord(tableOffset) != Gen1Constants.encounterTableEnd) { + mapID++; + int offset = calculateOffset(tableBank, readWord(tableOffset)); + int rootOffset = offset; + if (!usedOffsets.contains(offset)) { + usedOffsets.add(offset); + // grass and water are exactly the same + for (int a = 0; a < 2; a++) { + int rate = rom[offset++] & 0xFF; + if (rate > 0) { + // there is data here + EncounterSet thisSet = new EncounterSet(); + thisSet.rate = rate; + thisSet.offset = rootOffset; + thisSet.displayName = (a == 1 ? "Surfing" : "Grass/Cave") + " on " + mapNames[mapID]; + if (mapID >= Gen1Constants.towerMapsStartIndex && mapID <= Gen1Constants.towerMapsEndIndex) { + thisSet.bannedPokemon.add(ghostMarowak); + } + for (int slot = 0; slot < Gen1Constants.encounterTableSize; slot++) { + Encounter enc = new Encounter(); + enc.level = rom[offset] & 0xFF; + enc.pokemon = pokes[pokeRBYToNumTable[rom[offset + 1] & 0xFF]]; + thisSet.encounters.add(enc); + offset += 2; + } + encounters.add(thisSet); + } + } + } else { + for (EncounterSet es : encounters) { + if (es.offset == offset) { + es.displayName += ", " + mapNames[mapID]; + } + } + } + tableOffset += 2; + } + + // old rod + int oldRodOffset = romEntry.getValue("OldRodOffset"); + EncounterSet oldRodSet = new EncounterSet(); + oldRodSet.displayName = "Old Rod Fishing"; + Encounter oldRodEnc = new Encounter(); + oldRodEnc.level = rom[oldRodOffset + 2] & 0xFF; + oldRodEnc.pokemon = pokes[pokeRBYToNumTable[rom[oldRodOffset + 1] & 0xFF]]; + oldRodSet.encounters.add(oldRodEnc); + encounters.add(oldRodSet); + + // good rod + int goodRodOffset = romEntry.getValue("GoodRodOffset"); + EncounterSet goodRodSet = new EncounterSet(); + goodRodSet.displayName = "Good Rod Fishing"; + for (int grSlot = 0; grSlot < 2; grSlot++) { + Encounter enc = new Encounter(); + enc.level = rom[goodRodOffset + grSlot * 2] & 0xFF; + enc.pokemon = pokes[pokeRBYToNumTable[rom[goodRodOffset + grSlot * 2 + 1] & 0xFF]]; + goodRodSet.encounters.add(enc); + } + encounters.add(goodRodSet); + + // super rod + if (romEntry.isYellow) { + int superRodOffset = romEntry.getValue("SuperRodTableOffset"); + while ((rom[superRodOffset] & 0xFF) != 0xFF) { + int map = rom[superRodOffset++] & 0xFF; + EncounterSet thisSet = new EncounterSet(); + thisSet.displayName = "Super Rod Fishing on " + mapNames[map]; + for (int encN = 0; encN < Gen1Constants.yellowSuperRodTableSize; encN++) { + Encounter enc = new Encounter(); + enc.level = rom[superRodOffset + 1] & 0xFF; + enc.pokemon = pokes[pokeRBYToNumTable[rom[superRodOffset] & 0xFF]]; + thisSet.encounters.add(enc); + superRodOffset += 2; + } + encounters.add(thisSet); + } + } else { + // red/blue + int superRodOffset = romEntry.getValue("SuperRodTableOffset"); + int superRodBank = bankOf(superRodOffset); + List<Integer> usedSROffsets = new ArrayList<Integer>(); + while ((rom[superRodOffset] & 0xFF) != 0xFF) { + int map = rom[superRodOffset++] & 0xFF; + int setOffset = calculateOffset(superRodBank, readWord(superRodOffset)); + superRodOffset += 2; + if (!usedSROffsets.contains(setOffset)) { + usedSROffsets.add(setOffset); + EncounterSet thisSet = new EncounterSet(); + thisSet.displayName = "Super Rod Fishing on " + mapNames[map]; + thisSet.offset = setOffset; + int pokesInSet = rom[setOffset++] & 0xFF; + for (int encN = 0; encN < pokesInSet; encN++) { + Encounter enc = new Encounter(); + enc.level = rom[setOffset] & 0xFF; + enc.pokemon = pokes[pokeRBYToNumTable[rom[setOffset + 1] & 0xFF]]; + thisSet.encounters.add(enc); + setOffset += 2; + } + encounters.add(thisSet); + } else { + for (EncounterSet es : encounters) { + if (es.offset == setOffset) { + es.displayName += ", " + mapNames[map]; + } + } + } + } + } + + return encounters; + } + + @Override + public void setEncounters(boolean useTimeOfDay, List<EncounterSet> encounters) { + Iterator<EncounterSet> encsetit = encounters.iterator(); + + // grass & water + List<Integer> usedOffsets = new ArrayList<Integer>(); + int tableOffset = romEntry.getValue("WildPokemonTableOffset"); + int tableBank = bankOf(tableOffset); + + while (readWord(tableOffset) != Gen1Constants.encounterTableEnd) { + int offset = calculateOffset(tableBank, readWord(tableOffset)); + if (!usedOffsets.contains(offset)) { + usedOffsets.add(offset); + // grass and water are exactly the same + for (int a = 0; a < 2; a++) { + int rate = rom[offset++] & 0xFF; + if (rate > 0) { + // there is data here + EncounterSet thisSet = encsetit.next(); + for (int slot = 0; slot < Gen1Constants.encounterTableSize; slot++) { + Encounter enc = thisSet.encounters.get(slot); + rom[offset] = (byte) enc.level; + rom[offset + 1] = (byte) pokeNumToRBYTable[enc.pokemon.number]; + offset += 2; + } + } + } + } + tableOffset += 2; + } + + // old rod + int oldRodOffset = romEntry.getValue("OldRodOffset"); + EncounterSet oldRodSet = encsetit.next(); + Encounter oldRodEnc = oldRodSet.encounters.get(0); + rom[oldRodOffset + 2] = (byte) oldRodEnc.level; + rom[oldRodOffset + 1] = (byte) pokeNumToRBYTable[oldRodEnc.pokemon.number]; + + // good rod + int goodRodOffset = romEntry.getValue("GoodRodOffset"); + EncounterSet goodRodSet = encsetit.next(); + for (int grSlot = 0; grSlot < 2; grSlot++) { + Encounter enc = goodRodSet.encounters.get(grSlot); + rom[goodRodOffset + grSlot * 2] = (byte) enc.level; + rom[goodRodOffset + grSlot * 2 + 1] = (byte) pokeNumToRBYTable[enc.pokemon.number]; + } + + // super rod + if (romEntry.isYellow) { + int superRodOffset = romEntry.getValue("SuperRodTableOffset"); + while ((rom[superRodOffset] & 0xFF) != 0xFF) { + superRodOffset++; + EncounterSet thisSet = encsetit.next(); + for (int encN = 0; encN < Gen1Constants.yellowSuperRodTableSize; encN++) { + Encounter enc = thisSet.encounters.get(encN); + rom[superRodOffset + 1] = (byte) enc.level; + rom[superRodOffset] = (byte) pokeNumToRBYTable[enc.pokemon.number]; + superRodOffset += 2; + } + } + } else { + // red/blue + int superRodOffset = romEntry.getValue("SuperRodTableOffset"); + int superRodBank = bankOf(superRodOffset); + List<Integer> usedSROffsets = new ArrayList<Integer>(); + while ((rom[superRodOffset] & 0xFF) != 0xFF) { + superRodOffset++; + int setOffset = calculateOffset(superRodBank, readWord(superRodOffset)); + superRodOffset += 2; + if (!usedSROffsets.contains(setOffset)) { + usedSROffsets.add(setOffset); + int pokesInSet = rom[setOffset++] & 0xFF; + EncounterSet thisSet = encsetit.next(); + for (int encN = 0; encN < pokesInSet; encN++) { + Encounter enc = thisSet.encounters.get(encN); + rom[setOffset] = (byte) enc.level; + rom[setOffset + 1] = (byte) pokeNumToRBYTable[enc.pokemon.number]; + setOffset += 2; + } + } + } + } + } + + @Override + public List<Pokemon> getPokemon() { + return pokemonList; + } + + public List<Trainer> getTrainers() { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = Gen1Constants.trainerClassCount; + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount + 1]; + for (int i = 1; i <= traineramount; i++) { + int tPointer = readWord(traineroffset + (i - 1) * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), tPointer); + } + + List<String> tcnames = getTrainerClassesForText(); + + List<Trainer> allTrainers = new ArrayList<Trainer>(); + for (int i = 1; i <= traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + String tcname = tcnames.get(i - 1); + for (int trnum = 0; trnum < limit; trnum++) { + Trainer tr = new Trainer(); + tr.offset = offs; + tr.trainerclass = i; + tr.fullDisplayName = tcname; + int dataType = rom[offs] & 0xFF; + if (dataType == 0xFF) { + // "Special" trainer + tr.poketype = 1; + offs++; + while (rom[offs] != 0x0) { + TrainerPokemon tpk = new TrainerPokemon(); + tpk.level = rom[offs] & 0xFF; + tpk.pokemon = pokes[pokeRBYToNumTable[rom[offs + 1] & 0xFF]]; + tr.pokemon.add(tpk); + offs += 2; + } + } else { + tr.poketype = 0; + int fixedLevel = dataType; + offs++; + while (rom[offs] != 0x0) { + TrainerPokemon tpk = new TrainerPokemon(); + tpk.level = fixedLevel; + tpk.pokemon = pokes[pokeRBYToNumTable[rom[offs] & 0xFF]]; + tr.pokemon.add(tpk); + offs++; + } + } + offs++; + allTrainers.add(tr); + } + } + Gen1Constants.tagTrainersUniversal(allTrainers); + if (romEntry.isYellow) { + Gen1Constants.tagTrainersYellow(allTrainers); + } else { + Gen1Constants.tagTrainersRB(allTrainers); + } + return allTrainers; + } + + public void setTrainers(List<Trainer> trainerData) { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = Gen1Constants.trainerClassCount; + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount + 1]; + for (int i = 1; i <= traineramount; i++) { + int tPointer = readWord(traineroffset + (i - 1) * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), tPointer); + } + + Iterator<Trainer> allTrainers = trainerData.iterator(); + for (int i = 1; i <= traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + for (int trnum = 0; trnum < limit; trnum++) { + Trainer tr = allTrainers.next(); + if (tr.trainerclass != i) { + System.err.println("Trainer mismatch: " + tr.name); + } + Iterator<TrainerPokemon> tPokes = tr.pokemon.iterator(); + // Write their pokemon based on poketype + if (tr.poketype == 0) { + // Regular trainer + int fixedLevel = tr.pokemon.get(0).level; + rom[offs] = (byte) fixedLevel; + offs++; + while (tPokes.hasNext()) { + TrainerPokemon tpk = tPokes.next(); + rom[offs] = (byte) pokeNumToRBYTable[tpk.pokemon.number]; + offs++; + } + } else { + // Special trainer + rom[offs] = (byte) 0xFF; + offs++; + while (tPokes.hasNext()) { + TrainerPokemon tpk = tPokes.next(); + rom[offs] = (byte) tpk.level; + rom[offs + 1] = (byte) pokeNumToRBYTable[tpk.pokemon.number]; + offs += 2; + } + } + rom[offs] = 0; + offs++; + } + } + + // Custom Moves AI Table + // Zero it out entirely. + rom[romEntry.getValue("ExtraTrainerMovesTableOffset")] = (byte) 0xFF; + + // Champion Rival overrides in Red/Blue + if (!isYellow()) { + // hacky relative offset (very likely to work but maybe not always) + int champRivalJump = romEntry.getValue("GymLeaderMovesTableOffset") + - Gen1Constants.champRivalOffsetFromGymLeaderMoves; + // nop out this jump + rom[champRivalJump] = GBConstants.gbZ80Nop; + rom[champRivalJump + 1] = GBConstants.gbZ80Nop; + } + + } + + @Override + public boolean isYellow() { + return romEntry.isYellow; + } + + @Override + public boolean typeInGame(Type type) { + if (type.isHackOnly == false && (type != Type.DARK && type != Type.STEEL)) { + return true; + } + if (romEntry.extraTypeReverse.containsKey(type)) { + return true; + } + return false; + } + + private void fixTypeEffectiveness() { + // TODO rewrite to use table properly + int base = romEntry.getValue("TypeEffectivenessOffset"); + log("--Fixing Type Effectiveness--"); + // Change Poison SE to bug (should be neutral) + // to Ice NE to Fire (is currently neutral) + log("Replaced: Poison super effective vs Bug => Ice not very effective vs Fire"); + rom[base + 135] = typeToByte(Type.ICE); + rom[base + 136] = typeToByte(Type.FIRE); + rom[base + 137] = 5; // Not very effective + // Change BUG SE to Poison to Bug NE to Poison + log("Changed: Bug super effective vs Poison => Bug not very effective vs Poison"); + rom[base + 203] = 5; // Not very effective + // Change Ghost 0E to Psychic to Ghost SE to Psychic + log("Changed: Psychic immune to Ghost => Ghost super effective vs Psychic"); + rom[base + 227] = 20; // Super effective + logBlankLine(); + } + + @Override + public Map<Pokemon, List<MoveLearnt>> getMovesLearnt() { + Map<Pokemon, List<MoveLearnt>> movesets = new TreeMap<Pokemon, List<MoveLearnt>>(); + int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + int pkmnCount = romEntry.getValue("InternalPokemonCount"); + for (int i = 1; i <= pkmnCount; i++) { + int pointer = readWord(pointersOffset + (i - 1) * 2); + int realPointer = calculateOffset(bankOf(pointersOffset), pointer); + if (pokeRBYToNumTable[i] != 0) { + Pokemon pkmn = pokes[pokeRBYToNumTable[i]]; + int statsOffset = 0; + if (pokeRBYToNumTable[i] == Gen1Constants.mewIndex && !romEntry.isYellow) { + // Mewww + statsOffset = romEntry.getValue("MewStatsOffset"); + } else { + statsOffset = (pokeRBYToNumTable[i] - 1) * 0x1C + pokeStatsOffset; + } + List<MoveLearnt> ourMoves = new ArrayList<MoveLearnt>(); + for (int delta = Gen1Constants.bsLevel1MovesOffset; delta < Gen1Constants.bsLevel1MovesOffset + 4; delta++) { + if (rom[statsOffset + delta] != 0x00) { + MoveLearnt learnt = new MoveLearnt(); + learnt.level = 1; + learnt.move = moveRomToNumTable[rom[statsOffset + delta] & 0xFF]; + ourMoves.add(learnt); + } + } + // Skip over evolution data + while (rom[realPointer] != 0) { + if (rom[realPointer] == 1) { + realPointer += 3; + } else if (rom[realPointer] == 2) { + realPointer += 4; + } else if (rom[realPointer] == 3) { + realPointer += 3; + } + } + realPointer++; + while (rom[realPointer] != 0) { + MoveLearnt learnt = new MoveLearnt(); + learnt.level = rom[realPointer] & 0xFF; + learnt.move = moveRomToNumTable[rom[realPointer + 1] & 0xFF]; + ourMoves.add(learnt); + realPointer += 2; + } + movesets.put(pkmn, ourMoves); + } + } + return movesets; + } + + @Override + public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets) { + // new method for moves learnt + writeEvosAndMovesLearnt(false, movesets); + } + + @Override + public List<Pokemon> getStaticPokemon() { + List<Pokemon> statics = new ArrayList<Pokemon>(); + if (romEntry.getValue("StaticPokemonSupport") > 0) { + for (int offset : romEntry.staticPokemonSingle) { + statics.add(pokes[pokeRBYToNumTable[rom[offset] & 0xFF]]); + } + for (GameCornerPokemon gcp : romEntry.staticPokemonGameCorner) { + statics.add(pokes[pokeRBYToNumTable[rom[gcp.offsets[0]] & 0xFF]]); + } + // Ghost Marowak + statics.add(pokes[pokeRBYToNumTable[rom[romEntry.ghostMarowakOffsets[0]] & 0xFF]]); + } + return statics; + } + + @Override + public boolean setStaticPokemon(List<Pokemon> staticPokemon) { + if (romEntry.getValue("StaticPokemonSupport") == 0) { + return false; + } + // Checks + int singleSize = romEntry.staticPokemonSingle.size(); + int gcSize = romEntry.staticPokemonGameCorner.size(); + if (staticPokemon.size() != singleSize + gcSize + 1) { + return false; + } + + // Singular entries + for (int i = 0; i < singleSize; i++) { + rom[romEntry.staticPokemonSingle.get(i)] = (byte) pokeNumToRBYTable[staticPokemon.get(i).number]; + } + + // Game corner + for (int i = 0; i < gcSize; i++) { + byte pokeNum = (byte) pokeNumToRBYTable[staticPokemon.get(i + singleSize).number]; + int[] offsets = romEntry.staticPokemonGameCorner.get(i).offsets; + for (int offset : offsets) { + rom[offset] = pokeNum; + } + } + + // Ghost Marowak + byte maroNum = (byte) pokeNumToRBYTable[staticPokemon.get(singleSize + gcSize).number]; + for (int maroOffset : romEntry.ghostMarowakOffsets) { + rom[maroOffset] = maroNum; + } + + return true; + } + + @Override + public boolean canChangeStaticPokemon() { + return (romEntry.getValue("StaticPokemonSupport") > 0); + } + + @Override + public List<Integer> getTMMoves() { + List<Integer> tms = new ArrayList<Integer>(); + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen1Constants.tmCount; i++) { + tms.add(moveRomToNumTable[rom[offset + (i - 1)] & 0xFF]); + } + return tms; + } + + @Override + public List<Integer> getHMMoves() { + List<Integer> hms = new ArrayList<Integer>(); + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen1Constants.hmCount; i++) { + hms.add(moveRomToNumTable[rom[offset + Gen1Constants.tmCount + (i - 1)] & 0xFF]); + } + return hms; + } + + @Override + public void setTMMoves(List<Integer> moveIndexes) { + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen1Constants.tmCount; i++) { + rom[offset + (i - 1)] = (byte) moveNumToRomTable[moveIndexes.get(i - 1)]; + } + + // Gym Leader TM Moves (RB only) + if (!romEntry.isYellow) { + int[] tms = Gen1Constants.gymLeaderTMs; + int glMovesOffset = romEntry.getValue("GymLeaderMovesTableOffset"); + for (int i = 0; i < tms.length; i++) { + // Set the special move used by gym (i+1) to + // the move we just wrote to TM tms[i] + rom[glMovesOffset + i * 2] = (byte) moveNumToRomTable[moveIndexes.get(tms[i] - 1)]; + } + } + + // TM Text + String[] moveNames = readMoveNames(); + for (TMTextEntry tte : romEntry.tmTexts) { + String moveName = moveNames[moveNumToRomTable[moveIndexes.get(tte.number - 1)]]; + String text = tte.template.replace("%m", moveName); + writeFixedLengthScriptString(text, tte.offset, lengthOfStringAt(tte.offset)); + } + } + + @Override + public int getTMCount() { + return Gen1Constants.tmCount; + } + + @Override + public int getHMCount() { + return Gen1Constants.hmCount; + } + + @Override + public Map<Pokemon, boolean[]> getTMHMCompatibility() { + Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + for (int i = 1; i <= pokedexCount; i++) { + int baseStatsOffset = (romEntry.isYellow || i != Gen1Constants.mewIndex) ? (pokeStatsOffset + (i - 1) + * Gen1Constants.baseStatsEntrySize) : romEntry.getValue("MewStatsOffset"); + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen1Constants.tmCount + Gen1Constants.hmCount + 1]; + for (int j = 0; j < 7; j++) { + readByteIntoFlags(flags, j * 8 + 1, baseStatsOffset + Gen1Constants.bsTMHMCompatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData) { + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int baseStatsOffset = (romEntry.isYellow || pkmn.number != Gen1Constants.mewIndex) ? (pokeStatsOffset + (pkmn.number - 1) + * Gen1Constants.baseStatsEntrySize) + : romEntry.getValue("MewStatsOffset"); + for (int j = 0; j < 7; j++) { + rom[baseStatsOffset + Gen1Constants.bsTMHMCompatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public boolean hasMoveTutors() { + return false; + } + + @Override + public List<Integer> getMoveTutorMoves() { + return new ArrayList<Integer>(); + } + + @Override + public void setMoveTutorMoves(List<Integer> moves) { + // Do nothing + } + + @Override + public Map<Pokemon, boolean[]> getMoveTutorCompatibility() { + return new TreeMap<Pokemon, boolean[]>(); + } + + @Override + public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData) { + // Do nothing + } + + @Override + public String getROMName() { + return "Pokemon " + romEntry.name; + } + + @Override + public String getROMCode() { + return romEntry.romName + " (" + romEntry.version + "/" + romEntry.nonJapanese + ")"; + } + + @Override + public String getSupportLevel() { + return (romEntry.getValue("StaticPokemonSupport") > 0) ? "Complete" : "No Static Pokemon"; + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); + + int pkmnCount = romEntry.getValue("InternalPokemonCount"); + for (int i = 1; i <= pkmnCount; i++) { + int pointer = readWord(pointersOffset + (i - 1) * 2); + int realPointer = calculateOffset(bankOf(pointersOffset), pointer); + if (pokeRBYToNumTable[i] != 0) { + int thisPoke = pokeRBYToNumTable[i]; + Pokemon pkmn = pokes[thisPoke]; + while (rom[realPointer] != 0) { + int method = rom[realPointer]; + EvolutionType type = EvolutionType.fromIndex(1, method); + int otherPoke = pokeRBYToNumTable[rom[realPointer + 2 + (type == EvolutionType.STONE ? 1 : 0)] & 0xFF]; + int extraInfo = rom[realPointer + 1] & 0xFF; + Evolution evo = new Evolution(pkmn, pokes[otherPoke], true, type, extraInfo); + if (!pkmn.evolutionsFrom.contains(evo)) { + pkmn.evolutionsFrom.add(evo); + pokes[otherPoke].evolutionsTo.add(evo); + } + realPointer += (type == EvolutionType.STONE ? 4 : 3); + } + // split evos don't carry stats + if (pkmn.evolutionsFrom.size() > 1) { + for (Evolution e : pkmn.evolutionsFrom) { + e.carryStats = false; + } + } + } + } + } + + @Override + public void removeTradeEvolutions(boolean changeMoveEvos) { + // Gen 1: only regular trade evos + // change them all to evolve at level 37 + log("--Removing Trade Evolutions--"); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + for (Evolution evo : pkmn.evolutionsFrom) { + if (evo.type == EvolutionType.TRADE) { + // change + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + logEvoChangeLevel(evo.from.name, evo.to.name, 37); + } + } + } + } + logBlankLine(); + } + + private List<String> getTrainerClassesForText() { + int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); + List<String> tcNames = new ArrayList<String>(); + int offset = offsets[offsets.length - 1]; + for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { + String name = readVariableLengthString(offset); + offset += (internalStringLength(name) + 1); + tcNames.add(name); + } + return tcNames; + } + + @Override + public List<String> getTrainerNames() { + int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); + List<String> trainerNames = new ArrayList<String>(); + int offset = offsets[offsets.length - 1]; + for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { + String name = readVariableLengthString(offset); + offset += (internalStringLength(name) + 1); + if (Gen1Constants.singularTrainers.contains(j)) { + trainerNames.add(name); + } + } + return trainerNames; + } + + @Override + public void setTrainerNames(List<String> trainerNames) { + if (romEntry.getValue("CanChangeTrainerText") > 0) { + int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); + Iterator<String> trainerNamesI = trainerNames.iterator(); + int offset = offsets[offsets.length - 1]; + for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { + String name = readVariableLengthString(offset); + if (Gen1Constants.singularTrainers.contains(j)) { + String newName = trainerNamesI.next(); + writeFixedLengthString(newName, offset, internalStringLength(name) + 1); + } + offset += (internalStringLength(name) + 1); + } + } + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.SAME_LENGTH; + } + + @Override + public List<Integer> getTCNameLengthsByTrainer() { + // not needed + return new ArrayList<Integer>(); + } + + @Override + public List<String> getTrainerClassNames() { + int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); + List<String> trainerClassNames = new ArrayList<String>(); + if (offsets.length == 2) { + for (int i = 0; i < offsets.length; i++) { + int offset = offsets[i]; + for (int j = 0; j < Gen1Constants.tclassesCounts[i]; j++) { + String name = readVariableLengthString(offset); + offset += (internalStringLength(name) + 1); + if (i == 0 || !Gen1Constants.singularTrainers.contains(j)) { + trainerClassNames.add(name); + } + } + } + } else { + int offset = offsets[0]; + for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { + String name = readVariableLengthString(offset); + offset += (internalStringLength(name) + 1); + if (!Gen1Constants.singularTrainers.contains(j)) { + trainerClassNames.add(name); + } + } + } + return trainerClassNames; + } + + @Override + public void setTrainerClassNames(List<String> trainerClassNames) { + if (romEntry.getValue("CanChangeTrainerText") > 0) { + int[] offsets = romEntry.arrayEntries.get("TrainerClassNamesOffsets"); + Iterator<String> tcNamesIter = trainerClassNames.iterator(); + if (offsets.length == 2) { + for (int i = 0; i < offsets.length; i++) { + int offset = offsets[i]; + for (int j = 0; j < Gen1Constants.tclassesCounts[i]; j++) { + String name = readVariableLengthString(offset); + if (i == 0 || !Gen1Constants.singularTrainers.contains(j)) { + String newName = tcNamesIter.next(); + writeFixedLengthString(newName, offset, internalStringLength(name) + 1); + } + offset += (internalStringLength(name) + 1); + } + } + } else { + int offset = offsets[0]; + for (int j = 0; j < Gen1Constants.tclassesCounts[1]; j++) { + String name = readVariableLengthString(offset); + if (!Gen1Constants.singularTrainers.contains(j)) { + String newName = tcNamesIter.next(); + writeFixedLengthString(newName, offset, internalStringLength(name) + 1); + } + offset += (internalStringLength(name) + 1); + } + } + } + + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return true; + } + + @Override + public String getDefaultExtension() { + if (((rom[GBConstants.isGBCOffset] & 0xFF) & 0x80) > 0) { + return "gbc"; + } + return "sgb"; + } + + @Override + public int abilitiesPerPokemon() { + return 0; + } + + @Override + public int highestAbilityIndex() { + return 0; + } + + @Override + public int internalStringLength(String string) { + return translateString(string).length; + } + + @Override + public int miscTweaksAvailable() { + int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); + available |= MiscTweak.UPDATE_TYPE_EFFECTIVENESS.getValue(); + + if (romEntry.tweakFiles.get("BWXPTweak") != null) { + available |= MiscTweak.BW_EXP_PATCH.getValue(); + } + if (romEntry.tweakFiles.get("XAccNerfTweak") != null) { + available |= MiscTweak.NERF_X_ACCURACY.getValue(); + } + if (romEntry.tweakFiles.get("CritRateTweak") != null) { + available |= MiscTweak.FIX_CRIT_RATE.getValue(); + } + if (romEntry.getValue("TextDelayFunctionOffset") != 0) { + available |= MiscTweak.FASTEST_TEXT.getValue(); + } + if (romEntry.getValue("PCPotionOffset") != 0) { + available |= MiscTweak.RANDOMIZE_PC_POTION.getValue(); + } + if (romEntry.getValue("PikachuEvoJumpOffset") != 0) { + available |= MiscTweak.ALLOW_PIKACHU_EVOLUTION.getValue(); + } + if (romEntry.getValue("CatchingTutorialMonOffset") != 0) { + available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); + } + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.BW_EXP_PATCH) { + applyBWEXPPatch(); + } else if (tweak == MiscTweak.NERF_X_ACCURACY) { + applyXAccNerfPatch(); + } else if (tweak == MiscTweak.FIX_CRIT_RATE) { + applyCritRatePatch(); + } else if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestTextPatch(); + } else if (tweak == MiscTweak.RANDOMIZE_PC_POTION) { + randomizePCPotion(); + } else if (tweak == MiscTweak.ALLOW_PIKACHU_EVOLUTION) { + applyPikachuEvoPatch(); + } else if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { + applyCamelCaseNames(); + } else if (tweak == MiscTweak.UPDATE_TYPE_EFFECTIVENESS) { + fixTypeEffectiveness(); + } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { + randomizeCatchingTutorial(); + } + } + + private void applyBWEXPPatch() { + genericIPSPatch("BWXPTweak"); + } + + private void applyXAccNerfPatch() { + xAccNerfed = genericIPSPatch("XAccNerfTweak"); + } + + private void applyCritRatePatch() { + genericIPSPatch("CritRateTweak"); + } + + private void applyFastestTextPatch() { + if (romEntry.getValue("TextDelayFunctionOffset") != 0) { + rom[romEntry.getValue("TextDelayFunctionOffset")] = GBConstants.gbZ80Ret; + } + } + + private void randomizePCPotion() { + if (romEntry.getValue("PCPotionOffset") != 0) { + rom[romEntry.getValue("PCPotionOffset")] = (byte) this.getNonBadItems().randomNonTM(this.random); + } + } + + private void applyPikachuEvoPatch() { + if (romEntry.getValue("PikachuEvoJumpOffset") != 0) { + rom[romEntry.getValue("PikachuEvoJumpOffset")] = GBConstants.gbZ80JumpRelative; + } + } + + private void randomizeCatchingTutorial() { + if (romEntry.getValue("CatchingTutorialMonOffset") != 0) { + rom[romEntry.getValue("CatchingTutorialMonOffset")] = (byte) pokeNumToRBYTable[this.randomPokemon().number]; + } + } + + private boolean genericIPSPatch(String ctName) { + String patchName = romEntry.tweakFiles.get(ctName); + if (patchName == null) { + return false; + } + + try { + FileFunctions.applyPatch(rom, patchName); + return true; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List<Integer> getGameBreakingMoves() { + // Sonicboom & drage & OHKO moves + // 160 add spore + // also remove OHKO if xacc nerfed + if (xAccNerfed) { + return Gen1Constants.bannedMovesWithXAccBanned; + } else { + return Gen1Constants.bannedMovesWithoutXAccBanned; + } + } + + @Override + public List<Integer> getFieldMoves() { + // cut, fly, surf, strength, flash, + // dig, teleport (NOT softboiled) + return Gen1Constants.fieldMoves; + } + + @Override + public List<Integer> getEarlyRequiredHMMoves() { + // just cut + return Gen1Constants.earlyRequiredHMs; + } + + @Override + public void applySignature() { + // First off, intro Pokemon + // 160 add yellow intro random + int introPokemon = pokeNumToRBYTable[this.randomPokemon().number]; + rom[romEntry.getValue("IntroPokemonOffset")] = (byte) introPokemon; + rom[romEntry.getValue("IntroCryOffset")] = (byte) introPokemon; + + } + + @Override + public ItemList getAllowedItems() { + return Gen1Constants.allowedItems; + } + + @Override + public ItemList getNonBadItems() { + // Gen 1 has no bad items Kappa + return Gen1Constants.allowedItems; + } + + private void loadItemNames() { + itemNames = new String[256]; + itemNames[0] = "glitch"; + // trying to emulate pretty much what the game does here + // normal items + int origOffset = romEntry.getValue("ItemNamesOffset"); + int itemNameOffset = origOffset; + for (int index = 1; index <= 0x100; index++) { + if (itemNameOffset / GBConstants.bankSize > origOffset / GBConstants.bankSize) { + // the game would continue making its merry way into VRAM here, + // but we don't have VRAM to simulate. + // just give up. + break; + } + int startOfText = itemNameOffset; + while ((rom[itemNameOffset] & 0xFF) != GBConstants.stringTerminator) { + itemNameOffset++; + } + itemNameOffset++; + itemNames[index % 256] = readFixedLengthString(startOfText, 20); + } + // hms override + for (int index = Gen1Constants.hmsStartIndex; index < Gen1Constants.tmsStartIndex; index++) { + itemNames[index] = String.format("HM%02d", index - Gen1Constants.hmsStartIndex + 1); + } + // tms override + for (int index = Gen1Constants.tmsStartIndex; index < 0x100; index++) { + itemNames[index] = String.format("TM%02d", index - Gen1Constants.tmsStartIndex + 1); + } + } + + @Override + public String[] getItemNames() { + return itemNames; + } + + private static class SubMap { + private int id; + private int addr; + private int bank; + private MapHeader header; + private Connection[] cons; + private int n_cons; + private int obj_addr; + private List<Integer> itemOffsets; + } + + private static class MapHeader { + private int tileset_id; // u8 + private int map_h, map_w; // u8 + private int map_ptr, text_ptr, script_ptr; // u16 + private int connect_byte; // u8 + // 10 bytes + } + + private static class Connection { + private int index; // u8 + private int connected_map; // u16 + private int current_map; // u16 + private int bigness; // u8 + private int map_width; // u8 + private int y_align; // u8 + private int x_align; // u8 + private int window; // u16 + // 11 bytes + } + + private void preloadMaps() { + int mapBanks = romEntry.getValue("MapBanks"); + int mapAddresses = romEntry.getValue("MapAddresses"); + + preloadMap(mapBanks, mapAddresses, 0); + } + + private void preloadMap(int mapBanks, int mapAddresses, int mapID) { + + if (maps[mapID] != null || mapID == 0xED || mapID == 0xFF) { + return; + } + + SubMap map = new SubMap(); + maps[mapID] = map; + + map.id = mapID; + map.addr = calculateOffset(rom[mapBanks + mapID] & 0xFF, readWord(mapAddresses + mapID * 2)); + map.bank = bankOf(map.addr); + + map.header = new MapHeader(); + map.header.tileset_id = rom[map.addr] & 0xFF; + map.header.map_h = rom[map.addr + 1] & 0xFF; + map.header.map_w = rom[map.addr + 2] & 0xFF; + map.header.map_ptr = calculateOffset(map.bank, readWord(map.addr + 3)); + map.header.text_ptr = calculateOffset(map.bank, readWord(map.addr + 5)); + map.header.script_ptr = calculateOffset(map.bank, readWord(map.addr + 7)); + map.header.connect_byte = rom[map.addr + 9] & 0xFF; + + int cb = map.header.connect_byte; + map.n_cons = ((cb & 8) >> 3) + ((cb & 4) >> 2) + ((cb & 2) >> 1) + (cb & 1); + + int cons_offset = map.addr + 10; + + map.cons = new Connection[map.n_cons]; + for (int i = 0; i < map.n_cons; i++) { + int tcon_offs = cons_offset + i * 11; + Connection con = new Connection(); + con.index = rom[tcon_offs] & 0xFF; + con.connected_map = readWord(tcon_offs + 1); + con.current_map = readWord(tcon_offs + 3); + con.bigness = rom[tcon_offs + 5] & 0xFF; + con.map_width = rom[tcon_offs + 6] & 0xFF; + con.y_align = rom[tcon_offs + 7] & 0xFF; + con.x_align = rom[tcon_offs + 8] & 0xFF; + con.window = readWord(tcon_offs + 9); + map.cons[i] = con; + preloadMap(mapBanks, mapAddresses, con.index); + } + map.obj_addr = calculateOffset(map.bank, readWord(cons_offset + map.n_cons * 11)); + + // Read objects + // +0 is the border tile (ignore) + // +1 is warp count + + int n_warps = rom[map.obj_addr + 1] & 0xFF; + int offs = map.obj_addr + 2; + for (int i = 0; i < n_warps; i++) { + // track this warp + int to_map = rom[offs + 3] & 0xFF; + preloadMap(mapBanks, mapAddresses, to_map); + offs += 4; + } + + // Now we're pointing to sign count + int n_signs = rom[offs++] & 0xFF; + offs += n_signs * 3; + + // Finally, entities, which contain the items + map.itemOffsets = new ArrayList<Integer>(); + int n_entities = rom[offs++] & 0xFF; + for (int i = 0; i < n_entities; i++) { + // Read text ID + int tid = rom[offs + 5] & 0xFF; + if ((tid & (1 << 6)) > 0) { + // trainer + offs += 8; + } else if ((tid & (1 << 7)) > 0 && (rom[offs + 6] != 0x00)) { + // item + map.itemOffsets.add(offs + 6); + offs += 7; + } else { + // generic + offs += 6; + } + } + } + + private void loadMapNames() { + mapNames = new String[256]; + int mapNameTableOffset = romEntry.getValue("MapNameTableOffset"); + int mapNameBank = bankOf(mapNameTableOffset); + // external names + List<Integer> usedExternal = new ArrayList<Integer>(); + for (int i = 0; i < 0x25; i++) { + int externalOffset = calculateOffset(mapNameBank, readWord(mapNameTableOffset + 1)); + usedExternal.add(externalOffset); + mapNames[i] = readVariableLengthString(externalOffset); + mapNameTableOffset += 3; + } + + // internal names + int lastMaxMap = 0x25; + Map<Integer, Integer> previousMapCounts = new HashMap<Integer, Integer>(); + while ((rom[mapNameTableOffset] & 0xFF) != 0xFF) { + int maxMap = rom[mapNameTableOffset] & 0xFF; + int nameOffset = calculateOffset(mapNameBank, readWord(mapNameTableOffset + 2)); + String actualName = readVariableLengthString(nameOffset).trim(); + if (usedExternal.contains(nameOffset)) { + for (int i = lastMaxMap; i < maxMap; i++) { + if (maps[i] != null) { + mapNames[i] = actualName + " (Building)"; + } + } + } else { + int mapCount = 0; + if (previousMapCounts.containsKey(nameOffset)) { + mapCount = previousMapCounts.get(nameOffset); + } + for (int i = lastMaxMap; i < maxMap; i++) { + if (maps[i] != null) { + mapCount++; + mapNames[i] = actualName + " (" + mapCount + ")"; + } + } + previousMapCounts.put(nameOffset, mapCount); + } + lastMaxMap = maxMap; + mapNameTableOffset += 4; + } + } + + private List<Integer> getItemOffsets() { + + List<Integer> itemOffs = new ArrayList<Integer>(); + + for (int i = 0; i < maps.length; i++) { + if (maps[i] != null) { + itemOffs.addAll(maps[i].itemOffsets); + } + } + + int hiRoutine = romEntry.getValue("HiddenItemRoutine"); + int spclTable = romEntry.getValue("SpecialMapPointerTable"); + int spclBank = bankOf(spclTable); + + if (!isYellow()) { + + int spclList = romEntry.getValue("SpecialMapList"); + + int lOffs = spclList; + int idx = 0; + + while ((rom[lOffs] & 0xFF) != 0xFF) { + + int spclOffset = calculateOffset(spclBank, readWord(spclTable + idx)); + + while ((rom[spclOffset] & 0xFF) != 0xFF) { + if (calculateOffset(rom[spclOffset + 3] & 0xFF, readWord(spclOffset + 4)) == hiRoutine) { + itemOffs.add(spclOffset + 2); + } + spclOffset += 6; + } + lOffs++; + idx += 2; + } + } else { + + int lOffs = spclTable; + + while ((rom[lOffs] & 0xFF) != 0xFF) { + + int spclOffset = calculateOffset(spclBank, readWord(lOffs + 1)); + + while ((rom[spclOffset] & 0xFF) != 0xFF) { + if (calculateOffset(rom[spclOffset + 3] & 0xFF, readWord(spclOffset + 4)) == hiRoutine) { + itemOffs.add(spclOffset + 2); + } + spclOffset += 6; + } + lOffs += 3; + } + } + + return itemOffs; + } + + @Override + public List<Integer> getRequiredFieldTMs() { + return Gen1Constants.requiredFieldTMs; + } + + @Override + public List<Integer> getCurrentFieldTMs() { + List<Integer> itemOffsets = getItemOffsets(); + List<Integer> fieldTMs = new ArrayList<Integer>(); + + for (int offset : itemOffsets) { + int itemHere = rom[offset] & 0xFF; + if (Gen1Constants.allowedItems.isTM(itemHere)) { + fieldTMs.add(itemHere - Gen1Constants.tmsStartIndex + 1); // TM + // offset + } + } + return fieldTMs; + } + + @Override + public void setFieldTMs(List<Integer> fieldTMs) { + List<Integer> itemOffsets = getItemOffsets(); + Iterator<Integer> iterTMs = fieldTMs.iterator(); + + for (int offset : itemOffsets) { + int itemHere = rom[offset] & 0xFF; + if (Gen1Constants.allowedItems.isTM(itemHere)) { + // Replace this with a TM from the list + rom[offset] = (byte) (iterTMs.next() + Gen1Constants.tmsStartIndex - 1); + } + } + } + + @Override + public List<Integer> getRegularFieldItems() { + List<Integer> itemOffsets = getItemOffsets(); + List<Integer> fieldItems = new ArrayList<Integer>(); + + for (int offset : itemOffsets) { + int itemHere = rom[offset] & 0xFF; + if (Gen1Constants.allowedItems.isAllowed(itemHere) && !(Gen1Constants.allowedItems.isTM(itemHere))) { + fieldItems.add(itemHere); + } + } + return fieldItems; + } + + @Override + public void setRegularFieldItems(List<Integer> items) { + List<Integer> itemOffsets = getItemOffsets(); + Iterator<Integer> iterItems = items.iterator(); + + for (int offset : itemOffsets) { + int itemHere = rom[offset] & 0xFF; + if (Gen1Constants.allowedItems.isAllowed(itemHere) && !(Gen1Constants.allowedItems.isTM(itemHere))) { + // Replace it + rom[offset] = (byte) (iterItems.next().intValue()); + } + } + + } + + @Override + public List<IngameTrade> getIngameTrades() { + List<IngameTrade> trades = new ArrayList<IngameTrade>(); + + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int nicknameLength = romEntry.getValue("TradeNameLength"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = nicknameLength + 3; + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = new IngameTrade(); + int entryOffset = tableOffset + entry * entryLength; + trade.requestedPokemon = pokes[pokeRBYToNumTable[rom[entryOffset] & 0xFF]]; + trade.givenPokemon = pokes[pokeRBYToNumTable[rom[entryOffset + 1] & 0xFF]]; + trade.nickname = readString(entryOffset + 3, nicknameLength); + trades.add(trade); + } + + return trades; + } + + @Override + public void setIngameTrades(List<IngameTrade> trades) { + + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int nicknameLength = romEntry.getValue("TradeNameLength"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = nicknameLength + 3; + int tradeOffset = 0; + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = trades.get(tradeOffset++); + int entryOffset = tableOffset + entry * entryLength; + rom[entryOffset] = (byte) pokeNumToRBYTable[trade.requestedPokemon.number]; + rom[entryOffset + 1] = (byte) pokeNumToRBYTable[trade.givenPokemon.number]; + if (romEntry.getValue("CanChangeTrainerText") > 0) { + writeFixedLengthString(trade.nickname, entryOffset + 3, nicknameLength); + } + } + } + + @Override + public boolean hasDVs() { + return true; + } + + @Override + public int generationOfPokemon() { + return 1; + } + + @Override + public void removeEvosForPokemonPool() { + // gen1 doesn't have this functionality anyway + } + + @Override + public boolean supportsFourStartingMoves() { + return true; + } + + private void writeEvosAndMovesLearnt(boolean writeEvos, Map<Pokemon, List<MoveLearnt>> movesets) { + // we assume a few things here: + // 1) evos & moves learnt are stored directly after their pointer table + // 2) PokemonMovesetsExtraSpaceOffset is in the same bank, and + // points to the start of the free space at the end of the bank + // (if set to 0, disabled from being used) + // 3) PokemonMovesetsDataSize is from the start of actual data to + // the start of engine/battle/e_2.asm in pokered (aka code we can't + // overwrite) + // it appears that in yellow, this code is moved + // so we can write the evos/movesets in one continuous block + // until the end of the bank. + // so for yellow, extraspace is disabled. + // specify null to either argument to copy old values + int pokeStatsOffset = romEntry.getValue("PokemonStatsOffset"); + int movesEvosStart = romEntry.getValue("PokemonMovesetsTableOffset"); + int movesEvosBank = bankOf(movesEvosStart); + int pkmnCount = romEntry.getValue("InternalPokemonCount"); + byte[] pointerTable = new byte[pkmnCount * 2]; + int mainDataBlockSize = romEntry.getValue("PokemonMovesetsDataSize"); + int mainDataBlockOffset = movesEvosStart + pointerTable.length; + byte[] mainDataBlock = new byte[mainDataBlockSize]; + int offsetInMainData = 0; + int extraSpaceOffset = romEntry.getValue("PokemonMovesetsExtraSpaceOffset"); + int extraSpaceBank = bankOf(extraSpaceOffset); + boolean extraSpaceEnabled = false; + byte[] extraDataBlock = null; + int offsetInExtraData = 0; + int extraSpaceSize = 0; + if (movesEvosBank == extraSpaceBank && extraSpaceOffset != 0) { + extraSpaceEnabled = true; + int startOfNextBank = ((extraSpaceOffset / GBConstants.bankSize) + 1) * GBConstants.bankSize; + extraSpaceSize = startOfNextBank - extraSpaceOffset; + extraDataBlock = new byte[extraSpaceSize]; + } + int nullEntryPointer = -1; + + for (int i = 1; i <= pkmnCount; i++) { + byte[] writeData = null; + int oldDataOffset = calculateOffset(movesEvosBank, readWord(movesEvosStart + (i - 1) * 2)); + boolean setNullEntryPointerHere = false; + if (pokeRBYToNumTable[i] == 0) { + // null entry + if (nullEntryPointer == -1) { + // make the null entry + writeData = new byte[] { 0, 0 }; + setNullEntryPointerHere = true; + } else { + writeWord(pointerTable, (i - 1) * 2, nullEntryPointer); + } + } else { + int pokeNum = pokeRBYToNumTable[i]; + Pokemon pkmn = pokes[pokeNum]; + ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); + // Evolutions + if (!writeEvos) { + // copy old + int evoOffset = oldDataOffset; + while (rom[evoOffset] != 0x00) { + int method = rom[evoOffset] & 0xFF; + int limiter = (method == 2) ? 4 : 3; + for (int b = 0; b < limiter; b++) { + dataStream.write(rom[evoOffset++] & 0xFF); + } + } + } else { + for (Evolution evo : pkmn.evolutionsFrom) { + // write evos for this poke + dataStream.write(evo.type.toIndex(1)); + if (evo.type == EvolutionType.LEVEL) { + dataStream.write(evo.extraInfo); // min lvl + } else if (evo.type == EvolutionType.STONE) { + dataStream.write(evo.extraInfo); // stone item + dataStream.write(1); // minimum level + } else if (evo.type == EvolutionType.TRADE) { + dataStream.write(1); // minimum level + } + int pokeIndexTo = pokeNumToRBYTable[evo.to.number]; + dataStream.write(pokeIndexTo); // species + } + } + // write terminator for evos + dataStream.write(0); + + // Movesets + if (movesets == null) { + // copy old + int movesOffset = oldDataOffset; + // move past evos + while (rom[movesOffset] != 0x00) { + int method = rom[movesOffset] & 0xFF; + movesOffset += (method == 2) ? 4 : 3; + } + movesOffset++; + // copy moves + while (rom[movesOffset] != 0x00) { + dataStream.write(rom[movesOffset++] & 0xFF); + dataStream.write(rom[movesOffset++] & 0xFF); + } + } else { + List<MoveLearnt> ourMoves = movesets.get(pkmn); + int statsOffset = 0; + if (pokeNum == Gen1Constants.mewIndex && !romEntry.isYellow) { + // Mewww + statsOffset = romEntry.getValue("MewStatsOffset"); + } else { + statsOffset = (pokeNum - 1) * Gen1Constants.baseStatsEntrySize + pokeStatsOffset; + } + int movenum = 0; + while (movenum < 4 && ourMoves.size() > movenum && ourMoves.get(movenum).level == 1) { + rom[statsOffset + Gen1Constants.bsLevel1MovesOffset + movenum] = (byte) moveNumToRomTable[ourMoves + .get(movenum).move]; + movenum++; + } + // Write out the rest of zeroes + for (int mn = movenum; mn < 4; mn++) { + rom[statsOffset + Gen1Constants.bsLevel1MovesOffset + mn] = 0; + } + // Add the non level 1 moves to the data stream + while (movenum < ourMoves.size()) { + dataStream.write(ourMoves.get(movenum).level); + dataStream.write(moveNumToRomTable[ourMoves.get(movenum).move]); + movenum++; + } + } + // terminator + dataStream.write(0); + + // done, set writeData + writeData = dataStream.toByteArray(); + try { + dataStream.close(); + } catch (IOException e) { + } + } + + // write data and set pointer? + if (writeData != null) { + int lengthToFit = writeData.length; + int pointerToWrite = -1; + // compression of leading & trailing 0s: + // every entry ends in a 0 (end of move list). + // if a block already has data in it, and the data + // we want to write starts with a 0 (no evolutions) + // we can compress it into the end of the last entry + // this saves a decent amount of space overall. + if ((offsetInMainData + lengthToFit <= mainDataBlockSize) + || (writeData[0] == 0 && offsetInMainData > 0 && offsetInMainData + lengthToFit == mainDataBlockSize + 1)) { + // place in main storage + if (writeData[0] == 0 && offsetInMainData > 0) { + int writtenDataOffset = mainDataBlockOffset + offsetInMainData - 1; + pointerToWrite = makeGBPointer(writtenDataOffset); + System.arraycopy(writeData, 1, mainDataBlock, offsetInMainData, lengthToFit - 1); + offsetInMainData += lengthToFit - 1; + } else { + int writtenDataOffset = mainDataBlockOffset + offsetInMainData; + pointerToWrite = makeGBPointer(writtenDataOffset); + System.arraycopy(writeData, 0, mainDataBlock, offsetInMainData, lengthToFit); + offsetInMainData += lengthToFit; + } + } else if (extraSpaceEnabled + && ((offsetInExtraData + lengthToFit <= extraSpaceSize) || (writeData[0] == 0 + && offsetInExtraData > 0 && offsetInExtraData + lengthToFit == extraSpaceSize + 1))) { + // place in extra space + if (writeData[0] == 0 && offsetInExtraData > 0) { + int writtenDataOffset = extraSpaceOffset + offsetInExtraData - 1; + pointerToWrite = makeGBPointer(writtenDataOffset); + System.arraycopy(writeData, 1, extraDataBlock, offsetInExtraData, lengthToFit - 1); + offsetInExtraData += lengthToFit - 1; + } else { + int writtenDataOffset = extraSpaceOffset + offsetInExtraData; + pointerToWrite = makeGBPointer(writtenDataOffset); + System.arraycopy(writeData, 0, extraDataBlock, offsetInExtraData, lengthToFit); + offsetInExtraData += lengthToFit; + } + } else { + // this should never happen, but if not, uh oh + throw new RuntimeException("Unable to save moves/evolutions, out of space"); + } + if (pointerToWrite >= 0) { + writeWord(pointerTable, (i - 1) * 2, pointerToWrite); + if (setNullEntryPointerHere) { + nullEntryPointer = pointerToWrite; + } + } + } + } + + // Done, write final results to ROM + System.arraycopy(pointerTable, 0, rom, movesEvosStart, pointerTable.length); + System.arraycopy(mainDataBlock, 0, rom, mainDataBlockOffset, mainDataBlock.length); + if (extraSpaceEnabled) { + System.arraycopy(extraDataBlock, 0, rom, extraSpaceOffset, extraDataBlock.length); + } + } + + @Override + public BufferedImage getMascotImage() { + Pokemon mascot = randomPokemon(); + int idx = pokeNumToRBYTable[mascot.number]; + int fsBank; + // define (by index number) the bank that a pokemon's image is in + // using pokered code + if (mascot.number == 151 && !romEntry.isYellow) { + // Mew + fsBank = 1; + } else if (idx < 0x1F) { + fsBank = 0x9; + } else if (idx < 0x4A) { + fsBank = 0xA; + } else if (idx < 0x74) { + fsBank = 0xB; + } else if (idx < 0x99) { + fsBank = 0xC; + } else { + fsBank = 0xD; + } + + int fsOffset = calculateOffset(fsBank, mascot.frontSpritePointer); + Gen1Decmp mscSprite = new Gen1Decmp(rom, fsOffset); + mscSprite.decompress(); + mscSprite.transpose(); + int w = mscSprite.getWidth(); + int h = mscSprite.getHeight(); + + // Palette? + int[] palette; + if (romEntry.getValue("MonPaletteIndicesOffset") > 0 && romEntry.getValue("SGBPalettesOffset") > 0) { + int palIndex = rom[romEntry.getValue("MonPaletteIndicesOffset") + mascot.number] & 0xFF; + int palOffset = romEntry.getValue("SGBPalettesOffset") + palIndex * 8; + palette = new int[4]; + for (int i = 0; i < 4; i++) { + palette[i] = GFXFunctions.conv16BitColorToARGB(readWord(palOffset + i * 2)); + } + } else { + palette = new int[] { 0xFFFFFFFF, 0xFFAAAAAA, 0xFF666666, 0xFF000000 }; + } + + byte[] data = mscSprite.getFlattenedData(); + + BufferedImage bim = GFXFunctions.drawTiledImage(data, palette, w, h, 8); + + return bim; + } } diff --git a/src/com/dabomstew/pkrandom/romhandlers/Gen2RomHandler.java b/src/com/dabomstew/pkrandom/romhandlers/Gen2RomHandler.java index 30d74f1..8b65190 100755 --- a/src/com/dabomstew/pkrandom/romhandlers/Gen2RomHandler.java +++ b/src/com/dabomstew/pkrandom/romhandlers/Gen2RomHandler.java @@ -64,2380 +64,2266 @@ import compressors.Gen2Decmp; public class Gen2RomHandler extends AbstractGBRomHandler { - public static class Factory extends RomHandler.Factory { - - @Override - public Gen2RomHandler create(Random random, PrintStream logStream) { - return new Gen2RomHandler(random, logStream); - } - - public boolean isLoadable(String filename) { - long fileLength = new File(filename).length(); - if (fileLength > 8 * 1024 * 1024) { - return false; - } - byte[] loaded = loadFilePartial(filename, 0x1000); - if (loaded.length == 0) { - // nope - return false; - } - return detectRomInner(loaded, (int) fileLength); - } - } - - public Gen2RomHandler(Random random) { - super(random, null); - } - - public Gen2RomHandler(Random random, PrintStream logStream) { - super(random, logStream); - } - - private static class RomEntry { - private String name; - private String romCode; - private int version, nonJapanese; - private String extraTableFile; - private boolean isCrystal; - private int crcInHeader = -1; - private Map<String, String> codeTweaks = new HashMap<String, String>(); - private List<TMTextEntry> tmTexts = new ArrayList<TMTextEntry>(); - private Map<String, Integer> entries = new HashMap<String, Integer>(); - private Map<String, int[]> arrayEntries = new HashMap<String, int[]>(); - private List<Integer> staticPokemonSingle = new ArrayList<Integer>(); - private Map<Integer, Integer> staticPokemonGameCorner = new TreeMap<Integer, Integer>(); - private Map<Integer, Integer> staticPokemonCopy = new TreeMap<Integer, Integer>(); - - private int getValue(String key) { - if (!entries.containsKey(key)) { - entries.put(key, 0); - } - return entries.get(key); - } - } - - private static class TMTextEntry { - private int number; - private int offset; - private String template; - } - - private static List<RomEntry> roms; - - static { - loadROMInfo(); - } - - private static void loadROMInfo() { - roms = new ArrayList<RomEntry>(); - RomEntry current = null; - try { - Scanner sc = new Scanner( - FileFunctions.openConfig("gen2_offsets.ini"), "UTF-8"); - while (sc.hasNextLine()) { - String q = sc.nextLine().trim(); - if (q.contains("//")) { - q = q.substring(0, q.indexOf("//")).trim(); - } - if (!q.isEmpty()) { - if (q.startsWith("[") && q.endsWith("]")) { - // New rom - current = new RomEntry(); - current.name = q.substring(1, q.length() - 1); - roms.add(current); - } else { - String[] r = q.split("=", 2); - if (r.length == 1) { - System.err.println("invalid entry " + q); - continue; - } - if (r[1].endsWith("\r\n")) { - r[1] = r[1].substring(0, r[1].length() - 2); - } - r[1] = r[1].trim(); - r[0] = r[0].trim(); - // Static Pokemon? - if (r[0].equals("StaticPokemonGameCorner[]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - if (offsets.length != 2) { - continue; - } - int[] offs = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - offs[c++] = parseRIInt(off); - } - current.staticPokemonGameCorner.put(offs[0], - offs[1]); - } else if (r[0].equals("StaticPokemonCopy[]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - if (offsets.length != 2) { - continue; - } - int[] offs = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - offs[c++] = parseRIInt(off); - } - current.staticPokemonCopy.put(offs[0], offs[1]); - } else if (r[0].equals("TMText[]")) { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] parts = r[1].substring(1, - r[1].length() - 1).split(",", 3); - TMTextEntry tte = new TMTextEntry(); - tte.number = parseRIInt(parts[0]); - tte.offset = parseRIInt(parts[1]); - tte.template = parts[2]; - current.tmTexts.add(tte); - } - } else if (r[0].equals("Game")) { - current.romCode = r[1]; - } else if (r[0].equals("Version")) { - current.version = parseRIInt(r[1]); - } else if (r[0].equals("NonJapanese")) { - current.nonJapanese = parseRIInt(r[1]); - } else if (r[0].equals("Type")) { - if (r[1].equalsIgnoreCase("Crystal")) { - current.isCrystal = true; - } else { - current.isCrystal = false; - } - } else if (r[0].equals("ExtraTableFile")) { - current.extraTableFile = r[1]; - } else if (r[0].equals("CRCInHeader")) { - current.crcInHeader = parseRIInt(r[1]); - } else if (r[0].endsWith("Tweak")) { - current.codeTweaks.put(r[0], r[1]); - } else if (r[0].equals("CopyFrom")) { - for (RomEntry otherEntry : roms) { - if (r[1].equalsIgnoreCase(otherEntry.name)) { - // copy from here - boolean cSP = (current - .getValue("CopyStaticPokemon") == 1); - boolean cTT = (current - .getValue("CopyTMText") == 1); - current.arrayEntries - .putAll(otherEntry.arrayEntries); - current.entries.putAll(otherEntry.entries); - if (cSP) { - current.staticPokemonSingle - .addAll(otherEntry.staticPokemonSingle); - current.staticPokemonGameCorner - .putAll(otherEntry.staticPokemonGameCorner); - current.staticPokemonCopy - .putAll(otherEntry.staticPokemonCopy); - current.entries.put( - "StaticPokemonSupport", 1); - } else { - current.entries.put( - "StaticPokemonSupport", 0); - } - if (cTT) { - current.tmTexts - .addAll(otherEntry.tmTexts); - } - current.extraTableFile = otherEntry.extraTableFile; - } - } - } else { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - if (offsets.length == 1 - && offsets[0].trim().isEmpty()) { - current.arrayEntries.put(r[0], new int[0]); - } else { - int[] offs = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - offs[c++] = parseRIInt(off); - } - if (r[0].startsWith("StaticPokemon")) { - for (int off : offs) { - current.staticPokemonSingle - .add(off); - } - } else { - current.arrayEntries.put(r[0], offs); - } - } - } else { - int offs = parseRIInt(r[1]); - current.entries.put(r[0], offs); - } - } - } - } - } - sc.close(); - } catch (FileNotFoundException e) { - } - - } - - 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; - } - } - - // This ROM's data - private Pokemon[] pokes; - private List<Pokemon> pokemonList; - private RomEntry romEntry; - private Move[] moves; - private String[] tb; - private Map<String, Byte> d; - private int longestTableToken; - private boolean havePatchedFleeing; - private String[] itemNames; - private List<Integer> itemOffs; - private String[][] mapNames; - private String[] landmarkNames; - private boolean isVietCrystal; - - @Override - public boolean detectRom(byte[] rom) { - return detectRomInner(rom, rom.length); - } - - private static boolean detectRomInner(byte[] rom, int romSize) { - if (romSize < GBConstants.minRomSize - || romSize > GBConstants.maxRomSize) { - return false; // size check - } - return checkRomEntry(rom) != null; // so it's OK if it's a valid ROM - } - - @Override - public void loadedRom() { - romEntry = checkRomEntry(this.rom); - tb = new String[256]; - d = new HashMap<String, Byte>(); - clearTextTables(); - readTextTable("gameboy_jap"); - if (romEntry.extraTableFile != null - && romEntry.extraTableFile.equalsIgnoreCase("none") == false) { - readTextTable(romEntry.extraTableFile); - } - // VietCrystal override - if (romEntry.name.equals("Crystal (J)") - && rom[Gen2Constants.vietCrystalCheckOffset] == Gen2Constants.vietCrystalCheckValue) { - readTextTable("vietcrystal"); - isVietCrystal = true; - } else { - isVietCrystal = false; - } - havePatchedFleeing = false; - loadPokemonStats(); - pokemonList = Arrays.asList(pokes); - loadMoves(); - loadLandmarkNames(); - preprocessMaps(); - loadItemNames(); - } - - private static RomEntry checkRomEntry(byte[] rom) { - int version = rom[GBConstants.versionOffset] & 0xFF; - int nonjap = rom[GBConstants.jpFlagOffset] & 0xFF; - // Check for specific CRC first - int crcInHeader = ((rom[GBConstants.crcOffset] & 0xFF) << 8) - | (rom[GBConstants.crcOffset + 1] & 0xFF); - for (RomEntry re : roms) { - if (romSig(rom, re.romCode) && re.version == version - && re.nonJapanese == nonjap - && re.crcInHeader == crcInHeader) { - return re; - } - } - // Now check for non-specific-CRC entries - for (RomEntry re : roms) { - if (romSig(rom, re.romCode) && re.version == version - && re.nonJapanese == nonjap && re.crcInHeader == -1) { - return re; - } - } - // Not found - return null; - } - - private void clearTextTables() { - tb = new String[256]; - d.clear(); - longestTableToken = 0; - } - - private void readTextTable(String name) { - try { - Scanner sc = new Scanner(FileFunctions.openConfig(name + ".tbl"), - "UTF-8"); - while (sc.hasNextLine()) { - String q = sc.nextLine(); - if (!q.trim().isEmpty()) { - String[] r = q.split("=", 2); - if (r[1].endsWith("\r\n")) { - r[1] = r[1].substring(0, r[1].length() - 2); - } - int hexcode = Integer.parseInt(r[0], 16); - if (tb[hexcode] != null) { - String oldMatch = tb[hexcode]; - tb[hexcode] = null; - d.remove(oldMatch); - } - tb[hexcode] = r[1]; - longestTableToken = Math.max(longestTableToken, - r[1].length()); - d.put(r[1], (byte) hexcode); - } - } - sc.close(); - } catch (FileNotFoundException e) { - } - - } - - @Override - public void savingRom() { - savePokemonStats(); - saveMoves(); - } - - private void loadPokemonStats() { - pokes = new Pokemon[Gen2Constants.pokemonCount + 1]; - // Fetch our names - String[] pokeNames = readPokemonNames(); - int offs = romEntry.getValue("PokemonStatsOffset"); - // Get base stats - for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { - pokes[i] = new Pokemon(); - pokes[i].number = i; - loadBasicPokeStats(pokes[i], offs + (i - 1) - * Gen2Constants.baseStatsEntrySize); - // Name? - pokes[i].name = pokeNames[i]; - } - - // Get evolutions - populateEvolutions(); - - } - - private void savePokemonStats() { - // Write pokemon names - int offs = romEntry.getValue("PokemonNamesOffset"); - int len = romEntry.getValue("PokemonNamesLength"); - for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { - int stringOffset = offs + (i - 1) * len; - writeFixedLengthString(pokes[i].name, stringOffset, len); - } - // Write pokemon stats - int offs2 = romEntry.getValue("PokemonStatsOffset"); - for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { - saveBasicPokeStats(pokes[i], offs2 + (i - 1) - * Gen2Constants.baseStatsEntrySize); - } - // Write evolutions - writeEvosAndMovesLearnt(true, null); - } - - private String[] readMoveNames() { - int offset = romEntry.getValue("MoveNamesOffset"); - String[] moveNames = new String[Gen2Constants.moveCount + 1]; - for (int i = 1; i <= Gen2Constants.moveCount; i++) { - moveNames[i] = readVariableLengthString(offset); - offset += lengthOfStringAt(offset) + 1; - } - return moveNames; - } - - private void loadMoves() { - moves = new Move[Gen2Constants.moveCount + 1]; - String[] moveNames = readMoveNames(); - int offs = romEntry.getValue("MoveDataOffset"); - for (int i = 1; i <= Gen2Constants.moveCount; i++) { - moves[i] = new Move(); - moves[i].name = moveNames[i]; - moves[i].number = i; - moves[i].internalId = i; - moves[i].effectIndex = rom[offs + (i - 1) * 7] & 0xFF; - moves[i].hitratio = ((rom[offs + (i - 1) * 7 + 3] & 0xFF) + 0) / 255.0 * 100; - moves[i].power = rom[offs + (i - 1) * 7 + 1] & 0xFF; - moves[i].pp = rom[offs + (i - 1) * 7 + 4] & 0xFF; - moves[i].type = Gen2Constants.typeTable[rom[offs + (i - 1) * 7 + 2]]; - } - - } - - private void saveMoves() { - int offs = romEntry.getValue("MoveDataOffset"); - for (int i = 1; i <= 251; i++) { - rom[offs + (i - 1) * 7] = (byte) moves[i].effectIndex; - rom[offs + (i - 1) * 7 + 1] = (byte) moves[i].power; - rom[offs + (i - 1) * 7 + 2] = Gen2Constants - .typeToByte(moves[i].type); - int hitratio = (int) Math.round(moves[i].hitratio * 2.55); - if (hitratio < 0) { - hitratio = 0; - } - if (hitratio > 255) { - hitratio = 255; - } - rom[offs + (i - 1) * 7 + 3] = (byte) hitratio; - rom[offs + (i - 1) * 7 + 4] = (byte) moves[i].pp; - } - } - - public List<Move> getMoves() { - return Arrays.asList(moves); - } - - private void loadBasicPokeStats(Pokemon pkmn, int offset) { - pkmn.hp = rom[offset + Gen2Constants.bsHPOffset] & 0xFF; - pkmn.attack = rom[offset + Gen2Constants.bsAttackOffset] & 0xFF; - pkmn.defense = rom[offset + Gen2Constants.bsDefenseOffset] & 0xFF; - pkmn.speed = rom[offset + Gen2Constants.bsSpeedOffset] & 0xFF; - pkmn.spatk = rom[offset + Gen2Constants.bsSpAtkOffset] & 0xFF; - pkmn.spdef = rom[offset + Gen2Constants.bsSpDefOffset] & 0xFF; - // Type - pkmn.primaryType = Gen2Constants.typeTable[rom[offset - + Gen2Constants.bsPrimaryTypeOffset] & 0xFF]; - pkmn.secondaryType = Gen2Constants.typeTable[rom[offset - + Gen2Constants.bsSecondaryTypeOffset] & 0xFF]; - // Only one type? - if (pkmn.secondaryType == pkmn.primaryType) { - pkmn.secondaryType = null; - } - pkmn.catchRate = rom[offset + Gen2Constants.bsCatchRateOffset] & 0xFF; - pkmn.guaranteedHeldItem = -1; - pkmn.commonHeldItem = rom[offset + Gen2Constants.bsCommonHeldItemOffset] & 0xFF; - pkmn.rareHeldItem = rom[offset + Gen2Constants.bsRareHeldItemOffset] & 0xFF; - pkmn.darkGrassHeldItem = -1; - pkmn.growthCurve = ExpCurve.fromByte(rom[offset - + Gen2Constants.bsGrowthCurveOffset]); - pkmn.picDimensions = rom[offset + Gen2Constants.bsPicDimensionsOffset] & 0xFF; - - } - - private void saveBasicPokeStats(Pokemon pkmn, int offset) { - rom[offset + Gen2Constants.bsHPOffset] = (byte) pkmn.hp; - rom[offset + Gen2Constants.bsAttackOffset] = (byte) pkmn.attack; - rom[offset + Gen2Constants.bsDefenseOffset] = (byte) pkmn.defense; - rom[offset + Gen2Constants.bsSpeedOffset] = (byte) pkmn.speed; - rom[offset + Gen2Constants.bsSpAtkOffset] = (byte) pkmn.spatk; - rom[offset + Gen2Constants.bsSpDefOffset] = (byte) pkmn.spdef; - rom[offset + Gen2Constants.bsPrimaryTypeOffset] = Gen2Constants - .typeToByte(pkmn.primaryType); - if (pkmn.secondaryType == null) { - rom[offset + Gen2Constants.bsSecondaryTypeOffset] = rom[offset - + Gen2Constants.bsPrimaryTypeOffset]; - } else { - rom[offset + Gen2Constants.bsSecondaryTypeOffset] = Gen2Constants - .typeToByte(pkmn.secondaryType); - } - rom[offset + Gen2Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; - - rom[offset + Gen2Constants.bsCommonHeldItemOffset] = (byte) pkmn.commonHeldItem; - rom[offset + Gen2Constants.bsRareHeldItemOffset] = (byte) pkmn.rareHeldItem; - rom[offset + Gen2Constants.bsGrowthCurveOffset] = pkmn.growthCurve - .toByte(); - } - - private String[] readPokemonNames() { - int offs = romEntry.getValue("PokemonNamesOffset"); - int len = romEntry.getValue("PokemonNamesLength"); - String[] names = new String[Gen2Constants.pokemonCount + 1]; - for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { - names[i] = readFixedLengthString(offs + (i - 1) * len, len); - } - return names; - } - - private String readString(int offset, int maxLength) { - StringBuilder string = new StringBuilder(); - for (int c = 0; c < maxLength; c++) { - int currChar = rom[offset + c] & 0xFF; - if (tb[currChar] != null) { - string.append(tb[currChar]); - } else { - if (currChar == GBConstants.stringTerminator - || currChar == GBConstants.stringNull) { - break; - } else { - string.append("\\x" + String.format("%02X", currChar)); - } - } - } - return string.toString(); - } - - public byte[] translateString(String text) { - List<Byte> data = new ArrayList<Byte>(); - while (text.length() != 0) { - int i = Math.max(0, longestTableToken - text.length()); - if (text.charAt(0) == '\\' && text.charAt(1) == 'x') { - data.add((byte) Integer.parseInt(text.substring(2, 4), 16)); - text = text.substring(4); - } else { - while (!(d - .containsKey(text.substring(0, longestTableToken - i)) || (i == longestTableToken))) { - i++; - } - if (i == longestTableToken) { - text = text.substring(1); - } else { - data.add(d.get(text.substring(0, longestTableToken - i))); - text = text.substring(longestTableToken - i); - } - } - } - byte[] ret = new byte[data.size()]; - for (int i = 0; i < ret.length; i++) { - ret[i] = data.get(i); - } - return ret; - } - - private String readFixedLengthString(int offset, int length) { - return readString(offset, length); - } - - public String readVariableLengthString(int offset) { - return readString(offset, Integer.MAX_VALUE); - } - - public String readVariableLengthScriptString(int offset) { - return readString(offset, Integer.MAX_VALUE); - } - - public byte[] traduire(String str) { - return translateString(str); - } - - private void writeFixedLengthString(String str, int offset, int length) { - byte[] translated = translateString(str); - int len = Math.min(translated.length, length); - System.arraycopy(translated, 0, rom, offset, len); - while (len < length) { - rom[offset + len] = GBConstants.stringTerminator; - len++; - } - } - - private void writeFixedLengthScriptString(String str, int offset, int length) { - byte[] translated = translateString(str); - int len = Math.min(translated.length, length); - System.arraycopy(translated, 0, rom, offset, len); - while (len < length) { - rom[offset + len] = GBConstants.stringNull; - len++; - } - } - - private int lengthOfStringAt(int offset) { - int len = 0; - while (rom[offset + len] != GBConstants.stringTerminator - && rom[offset + len] != GBConstants.stringNull) { - len++; - } - return len; - } - - private int makeGBPointer(int offset) { - if (offset < GBConstants.bankSize) { - return offset; - } else { - return (offset % GBConstants.bankSize) + GBConstants.bankSize; - } - } - - private int bankOf(int offset) { - return (offset / GBConstants.bankSize); - } - - private int calculateOffset(int bank, int pointer) { - if (pointer < GBConstants.bankSize) { - return pointer; - } else { - return (pointer % GBConstants.bankSize) + bank - * GBConstants.bankSize; - } - } - - private static boolean romSig(byte[] rom, String sig) { - try { - int sigOffset = GBConstants.romCodeOffset; - byte[] sigBytes = sig.getBytes("US-ASCII"); - for (int i = 0; i < sigBytes.length; i++) { - if (rom[sigOffset + i] != sigBytes[i]) { - return false; - } - } - return true; - } catch (UnsupportedEncodingException ex) { - return false; - } - - } - - @Override - public List<Pokemon> getStarters() { - // Get the starters - List<Pokemon> starters = new ArrayList<Pokemon>(); - starters.add(pokes[rom[romEntry.arrayEntries.get("StarterOffsets1")[0]] & 0xFF]); - starters.add(pokes[rom[romEntry.arrayEntries.get("StarterOffsets2")[0]] & 0xFF]); - starters.add(pokes[rom[romEntry.arrayEntries.get("StarterOffsets3")[0]] & 0xFF]); - return starters; - } - - @Override - public boolean setStarters(List<Pokemon> newStarters) { - if (newStarters.size() != 3) { - return false; - } - - // Actually write - - for (int i = 0; i < 3; i++) { - byte starter = (byte) newStarters.get(i).number; - int[] offsets = romEntry.arrayEntries.get("StarterOffsets" - + (i + 1)); - for (int offset : offsets) { - rom[offset] = starter; - } - } - - // Attempt to replace text - if (romEntry.getValue("CanChangeStarterText") > 0) { - List<Integer> cyndaTexts = RomFunctions.search(rom, - traduire(Gen2Constants.starterNames[0])); - int offset = cyndaTexts.get(romEntry.isCrystal ? 1 : 0); - String pokeName = newStarters.get(0).name; - writeFixedLengthScriptString(pokeName + "?\\e", offset, - lengthOfStringAt(offset) + 1); - - List<Integer> totoTexts = RomFunctions.search(rom, - traduire(Gen2Constants.starterNames[1])); - offset = totoTexts.get(romEntry.isCrystal ? 1 : 0); - pokeName = newStarters.get(1).name; - writeFixedLengthScriptString(pokeName + "?\\e", offset, - lengthOfStringAt(offset) + 1); - - List<Integer> chikoTexts = RomFunctions.search(rom, - traduire(Gen2Constants.starterNames[2])); - offset = chikoTexts.get(romEntry.isCrystal ? 1 : 0); - pokeName = newStarters.get(2).name; - writeFixedLengthScriptString(pokeName + "?\\e", offset, - lengthOfStringAt(offset) + 1); - } - return true; - } - - @Override - public List<Integer> getStarterHeldItems() { - List<Integer> sHeldItems = new ArrayList<Integer>(); - int[] shiOffsets = romEntry.arrayEntries.get("StarterHeldItems"); - for (int offset : shiOffsets) { - sHeldItems.add(rom[offset] & 0xFF); - } - return sHeldItems; - } - - @Override - public void setStarterHeldItems(List<Integer> items) { - int[] shiOffsets = romEntry.arrayEntries.get("StarterHeldItems"); - if (items.size() != shiOffsets.length) { - return; - } - Iterator<Integer> sHeldItems = items.iterator(); - for (int offset : shiOffsets) { - rom[offset] = sHeldItems.next().byteValue(); - } - } - - @Override - public void shufflePokemonStats() { - for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { - pokes[i].shuffleStats(this.random); - } - } - - @Override - public List<EncounterSet> getEncounters(boolean useTimeOfDay) { - int offset = romEntry.getValue("WildPokemonOffset"); - List<EncounterSet> areas = new ArrayList<EncounterSet>(); - offset = readLandEncounters(offset, areas, useTimeOfDay); // Johto - offset = readSeaEncounters(offset, areas); // Johto - offset = readLandEncounters(offset, areas, useTimeOfDay); // Kanto - offset = readSeaEncounters(offset, areas); // Kanto - offset = readLandEncounters(offset, areas, useTimeOfDay); // Specials - offset = readSeaEncounters(offset, areas); // Specials - - // Fishing Data - offset = romEntry.getValue("FishingWildsOffset"); - int rootOffset = offset; - for (int k = 0; k < Gen2Constants.fishingGroupCount; k++) { - EncounterSet es = new EncounterSet(); - es.displayName = "Fishing Group " + (k + 1); - for (int i = 0; i < Gen2Constants.pokesPerFishingGroup; i++) { - offset++; - int pokeNum = rom[offset++] & 0xFF; - int level = rom[offset++] & 0xFF; - if (pokeNum == 0) { - if (!useTimeOfDay) { - // read the encounter they put here for DAY - int specialOffset = rootOffset - + Gen2Constants.fishingGroupEntryLength - * Gen2Constants.pokesPerFishingGroup - * Gen2Constants.fishingGroupCount + level * 4 - + 2; - Encounter enc = new Encounter(); - enc.pokemon = pokes[rom[specialOffset] & 0xFF]; - enc.level = rom[specialOffset + 1] & 0xFF; - es.encounters.add(enc); - } - // else will be handled by code below - } else { - Encounter enc = new Encounter(); - enc.pokemon = pokes[pokeNum]; - enc.level = level; - es.encounters.add(enc); - } - } - areas.add(es); - } - if (useTimeOfDay) { - for (int k = 0; k < Gen2Constants.timeSpecificFishingGroupCount; k++) { - EncounterSet es = new EncounterSet(); - es.displayName = "Time-Specific Fishing " + (k + 1); - for (int i = 0; i < Gen2Constants.pokesPerTSFishingGroup; i++) { - int pokeNum = rom[offset++] & 0xFF; - int level = rom[offset++] & 0xFF; - Encounter enc = new Encounter(); - enc.pokemon = pokes[pokeNum]; - enc.level = level; - es.encounters.add(enc); - } - areas.add(es); - } - } - - // Headbutt Data - offset = romEntry.getValue("HeadbuttWildsOffset"); - int limit = romEntry.getValue("HeadbuttTableSize"); - for (int i = 0; i < limit; i++) { - EncounterSet es = new EncounterSet(); - es.displayName = "Headbutt Trees Set " + (i + 1); - while ((rom[offset] & 0xFF) != 0xFF) { - offset++; - int pokeNum = rom[offset++] & 0xFF; - int level = rom[offset++] & 0xFF; - Encounter enc = new Encounter(); - enc.pokemon = pokes[pokeNum]; - enc.level = level; - es.encounters.add(enc); - } - offset++; - areas.add(es); - } - - // Bug Catching Contest Data - offset = romEntry.getValue("BCCWildsOffset"); - EncounterSet bccES = new EncounterSet(); - bccES.displayName = "Bug Catching Contest"; - while ((rom[offset] & 0xFF) != 0xFF) { - offset++; - Encounter enc = new Encounter(); - enc.pokemon = pokes[rom[offset++] & 0xFF]; - enc.level = rom[offset++] & 0xFF; - enc.maxLevel = rom[offset++] & 0xFF; - bccES.encounters.add(enc); - } - areas.add(bccES); - - return areas; - } - - private int readLandEncounters(int offset, List<EncounterSet> areas, - boolean useTimeOfDay) { - String[] todNames = new String[] { "Morning", "Day", "Night" }; - while ((rom[offset] & 0xFF) != 0xFF) { - int mapBank = rom[offset] & 0xFF; - int mapNumber = rom[offset + 1] & 0xFF; - String mapName = mapNames[mapBank][mapNumber]; - if (useTimeOfDay) { - for (int i = 0; i < 3; i++) { - EncounterSet encset = new EncounterSet(); - encset.rate = rom[offset + 2 + i] & 0xFF; - encset.displayName = mapName + " Grass/Cave (" - + todNames[i] + ")"; - for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { - Encounter enc = new Encounter(); - enc.level = rom[offset + 5 - + (i * Gen2Constants.landEncounterSlots * 2) - + (j * 2)] & 0xFF; - enc.maxLevel = 0; - enc.pokemon = pokes[rom[offset + 5 - + (i * Gen2Constants.landEncounterSlots * 2) - + (j * 2) + 1] & 0xFF]; - encset.encounters.add(enc); - } - areas.add(encset); - } - } else { - // Use Day only - EncounterSet encset = new EncounterSet(); - encset.rate = rom[offset + 3] & 0xFF; - encset.displayName = mapName + " Grass/Cave"; - for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { - Encounter enc = new Encounter(); - enc.level = rom[offset + 5 - + Gen2Constants.landEncounterSlots * 2 + (j * 2)] & 0xFF; - enc.maxLevel = 0; - enc.pokemon = pokes[rom[offset + 5 - + Gen2Constants.landEncounterSlots * 2 + (j * 2) - + 1] & 0xFF]; - encset.encounters.add(enc); - } - areas.add(encset); - } - offset += 5 + 6 * Gen2Constants.landEncounterSlots; - } - return offset + 1; - } - - private int readSeaEncounters(int offset, List<EncounterSet> areas) { - while ((rom[offset] & 0xFF) != 0xFF) { - int mapBank = rom[offset] & 0xFF; - int mapNumber = rom[offset + 1] & 0xFF; - String mapName = mapNames[mapBank][mapNumber]; - EncounterSet encset = new EncounterSet(); - encset.rate = rom[offset + 2] & 0xFF; - encset.displayName = mapName + " Surfing"; - for (int j = 0; j < Gen2Constants.seaEncounterSlots; j++) { - Encounter enc = new Encounter(); - enc.level = rom[offset + 3 + (j * 2)] & 0xFF; - enc.maxLevel = 0; - enc.pokemon = pokes[rom[offset + 3 + (j * 2) + 1] & 0xFF]; - encset.encounters.add(enc); - } - areas.add(encset); - offset += 3 + Gen2Constants.seaEncounterSlots * 2; - } - return offset + 1; - } - - @Override - public void setEncounters(boolean useTimeOfDay, - List<EncounterSet> encounters) { - if (!havePatchedFleeing) { - patchFleeing(); - } - int offset = romEntry.getValue("WildPokemonOffset"); - Iterator<EncounterSet> areas = encounters.iterator(); - offset = writeLandEncounters(offset, areas, useTimeOfDay); // Johto - offset = writeSeaEncounters(offset, areas); // Johto - offset = writeLandEncounters(offset, areas, useTimeOfDay); // Kanto - offset = writeSeaEncounters(offset, areas); // Kanto - offset = writeLandEncounters(offset, areas, useTimeOfDay); // Specials - offset = writeSeaEncounters(offset, areas); // Specials - - // Fishing Data - offset = romEntry.getValue("FishingWildsOffset"); - for (int k = 0; k < Gen2Constants.fishingGroupCount; k++) { - EncounterSet es = areas.next(); - Iterator<Encounter> encs = es.encounters.iterator(); - for (int i = 0; i < Gen2Constants.pokesPerFishingGroup; i++) { - offset++; - if (rom[offset] == 0) { - if (!useTimeOfDay) { - // overwrite with a static encounter - Encounter enc = encs.next(); - rom[offset++] = (byte) enc.pokemon.number; - rom[offset++] = (byte) enc.level; - } else { - // else handle below - offset += 2; - } - } else { - Encounter enc = encs.next(); - rom[offset++] = (byte) enc.pokemon.number; - rom[offset++] = (byte) enc.level; - } - } - } - if (useTimeOfDay) { - for (int k = 0; k < Gen2Constants.timeSpecificFishingGroupCount; k++) { - EncounterSet es = areas.next(); - Iterator<Encounter> encs = es.encounters.iterator(); - for (int i = 0; i < Gen2Constants.pokesPerTSFishingGroup; i++) { - Encounter enc = encs.next(); - rom[offset++] = (byte) enc.pokemon.number; - rom[offset++] = (byte) enc.level; - } - } - } - - // Headbutt Data - offset = romEntry.getValue("HeadbuttWildsOffset"); - int limit = romEntry.getValue("HeadbuttTableSize"); - for (int i = 0; i < limit; i++) { - EncounterSet es = areas.next(); - Iterator<Encounter> encs = es.encounters.iterator(); - while ((rom[offset] & 0xFF) != 0xFF) { - Encounter enc = encs.next(); - offset++; - rom[offset++] = (byte) enc.pokemon.number; - rom[offset++] = (byte) enc.level; - } - offset++; - } - - // Bug Catching Contest Data - offset = romEntry.getValue("BCCWildsOffset"); - EncounterSet bccES = areas.next(); - Iterator<Encounter> bccEncs = bccES.encounters.iterator(); - while ((rom[offset] & 0xFF) != 0xFF) { - offset++; - Encounter enc = bccEncs.next(); - rom[offset++] = (byte) enc.pokemon.number; - rom[offset++] = (byte) enc.level; - rom[offset++] = (byte) enc.maxLevel; - } - - } - - private int writeLandEncounters(int offset, Iterator<EncounterSet> areas, - boolean useTimeOfDay) { - while ((rom[offset] & 0xFF) != 0xFF) { - if (useTimeOfDay) { - for (int i = 0; i < 3; i++) { - EncounterSet encset = areas.next(); - Iterator<Encounter> encountersHere = encset.encounters - .iterator(); - for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { - rom[offset + 5 - + (i * Gen2Constants.landEncounterSlots * 2) - + (j * 2) + 1] = (byte) encountersHere.next().pokemon.number; - } - } - } else { - // Write the set to all 3 equally - EncounterSet encset = areas.next(); - for (int i = 0; i < 3; i++) { - Iterator<Encounter> encountersHere = encset.encounters - .iterator(); - for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { - rom[offset + 5 - + (i * Gen2Constants.landEncounterSlots * 2) - + (j * 2) + 1] = (byte) encountersHere.next().pokemon.number; - } - } - } - offset += 5 + 6 * Gen2Constants.landEncounterSlots; - } - return offset + 1; - } - - private int writeSeaEncounters(int offset, Iterator<EncounterSet> areas) { - while ((rom[offset] & 0xFF) != 0xFF) { - EncounterSet encset = areas.next(); - Iterator<Encounter> encountersHere = encset.encounters.iterator(); - for (int j = 0; j < Gen2Constants.seaEncounterSlots; j++) { - rom[offset + 3 + (j * 2) + 1] = (byte) encountersHere.next().pokemon.number; - } - offset += 3 + Gen2Constants.seaEncounterSlots * 2; - } - return offset + 1; - } - - @Override - public List<Trainer> getTrainers() { - int traineroffset = romEntry.getValue("TrainerDataTableOffset"); - int traineramount = romEntry.getValue("TrainerClassAmount"); - int[] trainerclasslimits = romEntry.arrayEntries - .get("TrainerDataClassCounts"); - - int[] pointers = new int[traineramount + 1]; - for (int i = 1; i <= traineramount; i++) { - int pointer = readWord(traineroffset + (i - 1) * 2); - pointers[i] = calculateOffset(bankOf(traineroffset), pointer); - } - - List<String> tcnames = this.getTrainerClassNames(); - - List<Trainer> allTrainers = new ArrayList<Trainer>(); - for (int i = 1; i <= traineramount; i++) { - int offs = pointers[i]; - int limit = trainerclasslimits[i]; - for (int trnum = 0; trnum < limit; trnum++) { - Trainer tr = new Trainer(); - tr.offset = offs; - tr.trainerclass = i; - String name = readVariableLengthString(offs); - tr.name = name; - tr.fullDisplayName = tcnames.get(i - 1) + " " + name; - int len = lengthOfStringAt(offs); - offs += len + 1; - int dataType = rom[offs] & 0xFF; - tr.poketype = dataType; - offs++; - while ((rom[offs] & 0xFF) != 0xFF) { - TrainerPokemon tp = new TrainerPokemon(); - tp.level = rom[offs] & 0xFF; - tp.pokemon = pokes[rom[offs + 1] & 0xFF]; - offs += 2; - if (dataType == 2 || dataType == 3) { - tp.heldItem = rom[offs] & 0xFF; - offs++; - } - if (dataType % 2 == 1) { - tp.move1 = rom[offs] & 0xFF; - tp.move2 = rom[offs + 1] & 0xFF; - tp.move3 = rom[offs + 2] & 0xFF; - tp.move4 = rom[offs + 3] & 0xFF; - offs += 4; - } - tr.pokemon.add(tp); - } - allTrainers.add(tr); - offs++; - } - } - Gen2Constants.universalTrainerTags(allTrainers); - if (romEntry.isCrystal) { - Gen2Constants.crystalTags(allTrainers); - } else { - Gen2Constants.goldSilverTags(allTrainers); - } - return allTrainers; - } - - @Override - public void setTrainers(List<Trainer> trainerData) { - int traineroffset = romEntry.getValue("TrainerDataTableOffset"); - int traineramount = romEntry.getValue("TrainerClassAmount"); - int[] trainerclasslimits = romEntry.arrayEntries - .get("TrainerDataClassCounts"); - - int[] pointers = new int[traineramount + 1]; - for (int i = 1; i <= traineramount; i++) { - int pointer = readWord(traineroffset + (i - 1) * 2); - pointers[i] = calculateOffset(bankOf(traineroffset), pointer); - } - - Iterator<Trainer> allTrainers = trainerData.iterator(); - for (int i = 1; i <= traineramount; i++) { - int offs = pointers[i]; - int limit = trainerclasslimits[i]; - for (int trnum = 0; trnum < limit; trnum++) { - Trainer tr = allTrainers.next(); - if (tr.trainerclass != i) { - System.err.println("Trainer mismatch: " + tr.name); - } - // Write their name - int trnamelen = internalStringLength(tr.name); - writeFixedLengthString(tr.name, offs, trnamelen + 1); - offs += trnamelen + 1; - // Poketype - tr.poketype = 0; // remove held items and moves - rom[offs++] = (byte) tr.poketype; - Iterator<TrainerPokemon> tPokes = tr.pokemon.iterator(); - for (int tpnum = 0; tpnum < tr.pokemon.size(); tpnum++) { - TrainerPokemon tp = tPokes.next(); - rom[offs] = (byte) tp.level; - rom[offs + 1] = (byte) tp.pokemon.number; - offs += 2; - if (tr.poketype == 2 || tr.poketype == 3) { - rom[offs] = (byte) tp.heldItem; - offs++; - } - if (tr.poketype % 2 == 1) { - rom[offs] = (byte) tp.move1; - rom[offs + 1] = (byte) tp.move2; - rom[offs + 2] = (byte) tp.move3; - rom[offs + 3] = (byte) tp.move4; - offs += 4; - } - } - rom[offs] = (byte) 0xFF; - offs++; - } - } - - } - - @Override - public List<Pokemon> getPokemon() { - return pokemonList; - } - - @Override - public Map<Pokemon, List<MoveLearnt>> getMovesLearnt() { - Map<Pokemon, List<MoveLearnt>> movesets = new TreeMap<Pokemon, List<MoveLearnt>>(); - int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); - for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { - int pointer = readWord(pointersOffset + (i - 1) * 2); - int realPointer = calculateOffset(bankOf(pointersOffset), pointer); - Pokemon pkmn = pokes[i]; - // Skip over evolution data - while (rom[realPointer] != 0) { - if (rom[realPointer] == 5) { - realPointer += 4; - } else { - realPointer += 3; - } - } - List<MoveLearnt> ourMoves = new ArrayList<MoveLearnt>(); - realPointer++; - while (rom[realPointer] != 0) { - MoveLearnt learnt = new MoveLearnt(); - learnt.level = rom[realPointer] & 0xFF; - learnt.move = rom[realPointer + 1] & 0xFF; - ourMoves.add(learnt); - realPointer += 2; - } - movesets.put(pkmn, ourMoves); - } - return movesets; - } - - @Override - public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets) { - writeEvosAndMovesLearnt(false, movesets); - } - - @Override - public List<Integer> getMovesBannedFromLevelup() { - // ban thief because trainers are broken with it - return Gen2Constants.bannedLevelupMoves; - } - - @Override - public List<Pokemon> getStaticPokemon() { - List<Pokemon> statics = new ArrayList<Pokemon>(); - if (romEntry.getValue("StaticPokemonSupport") > 0) { - for (int offset : romEntry.staticPokemonSingle) { - statics.add(pokes[rom[offset] & 0xFF]); - } - // Game Corner - for (int offset : romEntry.staticPokemonGameCorner.keySet()) { - statics.add(pokes[rom[offset] & 0xFF]); - } - } - return statics; - } - - @Override - public boolean setStaticPokemon(List<Pokemon> staticPokemon) { - if (romEntry.getValue("StaticPokemonSupport") == 0) { - return false; - } - if (!havePatchedFleeing) { - patchFleeing(); - } - if (staticPokemon.size() != romEntry.staticPokemonSingle.size() - + romEntry.staticPokemonGameCorner.size()) { - return false; - } - - Iterator<Pokemon> statics = staticPokemon.iterator(); - for (int offset : romEntry.staticPokemonSingle) { - rom[offset] = (byte) statics.next().number; - } - - int gcNameLength = romEntry.getValue("GameCornerPokemonNameLength"); - - // Sort out static Pokemon - for (int offset : romEntry.staticPokemonGameCorner.keySet()) { - rom[offset] = (byte) statics.next().number; - rom[offset + Gen2Constants.gameCornerRepeatPKIndex1] = rom[offset]; - rom[offset + Gen2Constants.gameCornerRepeatPKIndex2] = rom[offset]; - writePaddedPokemonName(pokes[rom[offset] & 0xFF].name, - gcNameLength, romEntry.staticPokemonGameCorner.get(offset)); - } - - // Copies? - for (int offset : romEntry.staticPokemonCopy.keySet()) { - int copyTo = romEntry.staticPokemonCopy.get(offset); - rom[copyTo] = rom[offset]; - } - return true; - } - - @Override - public boolean canChangeStaticPokemon() { - return (romEntry.getValue("StaticPokemonSupport") > 0); - } - - @Override - public List<Pokemon> bannedForStaticPokemon() { - return Arrays.asList(pokes[Gen2Constants.unownIndex]); // Unown banned - } - - private void writePaddedPokemonName(String name, int length, int offset) { - String paddedName = String.format("%-" + length + "s", name); - byte[] rawData = traduire(paddedName); - for (int i = 0; i < length; i++) { - rom[offset + i] = rawData[i]; - } - } - - @Override - public List<Integer> getTMMoves() { - List<Integer> tms = new ArrayList<Integer>(); - int offset = romEntry.getValue("TMMovesOffset"); - for (int i = 1; i <= Gen2Constants.tmCount; i++) { - tms.add(rom[offset + (i - 1)] & 0xFF); - } - return tms; - } - - @Override - public List<Integer> getHMMoves() { - List<Integer> hms = new ArrayList<Integer>(); - int offset = romEntry.getValue("TMMovesOffset"); - for (int i = 1; i <= Gen2Constants.hmCount; i++) { - hms.add(rom[offset + Gen2Constants.tmCount + (i - 1)] & 0xFF); - } - return hms; - } - - @Override - public void setTMMoves(List<Integer> moveIndexes) { - int offset = romEntry.getValue("TMMovesOffset"); - for (int i = 1; i <= Gen2Constants.tmCount; i++) { - rom[offset + (i - 1)] = moveIndexes.get(i - 1).byteValue(); - } - - // TM Text - String[] moveNames = readMoveNames(); - for (TMTextEntry tte : romEntry.tmTexts) { - String moveName = moveNames[moveIndexes.get(tte.number - 1)]; - String text = tte.template.replace("%m", moveName); - writeFixedLengthScriptString(text, tte.offset, - lengthOfStringAt(tte.offset)); - } - } - - @Override - public int getTMCount() { - return Gen2Constants.tmCount; - } - - @Override - public int getHMCount() { - return Gen2Constants.hmCount; - } - - @Override - public Map<Pokemon, boolean[]> getTMHMCompatibility() { - Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); - for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { - int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") - + (i - 1) * Gen2Constants.baseStatsEntrySize; - Pokemon pkmn = pokes[i]; - boolean[] flags = new boolean[Gen2Constants.tmCount - + Gen2Constants.hmCount + 1]; - for (int j = 0; j < 8; j++) { - readByteIntoFlags(flags, j * 8 + 1, baseStatsOffset - + Gen2Constants.bsTMHMCompatOffset + j); - } - compat.put(pkmn, flags); - } - return compat; - } - - @Override - public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData) { - for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { - Pokemon pkmn = compatEntry.getKey(); - boolean[] flags = compatEntry.getValue(); - int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") - + (pkmn.number - 1) * Gen2Constants.baseStatsEntrySize; - for (int j = 0; j < 8; j++) { - if (!romEntry.isCrystal || j != 7) { - rom[baseStatsOffset + Gen2Constants.bsTMHMCompatOffset + j] = getByteFromFlags( - flags, j * 8 + 1); - } else { - // Move tutor data - // bits 1,2,3 of byte 7 - int changedByte = getByteFromFlags(flags, j * 8 + 1) & 0xFF; - int currentByte = rom[baseStatsOffset - + Gen2Constants.bsTMHMCompatOffset + j]; - changedByte |= ((currentByte >> 1) & 0x01) << 1; - changedByte |= ((currentByte >> 2) & 0x01) << 2; - changedByte |= ((currentByte >> 3) & 0x01) << 3; - rom[baseStatsOffset + 0x18 + j] = (byte) changedByte; - } - } - } - } - - @Override - public boolean hasMoveTutors() { - return romEntry.isCrystal; - } - - @Override - public List<Integer> getMoveTutorMoves() { - if (romEntry.isCrystal) { - List<Integer> mtMoves = new ArrayList<Integer>(); - for (int offset : romEntry.arrayEntries.get("MoveTutorMoves")) { - mtMoves.add(rom[offset] & 0xFF); - } - return mtMoves; - } - return new ArrayList<Integer>(); - } - - @Override - public void setMoveTutorMoves(List<Integer> moves) { - if (!romEntry.isCrystal) { - return; - } - if (moves.size() != 3) { - return; - } - Iterator<Integer> mvList = moves.iterator(); - for (int offset : romEntry.arrayEntries.get("MoveTutorMoves")) { - rom[offset] = mvList.next().byteValue(); - } - - // Construct a new menu - if (romEntry.getValue("MoveTutorMenuOffset") > 0 - && romEntry.getValue("MoveTutorMenuNewSpace") > 0) { - String[] moveNames = readMoveNames(); - String[] names = new String[] { moveNames[moves.get(0)], - moveNames[moves.get(1)], moveNames[moves.get(2)], - Gen2Constants.mtMenuCancelString }; - int menuOffset = romEntry.getValue("MoveTutorMenuNewSpace"); - rom[menuOffset++] = Gen2Constants.mtMenuInitByte; - rom[menuOffset++] = 0x4; - for (int i = 0; i < 4; i++) { - byte[] trans = traduire(names[i]); - System.arraycopy(trans, 0, rom, menuOffset, trans.length); - menuOffset += trans.length; - rom[menuOffset++] = GBConstants.stringTerminator; - } - int pointerOffset = romEntry.getValue("MoveTutorMenuOffset"); - writeWord(pointerOffset, - makeGBPointer(romEntry.getValue("MoveTutorMenuNewSpace"))); - } - } - - @Override - public Map<Pokemon, boolean[]> getMoveTutorCompatibility() { - if (!romEntry.isCrystal) { - return new TreeMap<Pokemon, boolean[]>(); - } - Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); - for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { - int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") - + (i - 1) * Gen2Constants.baseStatsEntrySize; - Pokemon pkmn = pokes[i]; - boolean[] flags = new boolean[4]; - int mtByte = rom[baseStatsOffset + Gen2Constants.bsMTCompatOffset] & 0xFF; - for (int j = 1; j <= 3; j++) { - flags[j] = ((mtByte >> j) & 0x01) > 0; - } - compat.put(pkmn, flags); - } - return compat; - } - - @Override - public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData) { - if (!romEntry.isCrystal) { - return; - } - for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { - Pokemon pkmn = compatEntry.getKey(); - boolean[] flags = compatEntry.getValue(); - int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") - + (pkmn.number - 1) * Gen2Constants.baseStatsEntrySize; - int origMtByte = rom[baseStatsOffset - + Gen2Constants.bsMTCompatOffset] & 0xFF; - int mtByte = origMtByte & 0x01; - for (int j = 1; j <= 3; j++) { - mtByte |= flags[j] ? (1 << j) : 0; - } - rom[baseStatsOffset + Gen2Constants.bsMTCompatOffset] = (byte) mtByte; - } - } - - @Override - public String getROMName() { - if (isVietCrystal) { - return Gen2Constants.vietCrystalROMName; - } - return "Pokemon " + romEntry.name; - } - - @Override - public String getROMCode() { - return romEntry.romCode; - } - - @Override - public String getSupportLevel() { - return "Complete"; - } - - @Override - public boolean hasTimeBasedEncounters() { - return true; // All GSC do - } - - private void populateEvolutions() { - for (Pokemon pkmn : pokes) { - if (pkmn != null) { - pkmn.evolutionsFrom.clear(); - pkmn.evolutionsTo.clear(); - } - } - - int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); - for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { - int pointer = readWord(pointersOffset + (i - 1) * 2); - int realPointer = calculateOffset(bankOf(pointersOffset), pointer); - Pokemon pkmn = pokes[i]; - int thisPoke = i; - while (rom[realPointer] != 0) { - int method = rom[realPointer] & 0xFF; - int otherPoke = rom[realPointer + 2 + (method == 5 ? 1 : 0)] & 0xFF; - EvolutionType type = EvolutionType.fromIndex(2, method); - int extraInfo = 0; - if (type == EvolutionType.TRADE) { - int itemNeeded = rom[realPointer + 1] & 0xFF; - if (itemNeeded != 0xFF) { - type = EvolutionType.TRADE_ITEM; - extraInfo = itemNeeded; - } - } else if (type == EvolutionType.LEVEL_ATTACK_HIGHER) { - int tyrogueCond = rom[realPointer + 2] & 0xFF; - if (tyrogueCond == 2) { - type = EvolutionType.LEVEL_DEFENSE_HIGHER; - } else if (tyrogueCond == 3) { - type = EvolutionType.LEVEL_ATK_DEF_SAME; - } - extraInfo = rom[realPointer + 1] & 0xFF; - } else if (type == EvolutionType.HAPPINESS) { - int happCond = rom[realPointer + 1] & 0xFF; - if (happCond == 2) { - type = EvolutionType.HAPPINESS_DAY; - } else if (happCond == 3) { - type = EvolutionType.HAPPINESS_NIGHT; - } - } else { - extraInfo = rom[realPointer + 1] & 0xFF; - } - Evolution evo = new Evolution(pokes[thisPoke], - pokes[otherPoke], true, type, extraInfo); - if (!pkmn.evolutionsFrom.contains(evo)) { - pkmn.evolutionsFrom.add(evo); - pokes[otherPoke].evolutionsTo.add(evo); - } - realPointer += (method == 5 ? 4 : 3); - } - // split evos don't carry stats - if (pkmn.evolutionsFrom.size() > 1) { - for (Evolution e : pkmn.evolutionsFrom) { - e.carryStats = false; - } - } - } - } - - @Override - public void removeTradeEvolutions(boolean changeMoveEvos) { - // no move evos, so no need to check for those - log("--Removing Trade Evolutions--"); - for (Pokemon pkmn : pokes) { - if (pkmn != null) { - for (Evolution evol : pkmn.evolutionsFrom) { - if (evol.type == EvolutionType.TRADE - || evol.type == EvolutionType.TRADE_ITEM) { - // change - if (evol.from.number == Gen2Constants.slowpokeIndex) { - // Slowpoke: Make water stone => Slowking - evol.type = EvolutionType.STONE; - evol.extraInfo = 24; // water stone - logEvoChangeStone(evol.from.name, evol.to.name, - itemNames[24]); - } else if (evol.from.number == Gen2Constants.seadraIndex) { - // Seadra: level 40 - evol.type = EvolutionType.LEVEL; - evol.extraInfo = 40; // level - logEvoChangeLevel(evol.from.name, evol.to.name, 40); - } else if (evol.from.number == Gen2Constants.poliwhirlIndex - || evol.type == EvolutionType.TRADE) { - // Poliwhirl or any of the original 4 trade evos - // Level 37 - evol.type = EvolutionType.LEVEL; - evol.extraInfo = 37; // level - logEvoChangeLevel(evol.from.name, evol.to.name, 37); - } else { - // A new trade evo of a single stage Pokemon - // level 30 - evol.type = EvolutionType.LEVEL; - evol.extraInfo = 30; // level - logEvoChangeLevel(evol.from.name, evol.to.name, 30); - } - } - } - } - } - logBlankLine(); - - } - - @Override - public List<String> getTrainerNames() { - int traineroffset = romEntry.getValue("TrainerDataTableOffset"); - int traineramount = romEntry.getValue("TrainerClassAmount"); - int[] trainerclasslimits = romEntry.arrayEntries - .get("TrainerDataClassCounts"); - - int[] pointers = new int[traineramount + 1]; - for (int i = 1; i <= traineramount; i++) { - int pointer = readWord(traineroffset + (i - 1) * 2); - pointers[i] = calculateOffset(bankOf(traineroffset), pointer); - } - - List<String> allTrainers = new ArrayList<String>(); - for (int i = 1; i <= traineramount; i++) { - int offs = pointers[i]; - int limit = trainerclasslimits[i]; - for (int trnum = 0; trnum < limit; trnum++) { - String name = readVariableLengthString(offs); - allTrainers.add(name); - offs += name.length() + 1; - int dataType = rom[offs] & 0xFF; - offs++; - while ((rom[offs] & 0xFF) != 0xFF) { - offs += 2; - if (dataType == 2 || dataType == 3) { - offs++; - } - if (dataType % 2 == 1) { - offs += 4; - } - } - offs++; - } - } - return allTrainers; - } - - @Override - public void setTrainerNames(List<String> trainerNames) { - if (romEntry.getValue("CanChangeTrainerText") != 0) { - int traineroffset = romEntry.getValue("TrainerDataTableOffset"); - int traineramount = romEntry.getValue("TrainerClassAmount"); - int[] trainerclasslimits = romEntry.arrayEntries - .get("TrainerDataClassCounts"); - - int[] pointers = new int[traineramount + 1]; - for (int i = 1; i <= traineramount; i++) { - int pointer = readWord(traineroffset + (i - 1) * 2); - pointers[i] = calculateOffset(bankOf(traineroffset), pointer); - } - // Build up new trainer data using old as a guideline. - int[] offsetsInNew = new int[traineramount + 1]; - int oInNewCurrent = 0; - Iterator<String> allTrainers = trainerNames.iterator(); - ByteArrayOutputStream newData = new ByteArrayOutputStream(); - try { - for (int i = 1; i <= traineramount; i++) { - int offs = pointers[i]; - int limit = trainerclasslimits[i]; - offsetsInNew[i] = oInNewCurrent; - for (int trnum = 0; trnum < limit; trnum++) { - String name = readVariableLengthString(offs); - String newName = allTrainers.next(); - byte[] newNameStr = translateString(newName); - newData.write(newNameStr); - newData.write(GBConstants.stringTerminator); - oInNewCurrent += newNameStr.length + 1; - offs += internalStringLength(name) + 1; - int dataType = rom[offs] & 0xFF; - offs++; - newData.write(dataType); - oInNewCurrent++; - while ((rom[offs] & 0xFF) != 0xFF) { - newData.write(rom, offs, 2); - oInNewCurrent += 2; - offs += 2; - if (dataType == 2 || dataType == 3) { - newData.write(rom, offs, 1); - oInNewCurrent += 1; - offs++; - } - if (dataType % 2 == 1) { - newData.write(rom, offs, 4); - oInNewCurrent += 4; - offs += 4; - } - } - newData.write(0xFF); - oInNewCurrent++; - offs++; - } - } - - // Copy new data into ROM - byte[] newTrainerData = newData.toByteArray(); - int tdBase = pointers[1]; - System.arraycopy(newTrainerData, 0, rom, pointers[1], - newTrainerData.length); - - // Finally, update the pointers - for (int i = 2; i <= traineramount; i++) { - int newOffset = tdBase + offsetsInNew[i]; - writeWord(traineroffset + (i - 1) * 2, - makeGBPointer(newOffset)); - } - } catch (IOException ex) { - // This should never happen, but abort if it does. - } - } - - } - - @Override - public TrainerNameMode trainerNameMode() { - return TrainerNameMode.MAX_LENGTH_WITH_CLASS; - } - - @Override - public int maxTrainerNameLength() { - // line size minus one for space - return Gen2Constants.maxTrainerNameLength; - } - - @Override - public List<Integer> getTCNameLengthsByTrainer() { - int traineramount = romEntry.getValue("TrainerClassAmount"); - int[] trainerclasslimits = romEntry.arrayEntries - .get("TrainerDataClassCounts"); - List<String> tcNames = this.getTrainerClassNames(); - List<Integer> tcLengthsByT = new ArrayList<Integer>(); - - for (int i = 1; i <= traineramount; i++) { - int len = internalStringLength(tcNames.get(i - 1)); - for (int k = 0; k < trainerclasslimits[i]; k++) { - tcLengthsByT.add(len); - } - } - - return tcLengthsByT; - } - - @Override - public List<String> getTrainerClassNames() { - int amount = romEntry.getValue("TrainerClassAmount"); - int offset = romEntry.getValue("TrainerClassNamesOffset"); - List<String> trainerClassNames = new ArrayList<String>(); - for (int j = 0; j < amount; j++) { - String name = readVariableLengthString(offset); - offset += lengthOfStringAt(offset) + 1; - trainerClassNames.add(name); - } - return trainerClassNames; - } - - @Override - public void setTrainerClassNames(List<String> trainerClassNames) { - if (romEntry.getValue("CanChangeTrainerText") != 0) { - int amount = romEntry.getValue("TrainerClassAmount"); - int offset = romEntry.getValue("TrainerClassNamesOffset"); - Iterator<String> trainerClassNamesI = trainerClassNames.iterator(); - for (int j = 0; j < amount; j++) { - int len = lengthOfStringAt(offset) + 1; - String newName = trainerClassNamesI.next(); - writeFixedLengthString(newName, offset, len); - offset += len; - } - } - } - - @Override - public boolean fixedTrainerClassNamesLength() { - return true; - } - - @Override - public String getDefaultExtension() { - return "gbc"; - } - - @Override - public int abilitiesPerPokemon() { - return 0; - } - - @Override - public int highestAbilityIndex() { - return 0; - } - - @Override - public int internalStringLength(String string) { - return translateString(string).length; - } - - @Override - public int miscTweaksAvailable() { - int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); - if (romEntry.codeTweaks.get("BWXPTweak") != null) { - available |= MiscTweak.BW_EXP_PATCH.getValue(); - } - if (romEntry.getValue("TextDelayFunctionOffset") != 0) { - available |= MiscTweak.FASTEST_TEXT.getValue(); - } - if (romEntry.arrayEntries.containsKey("CatchingTutorialOffsets")) { - available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); - } - return available; - } - - @Override - public void applyMiscTweak(MiscTweak tweak) { - if (tweak == MiscTweak.BW_EXP_PATCH) { - applyBWEXPPatch(); - } else if (tweak == MiscTweak.FASTEST_TEXT) { - applyFastestTextPatch(); - } else if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { - applyCamelCaseNames(); - } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { - randomizeCatchingTutorial(); - } - } - - private void randomizeCatchingTutorial() { - if (romEntry.arrayEntries.containsKey("CatchingTutorialOffsets")) { - // Pick a pokemon - int pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; - while (pokemon == Gen2Constants.unownIndex) { - // Unown is banned - pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; - } - - int[] offsets = romEntry.arrayEntries - .get("CatchingTutorialOffsets"); - for (int offset : offsets) { - rom[offset] = (byte) pokemon; - } - } - - } - - private void applyBWEXPPatch() { - String patchName = romEntry.codeTweaks.get("BWXPTweak"); - if (patchName == null) { - return; - } - - try { - FileFunctions.applyPatch(rom, patchName); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void applyFastestTextPatch() { - if (romEntry.getValue("TextDelayFunctionOffset") != 0) { - rom[romEntry.getValue("TextDelayFunctionOffset")] = (byte) GBConstants.gbZ80Ret; - } - } - - @Override - public void applySignature() { - // Intro sprite - - // Pick a pokemon - int pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; - while (pokemon == Gen2Constants.unownIndex) { - // Unown is banned - pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; - } - - rom[romEntry.getValue("IntroSpriteOffset")] = (byte) pokemon; - rom[romEntry.getValue("IntroCryOffset")] = (byte) pokemon; - - } - - @Override - public ItemList getAllowedItems() { - return Gen2Constants.allowedItems; - } - - @Override - public ItemList getNonBadItems() { - return Gen2Constants.nonBadItems; - } - - private void loadItemNames() { - itemNames = new String[256]; - itemNames[0] = "glitch"; - // trying to emulate pretty much what the game does here - // normal items - int origOffset = romEntry.getValue("ItemNamesOffset"); - int itemNameOffset = origOffset; - for (int index = 1; index <= 0x100; index++) { - if (itemNameOffset / GBConstants.bankSize > origOffset - / GBConstants.bankSize) { - // the game would continue making its merry way into VRAM here, - // but we don't have VRAM to simulate. - // just give up. - break; - } - int startOfText = itemNameOffset; - while ((rom[itemNameOffset] & 0xFF) != GBConstants.stringTerminator) { - itemNameOffset++; - } - itemNameOffset++; - itemNames[index % 256] = readFixedLengthString(startOfText, 20); - } - } - - @Override - public String[] getItemNames() { - return itemNames; - } - - private void patchFleeing() { - havePatchedFleeing = true; - int offset = romEntry.getValue("FleeingDataOffset"); - rom[offset] = (byte) 0xFF; - rom[offset + Gen2Constants.fleeingSetTwoOffset] = (byte) 0xFF; - rom[offset + Gen2Constants.fleeingSetThreeOffset] = (byte) 0xFF; - } - - private void loadLandmarkNames() { - - int lmOffset = romEntry.getValue("LandmarkTableOffset"); - int lmBank = bankOf(lmOffset); - int lmCount = romEntry.getValue("LandmarkCount"); - - landmarkNames = new String[lmCount]; - - for (int i = 0; i < lmCount; i++) { - int lmNameOffset = calculateOffset(lmBank, readWord(lmOffset + i - * 4 + 2)); - landmarkNames[i] = readVariableLengthString(lmNameOffset).replace( - "\\x1F", " "); - } - - } - - private void preprocessMaps() { - itemOffs = new ArrayList<Integer>(); - - int mhOffset = romEntry.getValue("MapHeaders"); - int mapGroupCount = Gen2Constants.mapGroupCount; - int mapsInLastGroup = Gen2Constants.mapsInLastGroup; - int mhBank = bankOf(mhOffset); - mapNames = new String[mapGroupCount + 1][100]; - - int[] groupOffsets = new int[mapGroupCount]; - for (int i = 0; i < mapGroupCount; i++) { - groupOffsets[i] = calculateOffset(mhBank, - readWord(mhOffset + i * 2)); - } - - // Read maps - for (int mg = 0; mg < mapGroupCount; mg++) { - int offset = groupOffsets[mg]; - int maxOffset = (mg == mapGroupCount - 1) ? (mhBank + 1) - * GBConstants.bankSize : groupOffsets[mg + 1]; - int map = 0; - int maxMap = (mg == mapGroupCount - 1) ? mapsInLastGroup - : Integer.MAX_VALUE; - while (offset < maxOffset && map < maxMap) { - processMapAt(offset, mg + 1, map + 1); - offset += 9; - map++; - } - } - } - - private void processMapAt(int offset, int mapBank, int mapNumber) { - - // second map header - int smhBank = rom[offset] & 0xFF; - int smhPointer = readWord(offset + 3); - int smhOffset = calculateOffset(smhBank, smhPointer); - - // map name - int mapLandmark = rom[offset + 5] & 0xFF; - mapNames[mapBank][mapNumber] = landmarkNames[mapLandmark]; - - // event header - // event header is in same bank as script header - int ehBank = rom[smhOffset + 6] & 0xFF; - int ehPointer = readWord(smhOffset + 9); - int ehOffset = calculateOffset(ehBank, ehPointer); - - // skip over filler - ehOffset += 2; - - // warps - int warpCount = rom[ehOffset++] & 0xFF; - // warps are skipped - ehOffset += warpCount * 5; - - // xy triggers - int triggerCount = rom[ehOffset++] & 0xFF; - // xy triggers are skipped - ehOffset += triggerCount * 8; - - // signposts - int signpostCount = rom[ehOffset++] & 0xFF; - // we do care about these - for (int sp = 0; sp < signpostCount; sp++) { - // type=7 are hidden items - int spType = rom[ehOffset + sp * 5 + 2] & 0xFF; - if (spType == 7) { - // get event pointer - int spPointer = readWord(ehOffset + sp * 5 + 3); - int spOffset = calculateOffset(ehBank, spPointer); - // item is at spOffset+2 (first two bytes are the flag id) - itemOffs.add(spOffset + 2); - } - } - // now skip past them - ehOffset += signpostCount * 5; - - // visible objects/people - int peopleCount = rom[ehOffset++] & 0xFF; - // we also care about these - for (int p = 0; p < peopleCount; p++) { - // color_function & 1 = 1 if itemball - int pColorFunction = rom[ehOffset + p * 13 + 7]; - if ((pColorFunction & 1) == 1) { - // get event pointer - int pPointer = readWord(ehOffset + p * 13 + 9); - int pOffset = calculateOffset(ehBank, pPointer); - // item is at the pOffset for non-hidden items - itemOffs.add(pOffset); - } - } - - } - - @Override - public List<Integer> getRequiredFieldTMs() { - return Gen2Constants.requiredFieldTMs; - } - - @Override - public List<Integer> getCurrentFieldTMs() { - List<Integer> fieldTMs = new ArrayList<Integer>(); - - for (int offset : itemOffs) { - int itemHere = rom[offset] & 0xFF; - if (Gen2Constants.allowedItems.isTM(itemHere)) { - int thisTM = 0; - if (itemHere >= Gen2Constants.tmBlockOneIndex - && itemHere < Gen2Constants.tmBlockOneIndex - + Gen2Constants.tmBlockOneSize) { - thisTM = itemHere - Gen2Constants.tmBlockOneIndex + 1; - } else if (itemHere >= Gen2Constants.tmBlockTwoIndex - && itemHere < Gen2Constants.tmBlockTwoIndex - + Gen2Constants.tmBlockTwoSize) { - thisTM = itemHere - Gen2Constants.tmBlockTwoIndex + 1 - + Gen2Constants.tmBlockOneSize; // TM block 2 offset - } else { - thisTM = itemHere - Gen2Constants.tmBlockThreeIndex + 1 - + Gen2Constants.tmBlockOneSize - + Gen2Constants.tmBlockTwoSize; // TM block 3 offset - } - // hack for the bug catching contest repeat TM28 - if (fieldTMs.contains(thisTM) == false) { - fieldTMs.add(thisTM); - } - } - } - return fieldTMs; - } - - @Override - public void setFieldTMs(List<Integer> fieldTMs) { - Iterator<Integer> iterTMs = fieldTMs.iterator(); - int[] givenTMs = new int[256]; - - for (int offset : itemOffs) { - int itemHere = rom[offset] & 0xFF; - if (Gen2Constants.allowedItems.isTM(itemHere)) { - // Cache replaced TMs to duplicate bug catching contest TM - if (givenTMs[itemHere] != 0) { - rom[offset] = (byte) givenTMs[itemHere]; - } else { - // Replace this with a TM from the list - int tm = iterTMs.next(); - if (tm >= 1 && tm <= Gen2Constants.tmBlockOneSize) { - tm += Gen2Constants.tmBlockOneIndex - 1; - } else if (tm >= Gen2Constants.tmBlockOneSize + 1 - && tm <= Gen2Constants.tmBlockOneSize - + Gen2Constants.tmBlockTwoSize) { - tm += Gen2Constants.tmBlockTwoIndex - 1 - - Gen2Constants.tmBlockOneSize; - } else { - tm += Gen2Constants.tmBlockThreeIndex - 1 - - Gen2Constants.tmBlockOneSize - - Gen2Constants.tmBlockTwoSize; - } - givenTMs[itemHere] = tm; - rom[offset] = (byte) tm; - } - } - } - } - - @Override - public List<Integer> getRegularFieldItems() { - List<Integer> fieldItems = new ArrayList<Integer>(); - - for (int offset : itemOffs) { - int itemHere = rom[offset] & 0xFF; - if (Gen2Constants.allowedItems.isAllowed(itemHere) - && !(Gen2Constants.allowedItems.isTM(itemHere))) { - fieldItems.add(itemHere); - } - } - return fieldItems; - } - - @Override - public void setRegularFieldItems(List<Integer> items) { - Iterator<Integer> iterItems = items.iterator(); - - for (int offset : itemOffs) { - int itemHere = rom[offset] & 0xFF; - if (Gen2Constants.allowedItems.isAllowed(itemHere) - && !(Gen2Constants.allowedItems.isTM(itemHere))) { - // Replace it - rom[offset] = (byte) (iterItems.next().intValue()); - } - } - - } - - @Override - public List<IngameTrade> getIngameTrades() { - List<IngameTrade> trades = new ArrayList<IngameTrade>(); - - // info - int tableOffset = romEntry.getValue("TradeTableOffset"); - int tableSize = romEntry.getValue("TradeTableSize"); - int nicknameLength = romEntry.getValue("TradeNameLength"); - int otLength = romEntry.getValue("TradeOTLength"); - int[] unused = romEntry.arrayEntries.get("TradesUnused"); - int unusedOffset = 0; - int entryLength = nicknameLength + otLength + 10; - - for (int entry = 0; entry < tableSize; entry++) { - if (unusedOffset < unused.length && unused[unusedOffset] == entry) { - unusedOffset++; - continue; - } - IngameTrade trade = new IngameTrade(); - int entryOffset = tableOffset + entry * entryLength; - trade.requestedPokemon = pokes[rom[entryOffset + 1] & 0xFF]; - trade.givenPokemon = pokes[rom[entryOffset + 2] & 0xFF]; - trade.nickname = readString(entryOffset + 3, nicknameLength); - int atkdef = rom[entryOffset + 3 + nicknameLength] & 0xFF; - int spdspc = rom[entryOffset + 4 + nicknameLength] & 0xFF; - trade.ivs = new int[] { (atkdef >> 4) & 0xF, atkdef & 0xF, - (spdspc >> 4) & 0xF, spdspc & 0xF }; - trade.item = rom[entryOffset + 5 + nicknameLength] & 0xFF; - trade.otId = readWord(entryOffset + 6 + nicknameLength); - trade.otName = readString(entryOffset + 8 + nicknameLength, - otLength); - trades.add(trade); - } - - return trades; - - } - - @Override - public void setIngameTrades(List<IngameTrade> trades) { - // info - int tableOffset = romEntry.getValue("TradeTableOffset"); - int tableSize = romEntry.getValue("TradeTableSize"); - int nicknameLength = romEntry.getValue("TradeNameLength"); - int otLength = romEntry.getValue("TradeOTLength"); - int[] unused = romEntry.arrayEntries.get("TradesUnused"); - int unusedOffset = 0; - int entryLength = nicknameLength + otLength + 9; - if (entryLength % 2 != 0) { - entryLength++; - } - int tradeOffset = 0; - - for (int entry = 0; entry < tableSize; entry++) { - if (unusedOffset < unused.length && unused[unusedOffset] == entry) { - unusedOffset++; - continue; - } - IngameTrade trade = trades.get(tradeOffset++); - int entryOffset = tableOffset + entry * entryLength; - rom[entryOffset + 1] = (byte) trade.requestedPokemon.number; - rom[entryOffset + 2] = (byte) trade.givenPokemon.number; - if (romEntry.getValue("CanChangeTrainerText") > 0) { - writeFixedLengthString(trade.nickname, entryOffset + 3, - nicknameLength); - } - rom[entryOffset + 3 + nicknameLength] = (byte) (trade.ivs[0] << 4 | trade.ivs[1]); - rom[entryOffset + 4 + nicknameLength] = (byte) (trade.ivs[2] << 4 | trade.ivs[3]); - rom[entryOffset + 5 + nicknameLength] = (byte) trade.item; - writeWord(entryOffset + 6 + nicknameLength, trade.otId); - if (romEntry.getValue("CanChangeTrainerText") > 0) { - writeFixedLengthString(trade.otName, entryOffset + 8 - + nicknameLength, otLength); - } - // remove gender req - rom[entryOffset + 8 + nicknameLength + otLength] = 0; - - } - } - - @Override - public boolean hasDVs() { - return true; - } - - @Override - public int generationOfPokemon() { - return 2; - } - - @Override - public void removeEvosForPokemonPool() { - List<Pokemon> pokemonIncluded = this.mainPokemonList; - Set<Evolution> keepEvos = new HashSet<Evolution>(); - for (Pokemon pk : pokes) { - if (pk != null) { - keepEvos.clear(); - for (Evolution evol : pk.evolutionsFrom) { - if (pokemonIncluded.contains(evol.from) - && pokemonIncluded.contains(evol.to)) { - keepEvos.add(evol); - } else { - evol.to.evolutionsTo.remove(evol); - } - } - pk.evolutionsFrom.retainAll(keepEvos); - } - } - } - - private void writeEvosAndMovesLearnt(boolean writeEvos, - Map<Pokemon, List<MoveLearnt>> movesets) { - // this assumes that the evo/attack pointers & data - // are at the end of the bank - // which, in every clean G/S/C rom supported, they are - // specify null to either argument to copy old values - int movesEvosStart = romEntry.getValue("PokemonMovesetsTableOffset"); - int movesEvosBank = bankOf(movesEvosStart); - byte[] pointerTable = new byte[Gen2Constants.pokemonCount * 2]; - int startOfNextBank = ((movesEvosStart / GBConstants.bankSize) + 1) - * GBConstants.bankSize; - int dataBlockSize = startOfNextBank - - (movesEvosStart + pointerTable.length); - int dataBlockOffset = movesEvosStart + pointerTable.length; - byte[] dataBlock = new byte[dataBlockSize]; - int offsetInData = 0; - for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { - // determine pointer - int oldDataOffset = calculateOffset(movesEvosBank, - readWord(movesEvosStart + (i - 1) * 2)); - int offsetStart = dataBlockOffset + offsetInData; - boolean evoWritten = false; - if (!writeEvos) { - // copy old - int evoOffset = oldDataOffset; - while (rom[evoOffset] != 0x00) { - int method = rom[evoOffset] & 0xFF; - int limiter = (method == 5) ? 4 : 3; - for (int b = 0; b < limiter; b++) { - dataBlock[offsetInData++] = rom[evoOffset++]; - } - evoWritten = true; - } - } else { - for (Evolution evo : pokes[i].evolutionsFrom) { - // write evos - dataBlock[offsetInData++] = (byte) evo.type.toIndex(2); - if (evo.type == EvolutionType.LEVEL - || evo.type == EvolutionType.STONE - || evo.type == EvolutionType.TRADE_ITEM) { - // simple types - dataBlock[offsetInData++] = (byte) evo.extraInfo; - } else if (evo.type == EvolutionType.TRADE) { - // non-item trade - dataBlock[offsetInData++] = (byte) 0xFF; - } else if (evo.type == EvolutionType.HAPPINESS) { - // cond 01 - dataBlock[offsetInData++] = 0x01; - } else if (evo.type == EvolutionType.HAPPINESS_DAY) { - // cond 02 - dataBlock[offsetInData++] = 0x02; - } else if (evo.type == EvolutionType.HAPPINESS_NIGHT) { - // cond 03 - dataBlock[offsetInData++] = 0x03; - } else if (evo.type == EvolutionType.LEVEL_ATTACK_HIGHER) { - dataBlock[offsetInData++] = (byte) evo.extraInfo; - dataBlock[offsetInData++] = 0x01; - } else if (evo.type == EvolutionType.LEVEL_DEFENSE_HIGHER) { - dataBlock[offsetInData++] = (byte) evo.extraInfo; - dataBlock[offsetInData++] = 0x02; - } else if (evo.type == EvolutionType.LEVEL_ATK_DEF_SAME) { - dataBlock[offsetInData++] = (byte) evo.extraInfo; - dataBlock[offsetInData++] = 0x03; - } - dataBlock[offsetInData++] = (byte) evo.to.number; - evoWritten = true; - } - } - // can we reuse a terminator? - if (!evoWritten && offsetStart != dataBlockOffset) { - // reuse last pokemon's move terminator for our evos - offsetStart -= 1; - } else { - // write a terminator - dataBlock[offsetInData++] = 0x00; - } - // write table entry now that we're sure of its location - int pointerNow = makeGBPointer(offsetStart); - writeWord(pointerTable, (i - 1) * 2, pointerNow); - // moveset - if (movesets == null) { - // copy old - int movesOffset = oldDataOffset; - // move past evos - while (rom[movesOffset] != 0x00) { - int method = rom[movesOffset] & 0xFF; - movesOffset += (method == 5) ? 4 : 3; - } - movesOffset++; - // copy moves - while (rom[movesOffset] != 0x00) { - dataBlock[offsetInData++] = rom[movesOffset++]; - dataBlock[offsetInData++] = rom[movesOffset++]; - } - } else { - List<MoveLearnt> moves = movesets.get(pokes[i]); - for (MoveLearnt ml : moves) { - dataBlock[offsetInData++] = (byte) ml.level; - dataBlock[offsetInData++] = (byte) ml.move; - } - } - // terminator - dataBlock[offsetInData++] = 0x00; - } - // write new data - System.arraycopy(pointerTable, 0, rom, movesEvosStart, - pointerTable.length); - System.arraycopy(dataBlock, 0, rom, dataBlockOffset, dataBlock.length); - } - - @Override - public boolean supportsFourStartingMoves() { - return (romEntry.getValue("SupportsFourStartingMoves") > 0); - } - - @Override - public List<Integer> getGameBreakingMoves() { - // add OHKO moves for gen2 because x acc is still broken - return Gen2Constants.brokenMoves; - } - - @Override - public List<Integer> getFieldMoves() { - // cut, fly, surf, strength, flash, - // dig, teleport, whirlpool, waterfall, - // rock smash, headbutt, sweet scent - // not softboiled or milk drink - return Gen2Constants.fieldMoves; - } - - @Override - public List<Integer> getEarlyRequiredHMMoves() { - // just cut - return Gen2Constants.earlyRequiredHMMoves; - } - - @Override - public BufferedImage getMascotImage() { - Pokemon mascot = randomPokemon(); - while (mascot.number == Gen2Constants.unownIndex) { - // Unown is banned as handling it would add a ton of extra effort. - mascot = randomPokemon(); - } - - // Each Pokemon has a front and back pic with a bank and a pointer (3*2 - // = 6) - // There is no zero-entry. - int picPointer = romEntry.getValue("PicPointers") + (mascot.number - 1) - * 6; - int picWidth = mascot.picDimensions & 0x0F; - int picHeight = (mascot.picDimensions >> 4) & 0x0F; - - int picBank = (rom[picPointer] & 0xFF); - if (romEntry.isCrystal) { - // Crystal pic banks are offset by x36 for whatever reason. - picBank += 0x36; - } else { - // Hey, G/S are dumb too! Arbitrarily redirected bank numbers. - if (picBank == 0x13) { - picBank = 0x1F; - } else if (picBank == 0x14) { - picBank = 0x20; - } else if (picBank == 0x1F) { - picBank = 0x2E; - } - } - int picOffset = calculateOffset(picBank, readWord(picPointer + 1)); - - Gen2Decmp mscSprite = new Gen2Decmp(rom, picOffset, picWidth, picHeight); - int w = picWidth * 8; - int h = picHeight * 8; - - // Palette? - // Two colors per Pokemon + two more for shiny, unlike pics there is a - // zero-entry. - // Black and white are left alone at the start and end of the palette. - int[] palette = new int[] { 0xFFFFFFFF, 0xFFAAAAAA, 0xFF666666, - 0xFF000000 }; - int paletteOffset = romEntry.getValue("PokemonPalettes") - + mascot.number * 8; - if (random.nextInt(10) == 0) { - // Use shiny instead - paletteOffset += 4; - } - for (int i = 0; i < 2; i++) { - palette[i + 1] = GFXFunctions - .conv16BitColorToARGB(readWord(paletteOffset + i * 2)); - } - - byte[] data = mscSprite.getFlattenedData(); - - BufferedImage bim = GFXFunctions.drawTiledImage(data, palette, w, h, 8); - - return bim; - } + public static class Factory extends RomHandler.Factory { + + @Override + public Gen2RomHandler create(Random random, PrintStream logStream) { + return new Gen2RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + long fileLength = new File(filename).length(); + if (fileLength > 8 * 1024 * 1024) { + return false; + } + byte[] loaded = loadFilePartial(filename, 0x1000); + if (loaded.length == 0) { + // nope + return false; + } + return detectRomInner(loaded, (int) fileLength); + } + } + + public Gen2RomHandler(Random random) { + super(random, null); + } + + public Gen2RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + private static class RomEntry { + private String name; + private String romCode; + private int version, nonJapanese; + private String extraTableFile; + private boolean isCrystal; + private int crcInHeader = -1; + private Map<String, String> codeTweaks = new HashMap<String, String>(); + private List<TMTextEntry> tmTexts = new ArrayList<TMTextEntry>(); + private Map<String, Integer> entries = new HashMap<String, Integer>(); + private Map<String, int[]> arrayEntries = new HashMap<String, int[]>(); + private List<Integer> staticPokemonSingle = new ArrayList<Integer>(); + private Map<Integer, Integer> staticPokemonGameCorner = new TreeMap<Integer, Integer>(); + private Map<Integer, Integer> staticPokemonCopy = new TreeMap<Integer, Integer>(); + + private int getValue(String key) { + if (!entries.containsKey(key)) { + entries.put(key, 0); + } + return entries.get(key); + } + } + + private static class TMTextEntry { + private int number; + private int offset; + private String template; + } + + private static List<RomEntry> roms; + + static { + loadROMInfo(); + } + + private static void loadROMInfo() { + roms = new ArrayList<RomEntry>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen2_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + r[0] = r[0].trim(); + // Static Pokemon? + if (r[0].equals("StaticPokemonGameCorner[]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length != 2) { + continue; + } + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.staticPokemonGameCorner.put(offs[0], offs[1]); + } else if (r[0].equals("StaticPokemonCopy[]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length != 2) { + continue; + } + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.staticPokemonCopy.put(offs[0], offs[1]); + } else if (r[0].equals("TMText[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] parts = r[1].substring(1, r[1].length() - 1).split(",", 3); + TMTextEntry tte = new TMTextEntry(); + tte.number = parseRIInt(parts[0]); + tte.offset = parseRIInt(parts[1]); + tte.template = parts[2]; + current.tmTexts.add(tte); + } + } else if (r[0].equals("Game")) { + current.romCode = r[1]; + } else if (r[0].equals("Version")) { + current.version = parseRIInt(r[1]); + } else if (r[0].equals("NonJapanese")) { + current.nonJapanese = parseRIInt(r[1]); + } else if (r[0].equals("Type")) { + if (r[1].equalsIgnoreCase("Crystal")) { + current.isCrystal = true; + } else { + current.isCrystal = false; + } + } else if (r[0].equals("ExtraTableFile")) { + current.extraTableFile = r[1]; + } else if (r[0].equals("CRCInHeader")) { + current.crcInHeader = parseRIInt(r[1]); + } else if (r[0].endsWith("Tweak")) { + current.codeTweaks.put(r[0], r[1]); + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.name)) { + // copy from here + boolean cSP = (current.getValue("CopyStaticPokemon") == 1); + boolean cTT = (current.getValue("CopyTMText") == 1); + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.entries.putAll(otherEntry.entries); + if (cSP) { + current.staticPokemonSingle.addAll(otherEntry.staticPokemonSingle); + current.staticPokemonGameCorner.putAll(otherEntry.staticPokemonGameCorner); + current.staticPokemonCopy.putAll(otherEntry.staticPokemonCopy); + current.entries.put("StaticPokemonSupport", 1); + } else { + current.entries.put("StaticPokemonSupport", 0); + } + if (cTT) { + current.tmTexts.addAll(otherEntry.tmTexts); + } + current.extraTableFile = otherEntry.extraTableFile; + } + } + } else { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + if (r[0].startsWith("StaticPokemon")) { + for (int off : offs) { + current.staticPokemonSingle.add(off); + } + } else { + current.arrayEntries.put(r[0], offs); + } + } + } else { + int offs = parseRIInt(r[1]); + current.entries.put(r[0], offs); + } + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + } + + } + + 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; + } + } + + // This ROM's data + private Pokemon[] pokes; + private List<Pokemon> pokemonList; + private RomEntry romEntry; + private Move[] moves; + private String[] tb; + private Map<String, Byte> d; + private int longestTableToken; + private boolean havePatchedFleeing; + private String[] itemNames; + private List<Integer> itemOffs; + private String[][] mapNames; + private String[] landmarkNames; + private boolean isVietCrystal; + + @Override + public boolean detectRom(byte[] rom) { + return detectRomInner(rom, rom.length); + } + + private static boolean detectRomInner(byte[] rom, int romSize) { + if (romSize < GBConstants.minRomSize || romSize > GBConstants.maxRomSize) { + return false; // size check + } + return checkRomEntry(rom) != null; // so it's OK if it's a valid ROM + } + + @Override + public void loadedRom() { + romEntry = checkRomEntry(this.rom); + tb = new String[256]; + d = new HashMap<String, Byte>(); + clearTextTables(); + readTextTable("gameboy_jap"); + if (romEntry.extraTableFile != null && romEntry.extraTableFile.equalsIgnoreCase("none") == false) { + readTextTable(romEntry.extraTableFile); + } + // VietCrystal override + if (romEntry.name.equals("Crystal (J)") + && rom[Gen2Constants.vietCrystalCheckOffset] == Gen2Constants.vietCrystalCheckValue) { + readTextTable("vietcrystal"); + isVietCrystal = true; + } else { + isVietCrystal = false; + } + havePatchedFleeing = false; + loadPokemonStats(); + pokemonList = Arrays.asList(pokes); + loadMoves(); + loadLandmarkNames(); + preprocessMaps(); + loadItemNames(); + } + + private static RomEntry checkRomEntry(byte[] rom) { + int version = rom[GBConstants.versionOffset] & 0xFF; + int nonjap = rom[GBConstants.jpFlagOffset] & 0xFF; + // Check for specific CRC first + int crcInHeader = ((rom[GBConstants.crcOffset] & 0xFF) << 8) | (rom[GBConstants.crcOffset + 1] & 0xFF); + for (RomEntry re : roms) { + if (romSig(rom, re.romCode) && re.version == version && re.nonJapanese == nonjap + && re.crcInHeader == crcInHeader) { + return re; + } + } + // Now check for non-specific-CRC entries + for (RomEntry re : roms) { + if (romSig(rom, re.romCode) && re.version == version && re.nonJapanese == nonjap && re.crcInHeader == -1) { + return re; + } + } + // Not found + return null; + } + + private void clearTextTables() { + tb = new String[256]; + d.clear(); + longestTableToken = 0; + } + + private void readTextTable(String name) { + try { + Scanner sc = new Scanner(FileFunctions.openConfig(name + ".tbl"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine(); + if (!q.trim().isEmpty()) { + String[] r = q.split("=", 2); + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + int hexcode = Integer.parseInt(r[0], 16); + if (tb[hexcode] != null) { + String oldMatch = tb[hexcode]; + tb[hexcode] = null; + d.remove(oldMatch); + } + tb[hexcode] = r[1]; + longestTableToken = Math.max(longestTableToken, r[1].length()); + d.put(r[1], (byte) hexcode); + } + } + sc.close(); + } catch (FileNotFoundException e) { + } + + } + + @Override + public void savingRom() { + savePokemonStats(); + saveMoves(); + } + + private void loadPokemonStats() { + pokes = new Pokemon[Gen2Constants.pokemonCount + 1]; + // Fetch our names + String[] pokeNames = readPokemonNames(); + int offs = romEntry.getValue("PokemonStatsOffset"); + // Get base stats + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i], offs + (i - 1) * Gen2Constants.baseStatsEntrySize); + // Name? + pokes[i].name = pokeNames[i]; + } + + // Get evolutions + populateEvolutions(); + + } + + private void savePokemonStats() { + // Write pokemon names + int offs = romEntry.getValue("PokemonNamesOffset"); + int len = romEntry.getValue("PokemonNamesLength"); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int stringOffset = offs + (i - 1) * len; + writeFixedLengthString(pokes[i].name, stringOffset, len); + } + // Write pokemon stats + int offs2 = romEntry.getValue("PokemonStatsOffset"); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + saveBasicPokeStats(pokes[i], offs2 + (i - 1) * Gen2Constants.baseStatsEntrySize); + } + // Write evolutions + writeEvosAndMovesLearnt(true, null); + } + + private String[] readMoveNames() { + int offset = romEntry.getValue("MoveNamesOffset"); + String[] moveNames = new String[Gen2Constants.moveCount + 1]; + for (int i = 1; i <= Gen2Constants.moveCount; i++) { + moveNames[i] = readVariableLengthString(offset); + offset += lengthOfStringAt(offset) + 1; + } + return moveNames; + } + + private void loadMoves() { + moves = new Move[Gen2Constants.moveCount + 1]; + String[] moveNames = readMoveNames(); + int offs = romEntry.getValue("MoveDataOffset"); + for (int i = 1; i <= Gen2Constants.moveCount; i++) { + moves[i] = new Move(); + moves[i].name = moveNames[i]; + moves[i].number = i; + moves[i].internalId = i; + moves[i].effectIndex = rom[offs + (i - 1) * 7] & 0xFF; + moves[i].hitratio = ((rom[offs + (i - 1) * 7 + 3] & 0xFF) + 0) / 255.0 * 100; + moves[i].power = rom[offs + (i - 1) * 7 + 1] & 0xFF; + moves[i].pp = rom[offs + (i - 1) * 7 + 4] & 0xFF; + moves[i].type = Gen2Constants.typeTable[rom[offs + (i - 1) * 7 + 2]]; + } + + } + + private void saveMoves() { + int offs = romEntry.getValue("MoveDataOffset"); + for (int i = 1; i <= 251; i++) { + rom[offs + (i - 1) * 7] = (byte) moves[i].effectIndex; + rom[offs + (i - 1) * 7 + 1] = (byte) moves[i].power; + rom[offs + (i - 1) * 7 + 2] = Gen2Constants.typeToByte(moves[i].type); + int hitratio = (int) Math.round(moves[i].hitratio * 2.55); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 255) { + hitratio = 255; + } + rom[offs + (i - 1) * 7 + 3] = (byte) hitratio; + rom[offs + (i - 1) * 7 + 4] = (byte) moves[i].pp; + } + } + + public List<Move> getMoves() { + return Arrays.asList(moves); + } + + private void loadBasicPokeStats(Pokemon pkmn, int offset) { + pkmn.hp = rom[offset + Gen2Constants.bsHPOffset] & 0xFF; + pkmn.attack = rom[offset + Gen2Constants.bsAttackOffset] & 0xFF; + pkmn.defense = rom[offset + Gen2Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = rom[offset + Gen2Constants.bsSpeedOffset] & 0xFF; + pkmn.spatk = rom[offset + Gen2Constants.bsSpAtkOffset] & 0xFF; + pkmn.spdef = rom[offset + Gen2Constants.bsSpDefOffset] & 0xFF; + // Type + pkmn.primaryType = Gen2Constants.typeTable[rom[offset + Gen2Constants.bsPrimaryTypeOffset] & 0xFF]; + pkmn.secondaryType = Gen2Constants.typeTable[rom[offset + Gen2Constants.bsSecondaryTypeOffset] & 0xFF]; + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + pkmn.catchRate = rom[offset + Gen2Constants.bsCatchRateOffset] & 0xFF; + pkmn.guaranteedHeldItem = -1; + pkmn.commonHeldItem = rom[offset + Gen2Constants.bsCommonHeldItemOffset] & 0xFF; + pkmn.rareHeldItem = rom[offset + Gen2Constants.bsRareHeldItemOffset] & 0xFF; + pkmn.darkGrassHeldItem = -1; + pkmn.growthCurve = ExpCurve.fromByte(rom[offset + Gen2Constants.bsGrowthCurveOffset]); + pkmn.picDimensions = rom[offset + Gen2Constants.bsPicDimensionsOffset] & 0xFF; + + } + + private void saveBasicPokeStats(Pokemon pkmn, int offset) { + rom[offset + Gen2Constants.bsHPOffset] = (byte) pkmn.hp; + rom[offset + Gen2Constants.bsAttackOffset] = (byte) pkmn.attack; + rom[offset + Gen2Constants.bsDefenseOffset] = (byte) pkmn.defense; + rom[offset + Gen2Constants.bsSpeedOffset] = (byte) pkmn.speed; + rom[offset + Gen2Constants.bsSpAtkOffset] = (byte) pkmn.spatk; + rom[offset + Gen2Constants.bsSpDefOffset] = (byte) pkmn.spdef; + rom[offset + Gen2Constants.bsPrimaryTypeOffset] = Gen2Constants.typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + rom[offset + Gen2Constants.bsSecondaryTypeOffset] = rom[offset + Gen2Constants.bsPrimaryTypeOffset]; + } else { + rom[offset + Gen2Constants.bsSecondaryTypeOffset] = Gen2Constants.typeToByte(pkmn.secondaryType); + } + rom[offset + Gen2Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + + rom[offset + Gen2Constants.bsCommonHeldItemOffset] = (byte) pkmn.commonHeldItem; + rom[offset + Gen2Constants.bsRareHeldItemOffset] = (byte) pkmn.rareHeldItem; + rom[offset + Gen2Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + } + + private String[] readPokemonNames() { + int offs = romEntry.getValue("PokemonNamesOffset"); + int len = romEntry.getValue("PokemonNamesLength"); + String[] names = new String[Gen2Constants.pokemonCount + 1]; + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + names[i] = readFixedLengthString(offs + (i - 1) * len, len); + } + return names; + } + + private String readString(int offset, int maxLength) { + StringBuilder string = new StringBuilder(); + for (int c = 0; c < maxLength; c++) { + int currChar = rom[offset + c] & 0xFF; + if (tb[currChar] != null) { + string.append(tb[currChar]); + } else { + if (currChar == GBConstants.stringTerminator || currChar == GBConstants.stringNull) { + break; + } else { + string.append("\\x" + String.format("%02X", currChar)); + } + } + } + return string.toString(); + } + + public byte[] translateString(String text) { + List<Byte> data = new ArrayList<Byte>(); + while (text.length() != 0) { + int i = Math.max(0, longestTableToken - text.length()); + if (text.charAt(0) == '\\' && text.charAt(1) == 'x') { + data.add((byte) Integer.parseInt(text.substring(2, 4), 16)); + text = text.substring(4); + } else { + while (!(d.containsKey(text.substring(0, longestTableToken - i)) || (i == longestTableToken))) { + i++; + } + if (i == longestTableToken) { + text = text.substring(1); + } else { + data.add(d.get(text.substring(0, longestTableToken - i))); + text = text.substring(longestTableToken - i); + } + } + } + byte[] ret = new byte[data.size()]; + for (int i = 0; i < ret.length; i++) { + ret[i] = data.get(i); + } + return ret; + } + + private String readFixedLengthString(int offset, int length) { + return readString(offset, length); + } + + public String readVariableLengthString(int offset) { + return readString(offset, Integer.MAX_VALUE); + } + + public String readVariableLengthScriptString(int offset) { + return readString(offset, Integer.MAX_VALUE); + } + + public byte[] traduire(String str) { + return translateString(str); + } + + private void writeFixedLengthString(String str, int offset, int length) { + byte[] translated = translateString(str); + int len = Math.min(translated.length, length); + System.arraycopy(translated, 0, rom, offset, len); + while (len < length) { + rom[offset + len] = GBConstants.stringTerminator; + len++; + } + } + + private void writeFixedLengthScriptString(String str, int offset, int length) { + byte[] translated = translateString(str); + int len = Math.min(translated.length, length); + System.arraycopy(translated, 0, rom, offset, len); + while (len < length) { + rom[offset + len] = GBConstants.stringNull; + len++; + } + } + + private int lengthOfStringAt(int offset) { + int len = 0; + while (rom[offset + len] != GBConstants.stringTerminator && rom[offset + len] != GBConstants.stringNull) { + len++; + } + return len; + } + + private int makeGBPointer(int offset) { + if (offset < GBConstants.bankSize) { + return offset; + } else { + return (offset % GBConstants.bankSize) + GBConstants.bankSize; + } + } + + private int bankOf(int offset) { + return (offset / GBConstants.bankSize); + } + + private int calculateOffset(int bank, int pointer) { + if (pointer < GBConstants.bankSize) { + return pointer; + } else { + return (pointer % GBConstants.bankSize) + bank * GBConstants.bankSize; + } + } + + private static boolean romSig(byte[] rom, String sig) { + try { + int sigOffset = GBConstants.romCodeOffset; + byte[] sigBytes = sig.getBytes("US-ASCII"); + for (int i = 0; i < sigBytes.length; i++) { + if (rom[sigOffset + i] != sigBytes[i]) { + return false; + } + } + return true; + } catch (UnsupportedEncodingException ex) { + return false; + } + + } + + @Override + public List<Pokemon> getStarters() { + // Get the starters + List<Pokemon> starters = new ArrayList<Pokemon>(); + starters.add(pokes[rom[romEntry.arrayEntries.get("StarterOffsets1")[0]] & 0xFF]); + starters.add(pokes[rom[romEntry.arrayEntries.get("StarterOffsets2")[0]] & 0xFF]); + starters.add(pokes[rom[romEntry.arrayEntries.get("StarterOffsets3")[0]] & 0xFF]); + return starters; + } + + @Override + public boolean setStarters(List<Pokemon> newStarters) { + if (newStarters.size() != 3) { + return false; + } + + // Actually write + + for (int i = 0; i < 3; i++) { + byte starter = (byte) newStarters.get(i).number; + int[] offsets = romEntry.arrayEntries.get("StarterOffsets" + (i + 1)); + for (int offset : offsets) { + rom[offset] = starter; + } + } + + // Attempt to replace text + if (romEntry.getValue("CanChangeStarterText") > 0) { + List<Integer> cyndaTexts = RomFunctions.search(rom, traduire(Gen2Constants.starterNames[0])); + int offset = cyndaTexts.get(romEntry.isCrystal ? 1 : 0); + String pokeName = newStarters.get(0).name; + writeFixedLengthScriptString(pokeName + "?\\e", offset, lengthOfStringAt(offset) + 1); + + List<Integer> totoTexts = RomFunctions.search(rom, traduire(Gen2Constants.starterNames[1])); + offset = totoTexts.get(romEntry.isCrystal ? 1 : 0); + pokeName = newStarters.get(1).name; + writeFixedLengthScriptString(pokeName + "?\\e", offset, lengthOfStringAt(offset) + 1); + + List<Integer> chikoTexts = RomFunctions.search(rom, traduire(Gen2Constants.starterNames[2])); + offset = chikoTexts.get(romEntry.isCrystal ? 1 : 0); + pokeName = newStarters.get(2).name; + writeFixedLengthScriptString(pokeName + "?\\e", offset, lengthOfStringAt(offset) + 1); + } + return true; + } + + @Override + public List<Integer> getStarterHeldItems() { + List<Integer> sHeldItems = new ArrayList<Integer>(); + int[] shiOffsets = romEntry.arrayEntries.get("StarterHeldItems"); + for (int offset : shiOffsets) { + sHeldItems.add(rom[offset] & 0xFF); + } + return sHeldItems; + } + + @Override + public void setStarterHeldItems(List<Integer> items) { + int[] shiOffsets = romEntry.arrayEntries.get("StarterHeldItems"); + if (items.size() != shiOffsets.length) { + return; + } + Iterator<Integer> sHeldItems = items.iterator(); + for (int offset : shiOffsets) { + rom[offset] = sHeldItems.next().byteValue(); + } + } + + @Override + public void shufflePokemonStats() { + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + pokes[i].shuffleStats(this.random); + } + } + + @Override + public List<EncounterSet> getEncounters(boolean useTimeOfDay) { + int offset = romEntry.getValue("WildPokemonOffset"); + List<EncounterSet> areas = new ArrayList<EncounterSet>(); + offset = readLandEncounters(offset, areas, useTimeOfDay); // Johto + offset = readSeaEncounters(offset, areas); // Johto + offset = readLandEncounters(offset, areas, useTimeOfDay); // Kanto + offset = readSeaEncounters(offset, areas); // Kanto + offset = readLandEncounters(offset, areas, useTimeOfDay); // Specials + offset = readSeaEncounters(offset, areas); // Specials + + // Fishing Data + offset = romEntry.getValue("FishingWildsOffset"); + int rootOffset = offset; + for (int k = 0; k < Gen2Constants.fishingGroupCount; k++) { + EncounterSet es = new EncounterSet(); + es.displayName = "Fishing Group " + (k + 1); + for (int i = 0; i < Gen2Constants.pokesPerFishingGroup; i++) { + offset++; + int pokeNum = rom[offset++] & 0xFF; + int level = rom[offset++] & 0xFF; + if (pokeNum == 0) { + if (!useTimeOfDay) { + // read the encounter they put here for DAY + int specialOffset = rootOffset + Gen2Constants.fishingGroupEntryLength + * Gen2Constants.pokesPerFishingGroup * Gen2Constants.fishingGroupCount + level * 4 + 2; + Encounter enc = new Encounter(); + enc.pokemon = pokes[rom[specialOffset] & 0xFF]; + enc.level = rom[specialOffset + 1] & 0xFF; + es.encounters.add(enc); + } + // else will be handled by code below + } else { + Encounter enc = new Encounter(); + enc.pokemon = pokes[pokeNum]; + enc.level = level; + es.encounters.add(enc); + } + } + areas.add(es); + } + if (useTimeOfDay) { + for (int k = 0; k < Gen2Constants.timeSpecificFishingGroupCount; k++) { + EncounterSet es = new EncounterSet(); + es.displayName = "Time-Specific Fishing " + (k + 1); + for (int i = 0; i < Gen2Constants.pokesPerTSFishingGroup; i++) { + int pokeNum = rom[offset++] & 0xFF; + int level = rom[offset++] & 0xFF; + Encounter enc = new Encounter(); + enc.pokemon = pokes[pokeNum]; + enc.level = level; + es.encounters.add(enc); + } + areas.add(es); + } + } + + // Headbutt Data + offset = romEntry.getValue("HeadbuttWildsOffset"); + int limit = romEntry.getValue("HeadbuttTableSize"); + for (int i = 0; i < limit; i++) { + EncounterSet es = new EncounterSet(); + es.displayName = "Headbutt Trees Set " + (i + 1); + while ((rom[offset] & 0xFF) != 0xFF) { + offset++; + int pokeNum = rom[offset++] & 0xFF; + int level = rom[offset++] & 0xFF; + Encounter enc = new Encounter(); + enc.pokemon = pokes[pokeNum]; + enc.level = level; + es.encounters.add(enc); + } + offset++; + areas.add(es); + } + + // Bug Catching Contest Data + offset = romEntry.getValue("BCCWildsOffset"); + EncounterSet bccES = new EncounterSet(); + bccES.displayName = "Bug Catching Contest"; + while ((rom[offset] & 0xFF) != 0xFF) { + offset++; + Encounter enc = new Encounter(); + enc.pokemon = pokes[rom[offset++] & 0xFF]; + enc.level = rom[offset++] & 0xFF; + enc.maxLevel = rom[offset++] & 0xFF; + bccES.encounters.add(enc); + } + areas.add(bccES); + + return areas; + } + + private int readLandEncounters(int offset, List<EncounterSet> areas, boolean useTimeOfDay) { + String[] todNames = new String[] { "Morning", "Day", "Night" }; + while ((rom[offset] & 0xFF) != 0xFF) { + int mapBank = rom[offset] & 0xFF; + int mapNumber = rom[offset + 1] & 0xFF; + String mapName = mapNames[mapBank][mapNumber]; + if (useTimeOfDay) { + for (int i = 0; i < 3; i++) { + EncounterSet encset = new EncounterSet(); + encset.rate = rom[offset + 2 + i] & 0xFF; + encset.displayName = mapName + " Grass/Cave (" + todNames[i] + ")"; + for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { + Encounter enc = new Encounter(); + enc.level = rom[offset + 5 + (i * Gen2Constants.landEncounterSlots * 2) + (j * 2)] & 0xFF; + enc.maxLevel = 0; + enc.pokemon = pokes[rom[offset + 5 + (i * Gen2Constants.landEncounterSlots * 2) + (j * 2) + 1] & 0xFF]; + encset.encounters.add(enc); + } + areas.add(encset); + } + } else { + // Use Day only + EncounterSet encset = new EncounterSet(); + encset.rate = rom[offset + 3] & 0xFF; + encset.displayName = mapName + " Grass/Cave"; + for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { + Encounter enc = new Encounter(); + enc.level = rom[offset + 5 + Gen2Constants.landEncounterSlots * 2 + (j * 2)] & 0xFF; + enc.maxLevel = 0; + enc.pokemon = pokes[rom[offset + 5 + Gen2Constants.landEncounterSlots * 2 + (j * 2) + 1] & 0xFF]; + encset.encounters.add(enc); + } + areas.add(encset); + } + offset += 5 + 6 * Gen2Constants.landEncounterSlots; + } + return offset + 1; + } + + private int readSeaEncounters(int offset, List<EncounterSet> areas) { + while ((rom[offset] & 0xFF) != 0xFF) { + int mapBank = rom[offset] & 0xFF; + int mapNumber = rom[offset + 1] & 0xFF; + String mapName = mapNames[mapBank][mapNumber]; + EncounterSet encset = new EncounterSet(); + encset.rate = rom[offset + 2] & 0xFF; + encset.displayName = mapName + " Surfing"; + for (int j = 0; j < Gen2Constants.seaEncounterSlots; j++) { + Encounter enc = new Encounter(); + enc.level = rom[offset + 3 + (j * 2)] & 0xFF; + enc.maxLevel = 0; + enc.pokemon = pokes[rom[offset + 3 + (j * 2) + 1] & 0xFF]; + encset.encounters.add(enc); + } + areas.add(encset); + offset += 3 + Gen2Constants.seaEncounterSlots * 2; + } + return offset + 1; + } + + @Override + public void setEncounters(boolean useTimeOfDay, List<EncounterSet> encounters) { + if (!havePatchedFleeing) { + patchFleeing(); + } + int offset = romEntry.getValue("WildPokemonOffset"); + Iterator<EncounterSet> areas = encounters.iterator(); + offset = writeLandEncounters(offset, areas, useTimeOfDay); // Johto + offset = writeSeaEncounters(offset, areas); // Johto + offset = writeLandEncounters(offset, areas, useTimeOfDay); // Kanto + offset = writeSeaEncounters(offset, areas); // Kanto + offset = writeLandEncounters(offset, areas, useTimeOfDay); // Specials + offset = writeSeaEncounters(offset, areas); // Specials + + // Fishing Data + offset = romEntry.getValue("FishingWildsOffset"); + for (int k = 0; k < Gen2Constants.fishingGroupCount; k++) { + EncounterSet es = areas.next(); + Iterator<Encounter> encs = es.encounters.iterator(); + for (int i = 0; i < Gen2Constants.pokesPerFishingGroup; i++) { + offset++; + if (rom[offset] == 0) { + if (!useTimeOfDay) { + // overwrite with a static encounter + Encounter enc = encs.next(); + rom[offset++] = (byte) enc.pokemon.number; + rom[offset++] = (byte) enc.level; + } else { + // else handle below + offset += 2; + } + } else { + Encounter enc = encs.next(); + rom[offset++] = (byte) enc.pokemon.number; + rom[offset++] = (byte) enc.level; + } + } + } + if (useTimeOfDay) { + for (int k = 0; k < Gen2Constants.timeSpecificFishingGroupCount; k++) { + EncounterSet es = areas.next(); + Iterator<Encounter> encs = es.encounters.iterator(); + for (int i = 0; i < Gen2Constants.pokesPerTSFishingGroup; i++) { + Encounter enc = encs.next(); + rom[offset++] = (byte) enc.pokemon.number; + rom[offset++] = (byte) enc.level; + } + } + } + + // Headbutt Data + offset = romEntry.getValue("HeadbuttWildsOffset"); + int limit = romEntry.getValue("HeadbuttTableSize"); + for (int i = 0; i < limit; i++) { + EncounterSet es = areas.next(); + Iterator<Encounter> encs = es.encounters.iterator(); + while ((rom[offset] & 0xFF) != 0xFF) { + Encounter enc = encs.next(); + offset++; + rom[offset++] = (byte) enc.pokemon.number; + rom[offset++] = (byte) enc.level; + } + offset++; + } + + // Bug Catching Contest Data + offset = romEntry.getValue("BCCWildsOffset"); + EncounterSet bccES = areas.next(); + Iterator<Encounter> bccEncs = bccES.encounters.iterator(); + while ((rom[offset] & 0xFF) != 0xFF) { + offset++; + Encounter enc = bccEncs.next(); + rom[offset++] = (byte) enc.pokemon.number; + rom[offset++] = (byte) enc.level; + rom[offset++] = (byte) enc.maxLevel; + } + + } + + private int writeLandEncounters(int offset, Iterator<EncounterSet> areas, boolean useTimeOfDay) { + while ((rom[offset] & 0xFF) != 0xFF) { + if (useTimeOfDay) { + for (int i = 0; i < 3; i++) { + EncounterSet encset = areas.next(); + Iterator<Encounter> encountersHere = encset.encounters.iterator(); + for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { + rom[offset + 5 + (i * Gen2Constants.landEncounterSlots * 2) + (j * 2) + 1] = (byte) encountersHere + .next().pokemon.number; + } + } + } else { + // Write the set to all 3 equally + EncounterSet encset = areas.next(); + for (int i = 0; i < 3; i++) { + Iterator<Encounter> encountersHere = encset.encounters.iterator(); + for (int j = 0; j < Gen2Constants.landEncounterSlots; j++) { + rom[offset + 5 + (i * Gen2Constants.landEncounterSlots * 2) + (j * 2) + 1] = (byte) encountersHere + .next().pokemon.number; + } + } + } + offset += 5 + 6 * Gen2Constants.landEncounterSlots; + } + return offset + 1; + } + + private int writeSeaEncounters(int offset, Iterator<EncounterSet> areas) { + while ((rom[offset] & 0xFF) != 0xFF) { + EncounterSet encset = areas.next(); + Iterator<Encounter> encountersHere = encset.encounters.iterator(); + for (int j = 0; j < Gen2Constants.seaEncounterSlots; j++) { + rom[offset + 3 + (j * 2) + 1] = (byte) encountersHere.next().pokemon.number; + } + offset += 3 + Gen2Constants.seaEncounterSlots * 2; + } + return offset + 1; + } + + @Override + public List<Trainer> getTrainers() { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = romEntry.getValue("TrainerClassAmount"); + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount + 1]; + for (int i = 1; i <= traineramount; i++) { + int pointer = readWord(traineroffset + (i - 1) * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), pointer); + } + + List<String> tcnames = this.getTrainerClassNames(); + + List<Trainer> allTrainers = new ArrayList<Trainer>(); + for (int i = 1; i <= traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + for (int trnum = 0; trnum < limit; trnum++) { + Trainer tr = new Trainer(); + tr.offset = offs; + tr.trainerclass = i; + String name = readVariableLengthString(offs); + tr.name = name; + tr.fullDisplayName = tcnames.get(i - 1) + " " + name; + int len = lengthOfStringAt(offs); + offs += len + 1; + int dataType = rom[offs] & 0xFF; + tr.poketype = dataType; + offs++; + while ((rom[offs] & 0xFF) != 0xFF) { + TrainerPokemon tp = new TrainerPokemon(); + tp.level = rom[offs] & 0xFF; + tp.pokemon = pokes[rom[offs + 1] & 0xFF]; + offs += 2; + if (dataType == 2 || dataType == 3) { + tp.heldItem = rom[offs] & 0xFF; + offs++; + } + if (dataType % 2 == 1) { + tp.move1 = rom[offs] & 0xFF; + tp.move2 = rom[offs + 1] & 0xFF; + tp.move3 = rom[offs + 2] & 0xFF; + tp.move4 = rom[offs + 3] & 0xFF; + offs += 4; + } + tr.pokemon.add(tp); + } + allTrainers.add(tr); + offs++; + } + } + Gen2Constants.universalTrainerTags(allTrainers); + if (romEntry.isCrystal) { + Gen2Constants.crystalTags(allTrainers); + } else { + Gen2Constants.goldSilverTags(allTrainers); + } + return allTrainers; + } + + @Override + public void setTrainers(List<Trainer> trainerData) { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = romEntry.getValue("TrainerClassAmount"); + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount + 1]; + for (int i = 1; i <= traineramount; i++) { + int pointer = readWord(traineroffset + (i - 1) * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), pointer); + } + + Iterator<Trainer> allTrainers = trainerData.iterator(); + for (int i = 1; i <= traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + for (int trnum = 0; trnum < limit; trnum++) { + Trainer tr = allTrainers.next(); + if (tr.trainerclass != i) { + System.err.println("Trainer mismatch: " + tr.name); + } + // Write their name + int trnamelen = internalStringLength(tr.name); + writeFixedLengthString(tr.name, offs, trnamelen + 1); + offs += trnamelen + 1; + // Poketype + tr.poketype = 0; // remove held items and moves + rom[offs++] = (byte) tr.poketype; + Iterator<TrainerPokemon> tPokes = tr.pokemon.iterator(); + for (int tpnum = 0; tpnum < tr.pokemon.size(); tpnum++) { + TrainerPokemon tp = tPokes.next(); + rom[offs] = (byte) tp.level; + rom[offs + 1] = (byte) tp.pokemon.number; + offs += 2; + if (tr.poketype == 2 || tr.poketype == 3) { + rom[offs] = (byte) tp.heldItem; + offs++; + } + if (tr.poketype % 2 == 1) { + rom[offs] = (byte) tp.move1; + rom[offs + 1] = (byte) tp.move2; + rom[offs + 2] = (byte) tp.move3; + rom[offs + 3] = (byte) tp.move4; + offs += 4; + } + } + rom[offs] = (byte) 0xFF; + offs++; + } + } + + } + + @Override + public List<Pokemon> getPokemon() { + return pokemonList; + } + + @Override + public Map<Pokemon, List<MoveLearnt>> getMovesLearnt() { + Map<Pokemon, List<MoveLearnt>> movesets = new TreeMap<Pokemon, List<MoveLearnt>>(); + int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int pointer = readWord(pointersOffset + (i - 1) * 2); + int realPointer = calculateOffset(bankOf(pointersOffset), pointer); + Pokemon pkmn = pokes[i]; + // Skip over evolution data + while (rom[realPointer] != 0) { + if (rom[realPointer] == 5) { + realPointer += 4; + } else { + realPointer += 3; + } + } + List<MoveLearnt> ourMoves = new ArrayList<MoveLearnt>(); + realPointer++; + while (rom[realPointer] != 0) { + MoveLearnt learnt = new MoveLearnt(); + learnt.level = rom[realPointer] & 0xFF; + learnt.move = rom[realPointer + 1] & 0xFF; + ourMoves.add(learnt); + realPointer += 2; + } + movesets.put(pkmn, ourMoves); + } + return movesets; + } + + @Override + public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets) { + writeEvosAndMovesLearnt(false, movesets); + } + + @Override + public List<Integer> getMovesBannedFromLevelup() { + // ban thief because trainers are broken with it + return Gen2Constants.bannedLevelupMoves; + } + + @Override + public List<Pokemon> getStaticPokemon() { + List<Pokemon> statics = new ArrayList<Pokemon>(); + if (romEntry.getValue("StaticPokemonSupport") > 0) { + for (int offset : romEntry.staticPokemonSingle) { + statics.add(pokes[rom[offset] & 0xFF]); + } + // Game Corner + for (int offset : romEntry.staticPokemonGameCorner.keySet()) { + statics.add(pokes[rom[offset] & 0xFF]); + } + } + return statics; + } + + @Override + public boolean setStaticPokemon(List<Pokemon> staticPokemon) { + if (romEntry.getValue("StaticPokemonSupport") == 0) { + return false; + } + if (!havePatchedFleeing) { + patchFleeing(); + } + if (staticPokemon.size() != romEntry.staticPokemonSingle.size() + romEntry.staticPokemonGameCorner.size()) { + return false; + } + + Iterator<Pokemon> statics = staticPokemon.iterator(); + for (int offset : romEntry.staticPokemonSingle) { + rom[offset] = (byte) statics.next().number; + } + + int gcNameLength = romEntry.getValue("GameCornerPokemonNameLength"); + + // Sort out static Pokemon + for (int offset : romEntry.staticPokemonGameCorner.keySet()) { + rom[offset] = (byte) statics.next().number; + rom[offset + Gen2Constants.gameCornerRepeatPKIndex1] = rom[offset]; + rom[offset + Gen2Constants.gameCornerRepeatPKIndex2] = rom[offset]; + writePaddedPokemonName(pokes[rom[offset] & 0xFF].name, gcNameLength, + romEntry.staticPokemonGameCorner.get(offset)); + } + + // Copies? + for (int offset : romEntry.staticPokemonCopy.keySet()) { + int copyTo = romEntry.staticPokemonCopy.get(offset); + rom[copyTo] = rom[offset]; + } + return true; + } + + @Override + public boolean canChangeStaticPokemon() { + return (romEntry.getValue("StaticPokemonSupport") > 0); + } + + @Override + public List<Pokemon> bannedForStaticPokemon() { + return Arrays.asList(pokes[Gen2Constants.unownIndex]); // Unown banned + } + + private void writePaddedPokemonName(String name, int length, int offset) { + String paddedName = String.format("%-" + length + "s", name); + byte[] rawData = traduire(paddedName); + for (int i = 0; i < length; i++) { + rom[offset + i] = rawData[i]; + } + } + + @Override + public List<Integer> getTMMoves() { + List<Integer> tms = new ArrayList<Integer>(); + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen2Constants.tmCount; i++) { + tms.add(rom[offset + (i - 1)] & 0xFF); + } + return tms; + } + + @Override + public List<Integer> getHMMoves() { + List<Integer> hms = new ArrayList<Integer>(); + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen2Constants.hmCount; i++) { + hms.add(rom[offset + Gen2Constants.tmCount + (i - 1)] & 0xFF); + } + return hms; + } + + @Override + public void setTMMoves(List<Integer> moveIndexes) { + int offset = romEntry.getValue("TMMovesOffset"); + for (int i = 1; i <= Gen2Constants.tmCount; i++) { + rom[offset + (i - 1)] = moveIndexes.get(i - 1).byteValue(); + } + + // TM Text + String[] moveNames = readMoveNames(); + for (TMTextEntry tte : romEntry.tmTexts) { + String moveName = moveNames[moveIndexes.get(tte.number - 1)]; + String text = tte.template.replace("%m", moveName); + writeFixedLengthScriptString(text, tte.offset, lengthOfStringAt(tte.offset)); + } + } + + @Override + public int getTMCount() { + return Gen2Constants.tmCount; + } + + @Override + public int getHMCount() { + return Gen2Constants.hmCount; + } + + @Override + public Map<Pokemon, boolean[]> getTMHMCompatibility() { + Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") + (i - 1) * Gen2Constants.baseStatsEntrySize; + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen2Constants.tmCount + Gen2Constants.hmCount + 1]; + for (int j = 0; j < 8; j++) { + readByteIntoFlags(flags, j * 8 + 1, baseStatsOffset + Gen2Constants.bsTMHMCompatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData) { + for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") + (pkmn.number - 1) + * Gen2Constants.baseStatsEntrySize; + for (int j = 0; j < 8; j++) { + if (!romEntry.isCrystal || j != 7) { + rom[baseStatsOffset + Gen2Constants.bsTMHMCompatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } else { + // Move tutor data + // bits 1,2,3 of byte 7 + int changedByte = getByteFromFlags(flags, j * 8 + 1) & 0xFF; + int currentByte = rom[baseStatsOffset + Gen2Constants.bsTMHMCompatOffset + j]; + changedByte |= ((currentByte >> 1) & 0x01) << 1; + changedByte |= ((currentByte >> 2) & 0x01) << 2; + changedByte |= ((currentByte >> 3) & 0x01) << 3; + rom[baseStatsOffset + 0x18 + j] = (byte) changedByte; + } + } + } + } + + @Override + public boolean hasMoveTutors() { + return romEntry.isCrystal; + } + + @Override + public List<Integer> getMoveTutorMoves() { + if (romEntry.isCrystal) { + List<Integer> mtMoves = new ArrayList<Integer>(); + for (int offset : romEntry.arrayEntries.get("MoveTutorMoves")) { + mtMoves.add(rom[offset] & 0xFF); + } + return mtMoves; + } + return new ArrayList<Integer>(); + } + + @Override + public void setMoveTutorMoves(List<Integer> moves) { + if (!romEntry.isCrystal) { + return; + } + if (moves.size() != 3) { + return; + } + Iterator<Integer> mvList = moves.iterator(); + for (int offset : romEntry.arrayEntries.get("MoveTutorMoves")) { + rom[offset] = mvList.next().byteValue(); + } + + // Construct a new menu + if (romEntry.getValue("MoveTutorMenuOffset") > 0 && romEntry.getValue("MoveTutorMenuNewSpace") > 0) { + String[] moveNames = readMoveNames(); + String[] names = new String[] { moveNames[moves.get(0)], moveNames[moves.get(1)], moveNames[moves.get(2)], + Gen2Constants.mtMenuCancelString }; + int menuOffset = romEntry.getValue("MoveTutorMenuNewSpace"); + rom[menuOffset++] = Gen2Constants.mtMenuInitByte; + rom[menuOffset++] = 0x4; + for (int i = 0; i < 4; i++) { + byte[] trans = traduire(names[i]); + System.arraycopy(trans, 0, rom, menuOffset, trans.length); + menuOffset += trans.length; + rom[menuOffset++] = GBConstants.stringTerminator; + } + int pointerOffset = romEntry.getValue("MoveTutorMenuOffset"); + writeWord(pointerOffset, makeGBPointer(romEntry.getValue("MoveTutorMenuNewSpace"))); + } + } + + @Override + public Map<Pokemon, boolean[]> getMoveTutorCompatibility() { + if (!romEntry.isCrystal) { + return new TreeMap<Pokemon, boolean[]>(); + } + Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") + (i - 1) * Gen2Constants.baseStatsEntrySize; + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[4]; + int mtByte = rom[baseStatsOffset + Gen2Constants.bsMTCompatOffset] & 0xFF; + for (int j = 1; j <= 3; j++) { + flags[j] = ((mtByte >> j) & 0x01) > 0; + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData) { + if (!romEntry.isCrystal) { + return; + } + for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int baseStatsOffset = romEntry.getValue("PokemonStatsOffset") + (pkmn.number - 1) + * Gen2Constants.baseStatsEntrySize; + int origMtByte = rom[baseStatsOffset + Gen2Constants.bsMTCompatOffset] & 0xFF; + int mtByte = origMtByte & 0x01; + for (int j = 1; j <= 3; j++) { + mtByte |= flags[j] ? (1 << j) : 0; + } + rom[baseStatsOffset + Gen2Constants.bsMTCompatOffset] = (byte) mtByte; + } + } + + @Override + public String getROMName() { + if (isVietCrystal) { + return Gen2Constants.vietCrystalROMName; + } + return "Pokemon " + romEntry.name; + } + + @Override + public String getROMCode() { + return romEntry.romCode; + } + + @Override + public String getSupportLevel() { + return "Complete"; + } + + @Override + public boolean hasTimeBasedEncounters() { + return true; // All GSC do + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + int pointersOffset = romEntry.getValue("PokemonMovesetsTableOffset"); + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + int pointer = readWord(pointersOffset + (i - 1) * 2); + int realPointer = calculateOffset(bankOf(pointersOffset), pointer); + Pokemon pkmn = pokes[i]; + int thisPoke = i; + while (rom[realPointer] != 0) { + int method = rom[realPointer] & 0xFF; + int otherPoke = rom[realPointer + 2 + (method == 5 ? 1 : 0)] & 0xFF; + EvolutionType type = EvolutionType.fromIndex(2, method); + int extraInfo = 0; + if (type == EvolutionType.TRADE) { + int itemNeeded = rom[realPointer + 1] & 0xFF; + if (itemNeeded != 0xFF) { + type = EvolutionType.TRADE_ITEM; + extraInfo = itemNeeded; + } + } else if (type == EvolutionType.LEVEL_ATTACK_HIGHER) { + int tyrogueCond = rom[realPointer + 2] & 0xFF; + if (tyrogueCond == 2) { + type = EvolutionType.LEVEL_DEFENSE_HIGHER; + } else if (tyrogueCond == 3) { + type = EvolutionType.LEVEL_ATK_DEF_SAME; + } + extraInfo = rom[realPointer + 1] & 0xFF; + } else if (type == EvolutionType.HAPPINESS) { + int happCond = rom[realPointer + 1] & 0xFF; + if (happCond == 2) { + type = EvolutionType.HAPPINESS_DAY; + } else if (happCond == 3) { + type = EvolutionType.HAPPINESS_NIGHT; + } + } else { + extraInfo = rom[realPointer + 1] & 0xFF; + } + Evolution evo = new Evolution(pokes[thisPoke], pokes[otherPoke], true, type, extraInfo); + if (!pkmn.evolutionsFrom.contains(evo)) { + pkmn.evolutionsFrom.add(evo); + pokes[otherPoke].evolutionsTo.add(evo); + } + realPointer += (method == 5 ? 4 : 3); + } + // split evos don't carry stats + if (pkmn.evolutionsFrom.size() > 1) { + for (Evolution e : pkmn.evolutionsFrom) { + e.carryStats = false; + } + } + } + } + + @Override + public void removeTradeEvolutions(boolean changeMoveEvos) { + // no move evos, so no need to check for those + log("--Removing Trade Evolutions--"); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + for (Evolution evol : pkmn.evolutionsFrom) { + if (evol.type == EvolutionType.TRADE || evol.type == EvolutionType.TRADE_ITEM) { + // change + if (evol.from.number == Gen2Constants.slowpokeIndex) { + // Slowpoke: Make water stone => Slowking + evol.type = EvolutionType.STONE; + evol.extraInfo = 24; // water stone + logEvoChangeStone(evol.from.name, evol.to.name, itemNames[24]); + } else if (evol.from.number == Gen2Constants.seadraIndex) { + // Seadra: level 40 + evol.type = EvolutionType.LEVEL; + evol.extraInfo = 40; // level + logEvoChangeLevel(evol.from.name, evol.to.name, 40); + } else if (evol.from.number == Gen2Constants.poliwhirlIndex || evol.type == EvolutionType.TRADE) { + // Poliwhirl or any of the original 4 trade evos + // Level 37 + evol.type = EvolutionType.LEVEL; + evol.extraInfo = 37; // level + logEvoChangeLevel(evol.from.name, evol.to.name, 37); + } else { + // A new trade evo of a single stage Pokemon + // level 30 + evol.type = EvolutionType.LEVEL; + evol.extraInfo = 30; // level + logEvoChangeLevel(evol.from.name, evol.to.name, 30); + } + } + } + } + } + logBlankLine(); + + } + + @Override + public List<String> getTrainerNames() { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = romEntry.getValue("TrainerClassAmount"); + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount + 1]; + for (int i = 1; i <= traineramount; i++) { + int pointer = readWord(traineroffset + (i - 1) * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), pointer); + } + + List<String> allTrainers = new ArrayList<String>(); + for (int i = 1; i <= traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + for (int trnum = 0; trnum < limit; trnum++) { + String name = readVariableLengthString(offs); + allTrainers.add(name); + offs += name.length() + 1; + int dataType = rom[offs] & 0xFF; + offs++; + while ((rom[offs] & 0xFF) != 0xFF) { + offs += 2; + if (dataType == 2 || dataType == 3) { + offs++; + } + if (dataType % 2 == 1) { + offs += 4; + } + } + offs++; + } + } + return allTrainers; + } + + @Override + public void setTrainerNames(List<String> trainerNames) { + if (romEntry.getValue("CanChangeTrainerText") != 0) { + int traineroffset = romEntry.getValue("TrainerDataTableOffset"); + int traineramount = romEntry.getValue("TrainerClassAmount"); + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + + int[] pointers = new int[traineramount + 1]; + for (int i = 1; i <= traineramount; i++) { + int pointer = readWord(traineroffset + (i - 1) * 2); + pointers[i] = calculateOffset(bankOf(traineroffset), pointer); + } + // Build up new trainer data using old as a guideline. + int[] offsetsInNew = new int[traineramount + 1]; + int oInNewCurrent = 0; + Iterator<String> allTrainers = trainerNames.iterator(); + ByteArrayOutputStream newData = new ByteArrayOutputStream(); + try { + for (int i = 1; i <= traineramount; i++) { + int offs = pointers[i]; + int limit = trainerclasslimits[i]; + offsetsInNew[i] = oInNewCurrent; + for (int trnum = 0; trnum < limit; trnum++) { + String name = readVariableLengthString(offs); + String newName = allTrainers.next(); + byte[] newNameStr = translateString(newName); + newData.write(newNameStr); + newData.write(GBConstants.stringTerminator); + oInNewCurrent += newNameStr.length + 1; + offs += internalStringLength(name) + 1; + int dataType = rom[offs] & 0xFF; + offs++; + newData.write(dataType); + oInNewCurrent++; + while ((rom[offs] & 0xFF) != 0xFF) { + newData.write(rom, offs, 2); + oInNewCurrent += 2; + offs += 2; + if (dataType == 2 || dataType == 3) { + newData.write(rom, offs, 1); + oInNewCurrent += 1; + offs++; + } + if (dataType % 2 == 1) { + newData.write(rom, offs, 4); + oInNewCurrent += 4; + offs += 4; + } + } + newData.write(0xFF); + oInNewCurrent++; + offs++; + } + } + + // Copy new data into ROM + byte[] newTrainerData = newData.toByteArray(); + int tdBase = pointers[1]; + System.arraycopy(newTrainerData, 0, rom, pointers[1], newTrainerData.length); + + // Finally, update the pointers + for (int i = 2; i <= traineramount; i++) { + int newOffset = tdBase + offsetsInNew[i]; + writeWord(traineroffset + (i - 1) * 2, makeGBPointer(newOffset)); + } + } catch (IOException ex) { + // This should never happen, but abort if it does. + } + } + + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.MAX_LENGTH_WITH_CLASS; + } + + @Override + public int maxTrainerNameLength() { + // line size minus one for space + return Gen2Constants.maxTrainerNameLength; + } + + @Override + public List<Integer> getTCNameLengthsByTrainer() { + int traineramount = romEntry.getValue("TrainerClassAmount"); + int[] trainerclasslimits = romEntry.arrayEntries.get("TrainerDataClassCounts"); + List<String> tcNames = this.getTrainerClassNames(); + List<Integer> tcLengthsByT = new ArrayList<Integer>(); + + for (int i = 1; i <= traineramount; i++) { + int len = internalStringLength(tcNames.get(i - 1)); + for (int k = 0; k < trainerclasslimits[i]; k++) { + tcLengthsByT.add(len); + } + } + + return tcLengthsByT; + } + + @Override + public List<String> getTrainerClassNames() { + int amount = romEntry.getValue("TrainerClassAmount"); + int offset = romEntry.getValue("TrainerClassNamesOffset"); + List<String> trainerClassNames = new ArrayList<String>(); + for (int j = 0; j < amount; j++) { + String name = readVariableLengthString(offset); + offset += lengthOfStringAt(offset) + 1; + trainerClassNames.add(name); + } + return trainerClassNames; + } + + @Override + public void setTrainerClassNames(List<String> trainerClassNames) { + if (romEntry.getValue("CanChangeTrainerText") != 0) { + int amount = romEntry.getValue("TrainerClassAmount"); + int offset = romEntry.getValue("TrainerClassNamesOffset"); + Iterator<String> trainerClassNamesI = trainerClassNames.iterator(); + for (int j = 0; j < amount; j++) { + int len = lengthOfStringAt(offset) + 1; + String newName = trainerClassNamesI.next(); + writeFixedLengthString(newName, offset, len); + offset += len; + } + } + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return true; + } + + @Override + public String getDefaultExtension() { + return "gbc"; + } + + @Override + public int abilitiesPerPokemon() { + return 0; + } + + @Override + public int highestAbilityIndex() { + return 0; + } + + @Override + public int internalStringLength(String string) { + return translateString(string).length; + } + + @Override + public int miscTweaksAvailable() { + int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); + if (romEntry.codeTweaks.get("BWXPTweak") != null) { + available |= MiscTweak.BW_EXP_PATCH.getValue(); + } + if (romEntry.getValue("TextDelayFunctionOffset") != 0) { + available |= MiscTweak.FASTEST_TEXT.getValue(); + } + if (romEntry.arrayEntries.containsKey("CatchingTutorialOffsets")) { + available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); + } + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.BW_EXP_PATCH) { + applyBWEXPPatch(); + } else if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestTextPatch(); + } else if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { + applyCamelCaseNames(); + } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { + randomizeCatchingTutorial(); + } + } + + private void randomizeCatchingTutorial() { + if (romEntry.arrayEntries.containsKey("CatchingTutorialOffsets")) { + // Pick a pokemon + int pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; + while (pokemon == Gen2Constants.unownIndex) { + // Unown is banned + pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; + } + + int[] offsets = romEntry.arrayEntries.get("CatchingTutorialOffsets"); + for (int offset : offsets) { + rom[offset] = (byte) pokemon; + } + } + + } + + private void applyBWEXPPatch() { + String patchName = romEntry.codeTweaks.get("BWXPTweak"); + if (patchName == null) { + return; + } + + try { + FileFunctions.applyPatch(rom, patchName); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void applyFastestTextPatch() { + if (romEntry.getValue("TextDelayFunctionOffset") != 0) { + rom[romEntry.getValue("TextDelayFunctionOffset")] = (byte) GBConstants.gbZ80Ret; + } + } + + @Override + public void applySignature() { + // Intro sprite + + // Pick a pokemon + int pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; + while (pokemon == Gen2Constants.unownIndex) { + // Unown is banned + pokemon = this.random.nextInt(Gen2Constants.pokemonCount) + 1; + } + + rom[romEntry.getValue("IntroSpriteOffset")] = (byte) pokemon; + rom[romEntry.getValue("IntroCryOffset")] = (byte) pokemon; + + } + + @Override + public ItemList getAllowedItems() { + return Gen2Constants.allowedItems; + } + + @Override + public ItemList getNonBadItems() { + return Gen2Constants.nonBadItems; + } + + private void loadItemNames() { + itemNames = new String[256]; + itemNames[0] = "glitch"; + // trying to emulate pretty much what the game does here + // normal items + int origOffset = romEntry.getValue("ItemNamesOffset"); + int itemNameOffset = origOffset; + for (int index = 1; index <= 0x100; index++) { + if (itemNameOffset / GBConstants.bankSize > origOffset / GBConstants.bankSize) { + // the game would continue making its merry way into VRAM here, + // but we don't have VRAM to simulate. + // just give up. + break; + } + int startOfText = itemNameOffset; + while ((rom[itemNameOffset] & 0xFF) != GBConstants.stringTerminator) { + itemNameOffset++; + } + itemNameOffset++; + itemNames[index % 256] = readFixedLengthString(startOfText, 20); + } + } + + @Override + public String[] getItemNames() { + return itemNames; + } + + private void patchFleeing() { + havePatchedFleeing = true; + int offset = romEntry.getValue("FleeingDataOffset"); + rom[offset] = (byte) 0xFF; + rom[offset + Gen2Constants.fleeingSetTwoOffset] = (byte) 0xFF; + rom[offset + Gen2Constants.fleeingSetThreeOffset] = (byte) 0xFF; + } + + private void loadLandmarkNames() { + + int lmOffset = romEntry.getValue("LandmarkTableOffset"); + int lmBank = bankOf(lmOffset); + int lmCount = romEntry.getValue("LandmarkCount"); + + landmarkNames = new String[lmCount]; + + for (int i = 0; i < lmCount; i++) { + int lmNameOffset = calculateOffset(lmBank, readWord(lmOffset + i * 4 + 2)); + landmarkNames[i] = readVariableLengthString(lmNameOffset).replace("\\x1F", " "); + } + + } + + private void preprocessMaps() { + itemOffs = new ArrayList<Integer>(); + + int mhOffset = romEntry.getValue("MapHeaders"); + int mapGroupCount = Gen2Constants.mapGroupCount; + int mapsInLastGroup = Gen2Constants.mapsInLastGroup; + int mhBank = bankOf(mhOffset); + mapNames = new String[mapGroupCount + 1][100]; + + int[] groupOffsets = new int[mapGroupCount]; + for (int i = 0; i < mapGroupCount; i++) { + groupOffsets[i] = calculateOffset(mhBank, readWord(mhOffset + i * 2)); + } + + // Read maps + for (int mg = 0; mg < mapGroupCount; mg++) { + int offset = groupOffsets[mg]; + int maxOffset = (mg == mapGroupCount - 1) ? (mhBank + 1) * GBConstants.bankSize : groupOffsets[mg + 1]; + int map = 0; + int maxMap = (mg == mapGroupCount - 1) ? mapsInLastGroup : Integer.MAX_VALUE; + while (offset < maxOffset && map < maxMap) { + processMapAt(offset, mg + 1, map + 1); + offset += 9; + map++; + } + } + } + + private void processMapAt(int offset, int mapBank, int mapNumber) { + + // second map header + int smhBank = rom[offset] & 0xFF; + int smhPointer = readWord(offset + 3); + int smhOffset = calculateOffset(smhBank, smhPointer); + + // map name + int mapLandmark = rom[offset + 5] & 0xFF; + mapNames[mapBank][mapNumber] = landmarkNames[mapLandmark]; + + // event header + // event header is in same bank as script header + int ehBank = rom[smhOffset + 6] & 0xFF; + int ehPointer = readWord(smhOffset + 9); + int ehOffset = calculateOffset(ehBank, ehPointer); + + // skip over filler + ehOffset += 2; + + // warps + int warpCount = rom[ehOffset++] & 0xFF; + // warps are skipped + ehOffset += warpCount * 5; + + // xy triggers + int triggerCount = rom[ehOffset++] & 0xFF; + // xy triggers are skipped + ehOffset += triggerCount * 8; + + // signposts + int signpostCount = rom[ehOffset++] & 0xFF; + // we do care about these + for (int sp = 0; sp < signpostCount; sp++) { + // type=7 are hidden items + int spType = rom[ehOffset + sp * 5 + 2] & 0xFF; + if (spType == 7) { + // get event pointer + int spPointer = readWord(ehOffset + sp * 5 + 3); + int spOffset = calculateOffset(ehBank, spPointer); + // item is at spOffset+2 (first two bytes are the flag id) + itemOffs.add(spOffset + 2); + } + } + // now skip past them + ehOffset += signpostCount * 5; + + // visible objects/people + int peopleCount = rom[ehOffset++] & 0xFF; + // we also care about these + for (int p = 0; p < peopleCount; p++) { + // color_function & 1 = 1 if itemball + int pColorFunction = rom[ehOffset + p * 13 + 7]; + if ((pColorFunction & 1) == 1) { + // get event pointer + int pPointer = readWord(ehOffset + p * 13 + 9); + int pOffset = calculateOffset(ehBank, pPointer); + // item is at the pOffset for non-hidden items + itemOffs.add(pOffset); + } + } + + } + + @Override + public List<Integer> getRequiredFieldTMs() { + return Gen2Constants.requiredFieldTMs; + } + + @Override + public List<Integer> getCurrentFieldTMs() { + List<Integer> fieldTMs = new ArrayList<Integer>(); + + for (int offset : itemOffs) { + int itemHere = rom[offset] & 0xFF; + if (Gen2Constants.allowedItems.isTM(itemHere)) { + int thisTM = 0; + if (itemHere >= Gen2Constants.tmBlockOneIndex + && itemHere < Gen2Constants.tmBlockOneIndex + Gen2Constants.tmBlockOneSize) { + thisTM = itemHere - Gen2Constants.tmBlockOneIndex + 1; + } else if (itemHere >= Gen2Constants.tmBlockTwoIndex + && itemHere < Gen2Constants.tmBlockTwoIndex + Gen2Constants.tmBlockTwoSize) { + thisTM = itemHere - Gen2Constants.tmBlockTwoIndex + 1 + Gen2Constants.tmBlockOneSize; // TM + // block + // 2 + // offset + } else { + thisTM = itemHere - Gen2Constants.tmBlockThreeIndex + 1 + Gen2Constants.tmBlockOneSize + + Gen2Constants.tmBlockTwoSize; // TM block 3 offset + } + // hack for the bug catching contest repeat TM28 + if (fieldTMs.contains(thisTM) == false) { + fieldTMs.add(thisTM); + } + } + } + return fieldTMs; + } + + @Override + public void setFieldTMs(List<Integer> fieldTMs) { + Iterator<Integer> iterTMs = fieldTMs.iterator(); + int[] givenTMs = new int[256]; + + for (int offset : itemOffs) { + int itemHere = rom[offset] & 0xFF; + if (Gen2Constants.allowedItems.isTM(itemHere)) { + // Cache replaced TMs to duplicate bug catching contest TM + if (givenTMs[itemHere] != 0) { + rom[offset] = (byte) givenTMs[itemHere]; + } else { + // Replace this with a TM from the list + int tm = iterTMs.next(); + if (tm >= 1 && tm <= Gen2Constants.tmBlockOneSize) { + tm += Gen2Constants.tmBlockOneIndex - 1; + } else if (tm >= Gen2Constants.tmBlockOneSize + 1 + && tm <= Gen2Constants.tmBlockOneSize + Gen2Constants.tmBlockTwoSize) { + tm += Gen2Constants.tmBlockTwoIndex - 1 - Gen2Constants.tmBlockOneSize; + } else { + tm += Gen2Constants.tmBlockThreeIndex - 1 - Gen2Constants.tmBlockOneSize + - Gen2Constants.tmBlockTwoSize; + } + givenTMs[itemHere] = tm; + rom[offset] = (byte) tm; + } + } + } + } + + @Override + public List<Integer> getRegularFieldItems() { + List<Integer> fieldItems = new ArrayList<Integer>(); + + for (int offset : itemOffs) { + int itemHere = rom[offset] & 0xFF; + if (Gen2Constants.allowedItems.isAllowed(itemHere) && !(Gen2Constants.allowedItems.isTM(itemHere))) { + fieldItems.add(itemHere); + } + } + return fieldItems; + } + + @Override + public void setRegularFieldItems(List<Integer> items) { + Iterator<Integer> iterItems = items.iterator(); + + for (int offset : itemOffs) { + int itemHere = rom[offset] & 0xFF; + if (Gen2Constants.allowedItems.isAllowed(itemHere) && !(Gen2Constants.allowedItems.isTM(itemHere))) { + // Replace it + rom[offset] = (byte) (iterItems.next().intValue()); + } + } + + } + + @Override + public List<IngameTrade> getIngameTrades() { + List<IngameTrade> trades = new ArrayList<IngameTrade>(); + + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int nicknameLength = romEntry.getValue("TradeNameLength"); + int otLength = romEntry.getValue("TradeOTLength"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = nicknameLength + otLength + 10; + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = new IngameTrade(); + int entryOffset = tableOffset + entry * entryLength; + trade.requestedPokemon = pokes[rom[entryOffset + 1] & 0xFF]; + trade.givenPokemon = pokes[rom[entryOffset + 2] & 0xFF]; + trade.nickname = readString(entryOffset + 3, nicknameLength); + int atkdef = rom[entryOffset + 3 + nicknameLength] & 0xFF; + int spdspc = rom[entryOffset + 4 + nicknameLength] & 0xFF; + trade.ivs = new int[] { (atkdef >> 4) & 0xF, atkdef & 0xF, (spdspc >> 4) & 0xF, spdspc & 0xF }; + trade.item = rom[entryOffset + 5 + nicknameLength] & 0xFF; + trade.otId = readWord(entryOffset + 6 + nicknameLength); + trade.otName = readString(entryOffset + 8 + nicknameLength, otLength); + trades.add(trade); + } + + return trades; + + } + + @Override + public void setIngameTrades(List<IngameTrade> trades) { + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int nicknameLength = romEntry.getValue("TradeNameLength"); + int otLength = romEntry.getValue("TradeOTLength"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = nicknameLength + otLength + 9; + if (entryLength % 2 != 0) { + entryLength++; + } + int tradeOffset = 0; + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = trades.get(tradeOffset++); + int entryOffset = tableOffset + entry * entryLength; + rom[entryOffset + 1] = (byte) trade.requestedPokemon.number; + rom[entryOffset + 2] = (byte) trade.givenPokemon.number; + if (romEntry.getValue("CanChangeTrainerText") > 0) { + writeFixedLengthString(trade.nickname, entryOffset + 3, nicknameLength); + } + rom[entryOffset + 3 + nicknameLength] = (byte) (trade.ivs[0] << 4 | trade.ivs[1]); + rom[entryOffset + 4 + nicknameLength] = (byte) (trade.ivs[2] << 4 | trade.ivs[3]); + rom[entryOffset + 5 + nicknameLength] = (byte) trade.item; + writeWord(entryOffset + 6 + nicknameLength, trade.otId); + if (romEntry.getValue("CanChangeTrainerText") > 0) { + writeFixedLengthString(trade.otName, entryOffset + 8 + nicknameLength, otLength); + } + // remove gender req + rom[entryOffset + 8 + nicknameLength + otLength] = 0; + + } + } + + @Override + public boolean hasDVs() { + return true; + } + + @Override + public int generationOfPokemon() { + return 2; + } + + @Override + public void removeEvosForPokemonPool() { + List<Pokemon> pokemonIncluded = this.mainPokemonList; + Set<Evolution> keepEvos = new HashSet<Evolution>(); + for (Pokemon pk : pokes) { + if (pk != null) { + keepEvos.clear(); + for (Evolution evol : pk.evolutionsFrom) { + if (pokemonIncluded.contains(evol.from) && pokemonIncluded.contains(evol.to)) { + keepEvos.add(evol); + } else { + evol.to.evolutionsTo.remove(evol); + } + } + pk.evolutionsFrom.retainAll(keepEvos); + } + } + } + + private void writeEvosAndMovesLearnt(boolean writeEvos, Map<Pokemon, List<MoveLearnt>> movesets) { + // this assumes that the evo/attack pointers & data + // are at the end of the bank + // which, in every clean G/S/C rom supported, they are + // specify null to either argument to copy old values + int movesEvosStart = romEntry.getValue("PokemonMovesetsTableOffset"); + int movesEvosBank = bankOf(movesEvosStart); + byte[] pointerTable = new byte[Gen2Constants.pokemonCount * 2]; + int startOfNextBank = ((movesEvosStart / GBConstants.bankSize) + 1) * GBConstants.bankSize; + int dataBlockSize = startOfNextBank - (movesEvosStart + pointerTable.length); + int dataBlockOffset = movesEvosStart + pointerTable.length; + byte[] dataBlock = new byte[dataBlockSize]; + int offsetInData = 0; + for (int i = 1; i <= Gen2Constants.pokemonCount; i++) { + // determine pointer + int oldDataOffset = calculateOffset(movesEvosBank, readWord(movesEvosStart + (i - 1) * 2)); + int offsetStart = dataBlockOffset + offsetInData; + boolean evoWritten = false; + if (!writeEvos) { + // copy old + int evoOffset = oldDataOffset; + while (rom[evoOffset] != 0x00) { + int method = rom[evoOffset] & 0xFF; + int limiter = (method == 5) ? 4 : 3; + for (int b = 0; b < limiter; b++) { + dataBlock[offsetInData++] = rom[evoOffset++]; + } + evoWritten = true; + } + } else { + for (Evolution evo : pokes[i].evolutionsFrom) { + // write evos + dataBlock[offsetInData++] = (byte) evo.type.toIndex(2); + if (evo.type == EvolutionType.LEVEL || evo.type == EvolutionType.STONE + || evo.type == EvolutionType.TRADE_ITEM) { + // simple types + dataBlock[offsetInData++] = (byte) evo.extraInfo; + } else if (evo.type == EvolutionType.TRADE) { + // non-item trade + dataBlock[offsetInData++] = (byte) 0xFF; + } else if (evo.type == EvolutionType.HAPPINESS) { + // cond 01 + dataBlock[offsetInData++] = 0x01; + } else if (evo.type == EvolutionType.HAPPINESS_DAY) { + // cond 02 + dataBlock[offsetInData++] = 0x02; + } else if (evo.type == EvolutionType.HAPPINESS_NIGHT) { + // cond 03 + dataBlock[offsetInData++] = 0x03; + } else if (evo.type == EvolutionType.LEVEL_ATTACK_HIGHER) { + dataBlock[offsetInData++] = (byte) evo.extraInfo; + dataBlock[offsetInData++] = 0x01; + } else if (evo.type == EvolutionType.LEVEL_DEFENSE_HIGHER) { + dataBlock[offsetInData++] = (byte) evo.extraInfo; + dataBlock[offsetInData++] = 0x02; + } else if (evo.type == EvolutionType.LEVEL_ATK_DEF_SAME) { + dataBlock[offsetInData++] = (byte) evo.extraInfo; + dataBlock[offsetInData++] = 0x03; + } + dataBlock[offsetInData++] = (byte) evo.to.number; + evoWritten = true; + } + } + // can we reuse a terminator? + if (!evoWritten && offsetStart != dataBlockOffset) { + // reuse last pokemon's move terminator for our evos + offsetStart -= 1; + } else { + // write a terminator + dataBlock[offsetInData++] = 0x00; + } + // write table entry now that we're sure of its location + int pointerNow = makeGBPointer(offsetStart); + writeWord(pointerTable, (i - 1) * 2, pointerNow); + // moveset + if (movesets == null) { + // copy old + int movesOffset = oldDataOffset; + // move past evos + while (rom[movesOffset] != 0x00) { + int method = rom[movesOffset] & 0xFF; + movesOffset += (method == 5) ? 4 : 3; + } + movesOffset++; + // copy moves + while (rom[movesOffset] != 0x00) { + dataBlock[offsetInData++] = rom[movesOffset++]; + dataBlock[offsetInData++] = rom[movesOffset++]; + } + } else { + List<MoveLearnt> moves = movesets.get(pokes[i]); + for (MoveLearnt ml : moves) { + dataBlock[offsetInData++] = (byte) ml.level; + dataBlock[offsetInData++] = (byte) ml.move; + } + } + // terminator + dataBlock[offsetInData++] = 0x00; + } + // write new data + System.arraycopy(pointerTable, 0, rom, movesEvosStart, pointerTable.length); + System.arraycopy(dataBlock, 0, rom, dataBlockOffset, dataBlock.length); + } + + @Override + public boolean supportsFourStartingMoves() { + return (romEntry.getValue("SupportsFourStartingMoves") > 0); + } + + @Override + public List<Integer> getGameBreakingMoves() { + // add OHKO moves for gen2 because x acc is still broken + return Gen2Constants.brokenMoves; + } + + @Override + public List<Integer> getFieldMoves() { + // cut, fly, surf, strength, flash, + // dig, teleport, whirlpool, waterfall, + // rock smash, headbutt, sweet scent + // not softboiled or milk drink + return Gen2Constants.fieldMoves; + } + + @Override + public List<Integer> getEarlyRequiredHMMoves() { + // just cut + return Gen2Constants.earlyRequiredHMMoves; + } + + @Override + public BufferedImage getMascotImage() { + Pokemon mascot = randomPokemon(); + while (mascot.number == Gen2Constants.unownIndex) { + // Unown is banned as handling it would add a ton of extra effort. + mascot = randomPokemon(); + } + + // Each Pokemon has a front and back pic with a bank and a pointer (3*2 + // = 6) + // There is no zero-entry. + int picPointer = romEntry.getValue("PicPointers") + (mascot.number - 1) * 6; + int picWidth = mascot.picDimensions & 0x0F; + int picHeight = (mascot.picDimensions >> 4) & 0x0F; + + int picBank = (rom[picPointer] & 0xFF); + if (romEntry.isCrystal) { + // Crystal pic banks are offset by x36 for whatever reason. + picBank += 0x36; + } else { + // Hey, G/S are dumb too! Arbitrarily redirected bank numbers. + if (picBank == 0x13) { + picBank = 0x1F; + } else if (picBank == 0x14) { + picBank = 0x20; + } else if (picBank == 0x1F) { + picBank = 0x2E; + } + } + int picOffset = calculateOffset(picBank, readWord(picPointer + 1)); + + Gen2Decmp mscSprite = new Gen2Decmp(rom, picOffset, picWidth, picHeight); + int w = picWidth * 8; + int h = picHeight * 8; + + // Palette? + // Two colors per Pokemon + two more for shiny, unlike pics there is a + // zero-entry. + // Black and white are left alone at the start and end of the palette. + int[] palette = new int[] { 0xFFFFFFFF, 0xFFAAAAAA, 0xFF666666, 0xFF000000 }; + int paletteOffset = romEntry.getValue("PokemonPalettes") + mascot.number * 8; + if (random.nextInt(10) == 0) { + // Use shiny instead + paletteOffset += 4; + } + for (int i = 0; i < 2; i++) { + palette[i + 1] = GFXFunctions.conv16BitColorToARGB(readWord(paletteOffset + i * 2)); + } + + byte[] data = mscSprite.getFlattenedData(); + + BufferedImage bim = GFXFunctions.drawTiledImage(data, palette, w, h, 8); + + return bim; + } } diff --git a/src/com/dabomstew/pkrandom/romhandlers/Gen3RomHandler.java b/src/com/dabomstew/pkrandom/romhandlers/Gen3RomHandler.java index 1bc810f..801729e 100755 --- a/src/com/dabomstew/pkrandom/romhandlers/Gen3RomHandler.java +++ b/src/com/dabomstew/pkrandom/romhandlers/Gen3RomHandler.java @@ -63,3153 +63,2947 @@ import compressors.DSDecmp; public class Gen3RomHandler extends AbstractGBRomHandler { - public static class Factory extends RomHandler.Factory { - - @Override - public Gen3RomHandler create(Random random, PrintStream logStream) { - return new Gen3RomHandler(random, logStream); - } - - public boolean isLoadable(String filename) { - long fileLength = new File(filename).length(); - if (fileLength > 32 * 1024 * 1024) { - return false; - } - byte[] loaded = loadFilePartial(filename, 0x100000); - if (loaded.length == 0) { - // nope - return false; - } - return detectRomInner(loaded, (int) fileLength); - } - } - - public Gen3RomHandler(Random random) { - super(random, null); - } - - public Gen3RomHandler(Random random, PrintStream logStream) { - super(random, logStream); - } - - private static class RomEntry { - private String name; - private String romCode; - private String tableFile; - private int version; - private int romType; - private boolean copyStaticPokemon; - private Map<String, Integer> entries = new HashMap<String, Integer>(); - private Map<String, int[]> arrayEntries = new HashMap<String, int[]>(); - private List<StaticPokemon> staticPokemon = new ArrayList<StaticPokemon>(); - private List<TMOrMTTextEntry> tmmtTexts = new ArrayList<TMOrMTTextEntry>(); - - public RomEntry() { - - } - - public RomEntry(RomEntry toCopy) { - this.name = toCopy.name; - this.romCode = toCopy.romCode; - this.tableFile = toCopy.tableFile; - this.version = toCopy.version; - this.romType = toCopy.romType; - this.copyStaticPokemon = toCopy.copyStaticPokemon; - this.entries.putAll(toCopy.entries); - this.arrayEntries.putAll(toCopy.arrayEntries); - this.staticPokemon.addAll(toCopy.staticPokemon); - this.tmmtTexts.addAll(toCopy.tmmtTexts); - } - - private int getValue(String key) { - if (!entries.containsKey(key)) { - entries.put(key, 0); - } - return entries.get(key); - } - } - - private static class TMOrMTTextEntry { - private int number; - private int mapBank, mapNumber; - private int personNum; - private int offsetInScript; - private int actualOffset; - private String template; - private boolean isMoveTutor; - } - - private static List<RomEntry> roms; - - static { - loadROMInfo(); - } - - private static void loadROMInfo() { - roms = new ArrayList<RomEntry>(); - RomEntry current = null; - try { - Scanner sc = new Scanner( - FileFunctions.openConfig("gen3_offsets.ini"), "UTF-8"); - while (sc.hasNextLine()) { - String q = sc.nextLine().trim(); - if (q.contains("//")) { - q = q.substring(0, q.indexOf("//")).trim(); - } - if (!q.isEmpty()) { - if (q.startsWith("[") && q.endsWith("]")) { - // New rom - current = new RomEntry(); - current.name = q.substring(1, q.length() - 1); - roms.add(current); - } else { - String[] r = q.split("=", 2); - if (r.length == 1) { - System.err.println("invalid entry " + q); - continue; - } - if (r[1].endsWith("\r\n")) { - r[1] = r[1].substring(0, r[1].length() - 2); - } - r[1] = r[1].trim(); - // Static Pokemon? - if (r[0].equals("StaticPokemon[]")) { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - int[] offs = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - offs[c++] = parseRIInt(off); - } - current.staticPokemon.add(new StaticPokemon( - offs)); - } else { - int offs = parseRIInt(r[1]); - current.staticPokemon.add(new StaticPokemon( - offs)); - } - } else if (r[0].equals("TMText[]")) { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] parts = r[1].substring(1, - r[1].length() - 1).split(",", 6); - TMOrMTTextEntry tte = new TMOrMTTextEntry(); - tte.number = parseRIInt(parts[0]); - tte.mapBank = parseRIInt(parts[1]); - tte.mapNumber = parseRIInt(parts[2]); - tte.personNum = parseRIInt(parts[3]); - tte.offsetInScript = parseRIInt(parts[4]); - tte.template = parts[5]; - tte.isMoveTutor = false; - current.tmmtTexts.add(tte); - } - } else if (r[0].equals("MoveTutorText[]")) { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] parts = r[1].substring(1, - r[1].length() - 1).split(",", 6); - TMOrMTTextEntry tte = new TMOrMTTextEntry(); - tte.number = parseRIInt(parts[0]); - tte.mapBank = parseRIInt(parts[1]); - tte.mapNumber = parseRIInt(parts[2]); - tte.personNum = parseRIInt(parts[3]); - tte.offsetInScript = parseRIInt(parts[4]); - tte.template = parts[5]; - tte.isMoveTutor = true; - current.tmmtTexts.add(tte); - } - } else if (r[0].equals("Game")) { - current.romCode = r[1]; - } else if (r[0].equals("Version")) { - current.version = parseRIInt(r[1]); - } else if (r[0].equals("Type")) { - if (r[1].equalsIgnoreCase("Ruby")) { - current.romType = Gen3Constants.RomType_Ruby; - } else if (r[1].equalsIgnoreCase("Sapp")) { - current.romType = Gen3Constants.RomType_Sapp; - } else if (r[1].equalsIgnoreCase("Em")) { - current.romType = Gen3Constants.RomType_Em; - } else if (r[1].equalsIgnoreCase("FRLG")) { - current.romType = Gen3Constants.RomType_FRLG; - } else { - System.err.println("unrecognised rom type: " - + r[1]); - } - } else if (r[0].equals("TableFile")) { - current.tableFile = r[1]; - } else if (r[0].equals("CopyStaticPokemon")) { - int csp = parseRIInt(r[1]); - current.copyStaticPokemon = (csp > 0); - } else if (r[0].equals("CopyFrom")) { - for (RomEntry otherEntry : roms) { - if (r[1].equalsIgnoreCase(otherEntry.name)) { - // copy from here - current.arrayEntries - .putAll(otherEntry.arrayEntries); - current.entries.putAll(otherEntry.entries); - boolean cTT = (current - .getValue("CopyTMText") == 1); - if (current.copyStaticPokemon) { - current.staticPokemon - .addAll(otherEntry.staticPokemon); - current.entries.put( - "StaticPokemonSupport", 1); - } else { - current.entries.put( - "StaticPokemonSupport", 0); - } - if (cTT) { - current.tmmtTexts - .addAll(otherEntry.tmmtTexts); - } - current.tableFile = otherEntry.tableFile; - } - } - } else { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - if (offsets.length == 1 - && offsets[0].trim().isEmpty()) { - current.arrayEntries.put(r[0], new int[0]); - } else { - int[] offs = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - offs[c++] = parseRIInt(off); - } - current.arrayEntries.put(r[0], offs); - } - } else { - int offs = parseRIInt(r[1]); - current.entries.put(r[0], offs); - } - } - } - } - } - sc.close(); - } catch (FileNotFoundException e) { - } - - } - - 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 void loadTextTable(String filename) { - try { - Scanner sc = new Scanner( - FileFunctions.openConfig(filename + ".tbl"), "UTF-8"); - while (sc.hasNextLine()) { - String q = sc.nextLine(); - if (!q.trim().isEmpty()) { - String[] r = q.split("=", 2); - if (r[1].endsWith("\r\n")) { - r[1] = r[1].substring(0, r[1].length() - 2); - } - tb[Integer.parseInt(r[0], 16)] = r[1]; - d.put(r[1], (byte) Integer.parseInt(r[0], 16)); - } - } - sc.close(); - } catch (FileNotFoundException e) { - } - - } - - // This ROM's data - private Pokemon[] pokes, pokesInternal; - private List<Pokemon> pokemonList; - private int numRealPokemon; - private Move[] moves; - private boolean jamboMovesetHack; - private RomEntry romEntry; - private boolean havePatchedObedience; - public String[] tb; - public Map<String, Byte> d; - private String[] abilityNames; - private String[] itemNames; - private boolean mapLoadingDone; - private List<Integer> itemOffs; - private String[][] mapNames; - private boolean isRomHack; - private int[] internalToPokedex, pokedexToInternal; - private int pokedexCount; - private String[] pokeNames; - - @Override - public boolean detectRom(byte[] rom) { - return detectRomInner(rom, rom.length); - } - - private static boolean detectRomInner(byte[] rom, int romSize) { - if (romSize != Gen3Constants.size8M && romSize != Gen3Constants.size16M - && romSize != Gen3Constants.size32M) { - return false; // size check - } - // Special case for Emerald unofficial translation - if (romName(rom, Gen3Constants.unofficialEmeraldROMName)) { - // give it a rom code so it can be detected - rom[Gen3Constants.romCodeOffset] = 'B'; - rom[Gen3Constants.romCodeOffset + 1] = 'P'; - rom[Gen3Constants.romCodeOffset + 2] = 'E'; - rom[Gen3Constants.romCodeOffset + 3] = 'T'; - rom[Gen3Constants.headerChecksumOffset] = 0x66; - } - // Wild Pokemon header - if (find(rom, Gen3Constants.wildPokemonPointerPrefix) == -1) { - return false; - } - // Map Banks header - if (find(rom, Gen3Constants.mapBanksPointerPrefix) == -1) { - return false; - } - // Pokedex Order header - if (findMultiple(rom, Gen3Constants.pokedexOrderPointerPrefix).size() != 3) { - return false; - } - for (RomEntry re : roms) { - if (romCode(rom, re.romCode) - && (rom[Gen3Constants.romVersionOffset] & 0xFF) == re.version) { - return true; // match - } - } - return false; // GBA rom we don't support yet - } - - @Override - public void loadedRom() { - for (RomEntry re : roms) { - if (romCode(rom, re.romCode) && (rom[0xBC] & 0xFF) == re.version) { - romEntry = new RomEntry(re); // clone so we can modify - break; - } - } - - tb = new String[256]; - d = new HashMap<String, Byte>(); - isRomHack = false; - jamboMovesetHack = false; - - // Pokemon count stuff, needs to be available first - List<Integer> pokedexOrderPrefixes = findMultiple(rom, - Gen3Constants.pokedexOrderPointerPrefix); - romEntry.entries.put("PokedexOrder", - readPointer(pokedexOrderPrefixes.get(1) + 16)); - - // Pokemon names offset - if (romEntry.romType == Gen3Constants.RomType_Ruby - || romEntry.romType == Gen3Constants.RomType_Sapp) { - int baseNomOffset = find(rom, - Gen3Constants.rsPokemonNamesPointerSuffix); - romEntry.entries - .put("PokemonNames", readPointer(baseNomOffset - 4)); - romEntry.entries.put( - "FrontSprites", - readPointer(findPointerPrefixAndSuffix( - Gen3Constants.rsFrontSpritesPointerPrefix, - Gen3Constants.rsFrontSpritesPointerSuffix))); - romEntry.entries.put( - "PokemonPalettes", - readPointer(findPointerPrefixAndSuffix( - Gen3Constants.rsPokemonPalettesPointerPrefix, - Gen3Constants.rsPokemonPalettesPointerSuffix))); - } else { - romEntry.entries.put("PokemonNames", - readPointer(Gen3Constants.efrlgPokemonNamesPointer)); - romEntry.entries.put("MoveNames", - readPointer(Gen3Constants.efrlgMoveNamesPointer)); - romEntry.entries.put("AbilityNames", - readPointer(Gen3Constants.efrlgAbilityNamesPointer)); - romEntry.entries.put("ItemData", - readPointer(Gen3Constants.efrlgItemDataPointer)); - romEntry.entries.put("MoveData", - readPointer(Gen3Constants.efrlgMoveDataPointer)); - romEntry.entries.put("PokemonStats", - readPointer(Gen3Constants.efrlgPokemonStatsPointer)); - romEntry.entries.put("FrontSprites", - readPointer(Gen3Constants.efrlgFrontSpritesPointer)); - romEntry.entries.put("PokemonPalettes", - readPointer(Gen3Constants.efrlgPokemonPalettesPointer)); - romEntry.entries.put( - "MoveTutorCompatibility", - romEntry.getValue("MoveTutorData") - + romEntry.getValue("MoveTutorMoves") * 2); - } - - loadTextTable(romEntry.tableFile); - - if (romEntry.romCode.equals("BPRE") && romEntry.version == 0) { - basicBPRE10HackSupport(); - } - - loadPokemonNames(); - loadPokedex(); - loadPokemonStats(); - constructPokemonList(); - loadMoves(); - - // Get wild Pokemon offset - int baseWPOffset = findMultiple(rom, - Gen3Constants.wildPokemonPointerPrefix).get(0); - romEntry.entries.put("WildPokemon", readPointer(baseWPOffset + 12)); - - // map banks - int baseMapsOffset = findMultiple(rom, - Gen3Constants.mapBanksPointerPrefix).get(0); - romEntry.entries.put("MapHeaders", readPointer(baseMapsOffset + 12)); - this.determineMapBankSizes(); - - // map labels - if (romEntry.romType == Gen3Constants.RomType_FRLG) { - int baseMLOffset = find(rom, - Gen3Constants.frlgMapLabelsPointerPrefix); - romEntry.entries.put("MapLabels", readPointer(baseMLOffset + 12)); - } else { - int baseMLOffset = find(rom, - Gen3Constants.rseMapLabelsPointerPrefix); - romEntry.entries.put("MapLabels", readPointer(baseMLOffset + 12)); - } - - mapLoadingDone = false; - loadAbilityNames(); - loadItemNames(); - - } - - private int findPointerPrefixAndSuffix(String prefix, String suffix) { - if (prefix.length() % 2 != 0 || suffix.length() % 2 != 0) { - return -1; - } - byte[] searchPref = new byte[prefix.length() / 2]; - for (int i = 0; i < searchPref.length; i++) { - searchPref[i] = (byte) Integer.parseInt( - prefix.substring(i * 2, i * 2 + 2), 16); - } - byte[] searchSuff = new byte[suffix.length() / 2]; - for (int i = 0; i < searchSuff.length; i++) { - searchSuff[i] = (byte) Integer.parseInt( - suffix.substring(i * 2, i * 2 + 2), 16); - } - if (searchPref.length >= searchSuff.length) { - // Prefix first - List<Integer> offsets = RomFunctions.search(rom, searchPref); - if (offsets.size() == 0) { - return -1; - } - for (int prefOffset : offsets) { - if (prefOffset + 4 + searchSuff.length > rom.length) { - continue; // not enough room for this to be valid - } - int ptrOffset = prefOffset + searchPref.length; - int pointerValue = readPointer(ptrOffset); - if (pointerValue < 0 || pointerValue >= rom.length) { - // Not a valid pointer - continue; - } - boolean suffixMatch = true; - for (int i = 0; i < searchSuff.length; i++) { - if (rom[ptrOffset + 4 + i] != searchSuff[i]) { - suffixMatch = false; - break; - } - } - if (suffixMatch) { - return ptrOffset; - } - } - return -1; // No match - } else { - // Suffix first - List<Integer> offsets = RomFunctions.search(rom, searchSuff); - if (offsets.size() == 0) { - return -1; - } - for (int suffOffset : offsets) { - if (suffOffset - 4 - searchPref.length < 0) { - continue; // not enough room for this to be valid - } - int ptrOffset = suffOffset - 4; - int pointerValue = readPointer(ptrOffset); - if (pointerValue < 0 || pointerValue >= rom.length) { - // Not a valid pointer - continue; - } - boolean prefixMatch = true; - for (int i = 0; i < searchPref.length; i++) { - if (rom[ptrOffset - searchPref.length + i] != searchPref[i]) { - prefixMatch = false; - break; - } - } - if (prefixMatch) { - return ptrOffset; - } - } - return -1; // No match - } - } - - private void basicBPRE10HackSupport() { - if (basicBPRE10HackDetection()) { - this.isRomHack = true; - // NUMBER OF POKEMON DETECTION - - // this is the most annoying bit - // we'll try to get it from the pokemon names, - // and sanity check it using other things - // this of course means we can't support - // any hack with extended length names - - int iPokemonCount = 0; - int namesOffset = romEntry.getValue("PokemonNames"); - int nameLen = romEntry.getValue("PokemonNameLength"); - while (true) { - int nameOffset = namesOffset + (iPokemonCount + 1) * nameLen; - int nameStrLen = lengthOfStringAt(nameOffset); - if (nameStrLen > 0 && nameStrLen < nameLen - && rom[nameOffset] != 0) { - iPokemonCount++; - } else { - break; - } - } - - // Is there an unused egg slot at the end? - String lastName = readVariableLengthString(namesOffset - + iPokemonCount * nameLen); - if (lastName.equals("?") || lastName.equals("-")) { - iPokemonCount--; - } - - // Jambo's Moves Learnt table hack? - // need to check this before using moveset pointers - int movesetsTable; - if (readLong(0x3EB20) == 0x47084918) { - // Hack applied, adjust accordingly - int firstRoutinePtr = readPointer(0x3EB84); - movesetsTable = readPointer(firstRoutinePtr + 75); - jamboMovesetHack = true; - } else { - movesetsTable = readPointer(0x3EA7C); - jamboMovesetHack = false; - } - - // secondary check: moveset pointers - // if a slot has an invalid moveset pointer, it's not a real slot - // Before that, grab the moveset table from a known pointer to it. - romEntry.entries.put("PokemonMovesets", movesetsTable); - while (iPokemonCount >= 0) { - int movesetPtr = readPointer(movesetsTable + iPokemonCount * 4); - if (movesetPtr < 0 || movesetPtr >= rom.length) { - iPokemonCount--; - } else { - break; - } - } - - // sanity check: pokedex order - // pokedex entries have to be within 0-1023 - // even after extending the dex - // (at least with conventional methods) - // so if we run into an invalid one - // then we can cut off the count - int pdOffset = romEntry.getValue("PokedexOrder"); - for (int i = 1; i <= iPokemonCount; i++) { - int pdEntry = readWord(pdOffset + (i - 1) * 2); - if (pdEntry > 1023) { - iPokemonCount = i - 1; - break; - } - } - - // write new pokemon count - romEntry.entries.put("PokemonCount", iPokemonCount); - - // update some key offsets from known pointers - romEntry.entries.put("PokemonTMHMCompat", readPointer(0x43C68)); - romEntry.entries.put("PokemonEvolutions", readPointer(0x42F6C)); - romEntry.entries.put("MoveTutorCompatibility", - readPointer(0x120C30)); - int descsTable = readPointer(0xE5440); - romEntry.entries.put("MoveDescriptions", descsTable); - int trainersTable = readPointer(0xFC00); - romEntry.entries.put("TrainerData", trainersTable); - - // try to detect number of moves using the descriptions - int moveCount = 0; - while (true) { - int descPointer = readPointer(descsTable + (moveCount) * 4); - if (descPointer >= 0 && descPointer < rom.length) { - int descStrLen = lengthOfStringAt(descPointer); - if (descStrLen > 0 && descStrLen < 100) { - // okay, this does seem fine - moveCount++; - continue; - } - } - break; - } - romEntry.entries.put("MoveCount", moveCount); - - // attempt to detect number of trainers using various tells - int trainerCount = 1; - int tEntryLen = romEntry.getValue("TrainerEntrySize"); - int tNameLen = romEntry.getValue("TrainerNameLength"); - while (true) { - int trOffset = trainersTable + tEntryLen * trainerCount; - int pokeDataType = rom[trOffset] & 0xFF; - if (pokeDataType >= 4) { - // only allowed 0-3 - break; - } - int numPokes = rom[trOffset + (tEntryLen - 8)] & 0xFF; - if (numPokes == 0 || numPokes > 6) { - break; - } - int pointerToPokes = readPointer(trOffset + (tEntryLen - 4)); - if (pointerToPokes < 0 || pointerToPokes >= rom.length) { - break; - } - int nameLength = lengthOfStringAt(trOffset + 4); - if (nameLength >= tNameLen) { - break; - } - // found a valid trainer entry, recognize it - trainerCount++; - } - romEntry.entries.put("TrainerCount", trainerCount); - - // disable static pokemon & move tutor/tm text - romEntry.entries.put("StaticPokemonSupport", 0); - romEntry.staticPokemon.clear(); - romEntry.tmmtTexts.clear(); - - } - - } - - private boolean basicBPRE10HackDetection() { - if (rom.length != Gen3Constants.size16M) { - return true; - } - CRC32 checksum = new CRC32(); - checksum.update(rom); - long csum = checksum.getValue(); - // TODO: there might be more valid BPRE 1.0 checksums? - return csum != 3716707868L; - } - - @Override - public void savingRom() { - savePokemonStats(); - saveMoves(); - } - - private void loadPokedex() { - int pdOffset = romEntry.getValue("PokedexOrder"); - int numInternalPokes = romEntry.getValue("PokemonCount"); - int maxPokedex = 0; - internalToPokedex = new int[numInternalPokes + 1]; - pokedexToInternal = new int[numInternalPokes + 1]; - for (int i = 1; i <= numInternalPokes; i++) { - int dexEntry = readWord(rom, pdOffset + (i - 1) * 2); - if (dexEntry != 0) { - internalToPokedex[i] = dexEntry; - // take the first pokemon only for each dex entry - if (pokedexToInternal[dexEntry] == 0) { - pokedexToInternal[dexEntry] = i; - } - maxPokedex = Math.max(maxPokedex, dexEntry); - } - } - if (maxPokedex == Gen3Constants.unhackedMaxPokedex) { - // see if the slots between johto and hoenn are in use - // old rom hacks use them instead of expanding pokes - int offs = romEntry.getValue("PokemonStats"); - int usedSlots = 0; - for (int i = 0; i < Gen3Constants.unhackedMaxPokedex - - Gen3Constants.unhackedRealPokedex; i++) { - int pokeSlot = Gen3Constants.hoennPokesStart + i; - int pokeOffs = offs + pokeSlot - * Gen3Constants.baseStatsEntrySize; - String lowerName = pokeNames[pokeSlot].toLowerCase(); - if (!this.matches(rom, pokeOffs, Gen3Constants.emptyPokemonSig) - && !lowerName.contains("unused") - && !lowerName.equals("?") && !lowerName.equals("-")) { - usedSlots++; - pokedexToInternal[Gen3Constants.unhackedRealPokedex - + usedSlots] = pokeSlot; - internalToPokedex[pokeSlot] = Gen3Constants.unhackedRealPokedex - + usedSlots; - } else { - internalToPokedex[pokeSlot] = 0; - } - } - // remove the fake extra slots - for (int i = usedSlots + 1; i <= Gen3Constants.unhackedMaxPokedex - - Gen3Constants.unhackedRealPokedex; i++) { - pokedexToInternal[Gen3Constants.unhackedRealPokedex + i] = 0; - } - // if any slots were used at all, this is a rom hack - if (usedSlots > 0) { - this.isRomHack = true; - } - this.pokedexCount = Gen3Constants.unhackedRealPokedex + usedSlots; - } else { - this.isRomHack = true; - this.pokedexCount = maxPokedex; - } - - } - - private void constructPokemonList() { - if (!this.isRomHack) { - // simple behavior: all pokes in the dex are valid - pokemonList = Arrays.asList(pokes); - } else { - // only include "valid" pokes - pokemonList = new ArrayList<Pokemon>(); - pokemonList.add(null); - for (int i = 1; i < pokes.length; i++) { - Pokemon pk = pokes[i]; - if (pk != null) { - String lowerName = pk.name.toLowerCase(); - if (!lowerName.contains("unused") && !lowerName.equals("?")) { - pokemonList.add(pk); - } - } - } - } - numRealPokemon = pokemonList.size() - 1; - - } - - private void loadPokemonStats() { - pokes = new Pokemon[this.pokedexCount + 1]; - int numInternalPokes = romEntry.getValue("PokemonCount"); - pokesInternal = new Pokemon[numInternalPokes + 1]; - int offs = romEntry.getValue("PokemonStats"); - for (int i = 1; i <= numInternalPokes; i++) { - Pokemon pk = new Pokemon(); - pk.name = pokeNames[i]; - pk.number = internalToPokedex[i]; - if (pk.number != 0) { - pokes[pk.number] = pk; - } - pokesInternal[i] = pk; - int pkoffs = offs + i * Gen3Constants.baseStatsEntrySize; - loadBasicPokeStats(pk, pkoffs); - } - populateEvolutions(); - } - - private void savePokemonStats() { - // Write pokemon names & stats - int offs = romEntry.getValue("PokemonNames"); - int nameLen = romEntry.getValue("PokemonNameLength"); - int offs2 = romEntry.getValue("PokemonStats"); - int numInternalPokes = romEntry.getValue("PokemonCount"); - for (int i = 1; i <= numInternalPokes; i++) { - Pokemon pk = pokesInternal[i]; - int stringOffset = offs + i * nameLen; - writeFixedLengthString(pk.name, stringOffset, nameLen); - saveBasicPokeStats(pk, offs2 + i * Gen3Constants.baseStatsEntrySize); - } - writeEvolutions(); - } - - private void loadMoves() { - int moveCount = romEntry.getValue("MoveCount"); - moves = new Move[moveCount + 1]; - int offs = romEntry.getValue("MoveData"); - int nameoffs = romEntry.getValue("MoveNames"); - int namelen = romEntry.getValue("MoveNameLength"); - for (int i = 1; i <= moveCount; i++) { - moves[i] = new Move(); - moves[i].name = readFixedLengthString(nameoffs + i * namelen, - namelen); - moves[i].number = i; - moves[i].internalId = i; - moves[i].effectIndex = rom[offs + i * 0xC] & 0xFF; - moves[i].hitratio = ((rom[offs + i * 0xC + 3] & 0xFF) + 0); - moves[i].power = rom[offs + i * 0xC + 1] & 0xFF; - moves[i].pp = rom[offs + i * 0xC + 4] & 0xFF; - moves[i].type = Gen3Constants.typeTable[rom[offs + i * 0xC + 2]]; - } - - } - - private void saveMoves() { - int moveCount = romEntry.getValue("MoveCount"); - int offs = romEntry.getValue("MoveData"); - for (int i = 1; i <= moveCount; i++) { - rom[offs + i * 0xC] = (byte) moves[i].effectIndex; - rom[offs + i * 0xC + 1] = (byte) moves[i].power; - rom[offs + i * 0xC + 2] = Gen3Constants.typeToByte(moves[i].type); - int hitratio = (int) Math.round(moves[i].hitratio); - if (hitratio < 0) { - hitratio = 0; - } - if (hitratio > 100) { - hitratio = 100; - } - rom[offs + i * 0xC + 3] = (byte) hitratio; - rom[offs + i * 0xC + 4] = (byte) moves[i].pp; - } - } - - public List<Move> getMoves() { - return Arrays.asList(moves); - } - - private void loadBasicPokeStats(Pokemon pkmn, int offset) { - pkmn.hp = rom[offset + Gen3Constants.bsHPOffset] & 0xFF; - pkmn.attack = rom[offset + Gen3Constants.bsAttackOffset] & 0xFF; - pkmn.defense = rom[offset + Gen3Constants.bsDefenseOffset] & 0xFF; - pkmn.speed = rom[offset + Gen3Constants.bsSpeedOffset] & 0xFF; - pkmn.spatk = rom[offset + Gen3Constants.bsSpAtkOffset] & 0xFF; - pkmn.spdef = rom[offset + Gen3Constants.bsSpDefOffset] & 0xFF; - // Type - pkmn.primaryType = Gen3Constants.typeTable[rom[offset - + Gen3Constants.bsPrimaryTypeOffset] & 0xFF]; - pkmn.secondaryType = Gen3Constants.typeTable[rom[offset - + Gen3Constants.bsSecondaryTypeOffset] & 0xFF]; - // Only one type? - if (pkmn.secondaryType == pkmn.primaryType) { - pkmn.secondaryType = null; - } - pkmn.catchRate = rom[offset + Gen3Constants.bsCatchRateOffset] & 0xFF; - pkmn.growthCurve = ExpCurve.fromByte(rom[offset - + Gen3Constants.bsGrowthCurveOffset]); - // Abilities - pkmn.ability1 = rom[offset + Gen3Constants.bsAbility1Offset] & 0xFF; - pkmn.ability2 = rom[offset + Gen3Constants.bsAbility2Offset] & 0xFF; - - // Held Items? - int item1 = readWord(offset + Gen3Constants.bsCommonHeldItemOffset); - int item2 = readWord(offset + Gen3Constants.bsRareHeldItemOffset); - - if (item1 == item2) { - // guaranteed - pkmn.guaranteedHeldItem = item1; - pkmn.commonHeldItem = 0; - pkmn.rareHeldItem = 0; - } else { - pkmn.guaranteedHeldItem = 0; - pkmn.commonHeldItem = item1; - pkmn.rareHeldItem = item2; - } - pkmn.darkGrassHeldItem = -1; - - pkmn.genderRatio = rom[offset + Gen3Constants.bsGenderRatioOffset] & 0xFF; - } - - private void saveBasicPokeStats(Pokemon pkmn, int offset) { - rom[offset + Gen3Constants.bsHPOffset] = (byte) pkmn.hp; - rom[offset + Gen3Constants.bsAttackOffset] = (byte) pkmn.attack; - rom[offset + Gen3Constants.bsDefenseOffset] = (byte) pkmn.defense; - rom[offset + Gen3Constants.bsSpeedOffset] = (byte) pkmn.speed; - rom[offset + Gen3Constants.bsSpAtkOffset] = (byte) pkmn.spatk; - rom[offset + Gen3Constants.bsSpDefOffset] = (byte) pkmn.spdef; - rom[offset + Gen3Constants.bsPrimaryTypeOffset] = Gen3Constants - .typeToByte(pkmn.primaryType); - if (pkmn.secondaryType == null) { - rom[offset + Gen3Constants.bsSecondaryTypeOffset] = rom[offset - + Gen3Constants.bsPrimaryTypeOffset]; - } else { - rom[offset + Gen3Constants.bsSecondaryTypeOffset] = Gen3Constants - .typeToByte(pkmn.secondaryType); - } - rom[offset + Gen3Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; - rom[offset + Gen3Constants.bsGrowthCurveOffset] = pkmn.growthCurve - .toByte(); - - rom[offset + Gen3Constants.bsAbility1Offset] = (byte) pkmn.ability1; - if (pkmn.ability2 == 0) { - // required to not break evos with random ability - rom[offset + Gen3Constants.bsAbility2Offset] = (byte) pkmn.ability1; - } else { - rom[offset + Gen3Constants.bsAbility2Offset] = (byte) pkmn.ability2; - } - - // Held items - if (pkmn.guaranteedHeldItem > 0) { - writeWord(offset + Gen3Constants.bsCommonHeldItemOffset, - pkmn.guaranteedHeldItem); - writeWord(offset + Gen3Constants.bsRareHeldItemOffset, - pkmn.guaranteedHeldItem); - } else { - writeWord(offset + Gen3Constants.bsCommonHeldItemOffset, - pkmn.commonHeldItem); - writeWord(offset + Gen3Constants.bsRareHeldItemOffset, - pkmn.rareHeldItem); - } - - rom[offset + Gen3Constants.bsGenderRatioOffset] = (byte) pkmn.genderRatio; - } - - private void loadPokemonNames() { - int offs = romEntry.getValue("PokemonNames"); - int nameLen = romEntry.getValue("PokemonNameLength"); - int numInternalPokes = romEntry.getValue("PokemonCount"); - pokeNames = new String[numInternalPokes + 1]; - for (int i = 1; i <= numInternalPokes; i++) { - pokeNames[i] = readFixedLengthString(offs + i * nameLen, nameLen); - } - } - - private String readString(int offset, int maxLength) { - StringBuilder string = new StringBuilder(); - for (int c = 0; c < maxLength; c++) { - int currChar = rom[offset + c] & 0xFF; - if (tb[currChar] != null) { - string.append(tb[currChar]); - } else { - if (currChar == Gen3Constants.textTerminator) { - break; - } else if (currChar == Gen3Constants.textVariable) { - int nextChar = rom[offset + c + 1] & 0xFF; - string.append("\\v" + String.format("%02X", nextChar)); - c++; - } else { - string.append("\\x" + String.format("%02X", currChar)); - } - } - } - return string.toString(); - } - - private byte[] translateString(String text) { - List<Byte> data = new ArrayList<Byte>(); - while (text.length() != 0) { - int i = Math.max(0, 4 - text.length()); - if (text.charAt(0) == '\\' && text.charAt(1) == 'x') { - data.add((byte) Integer.parseInt(text.substring(2, 4), 16)); - text = text.substring(4); - } else if (text.charAt(0) == '\\' && text.charAt(1) == 'v') { - data.add((byte) Gen3Constants.textVariable); - data.add((byte) Integer.parseInt(text.substring(2, 4), 16)); - text = text.substring(4); - } else { - while (!(d.containsKey(text.substring(0, 4 - i)) || (i == 4))) { - i++; - } - if (i == 4) { - text = text.substring(1); - } else { - data.add(d.get(text.substring(0, 4 - i))); - text = text.substring(4 - i); - } - } - } - byte[] ret = new byte[data.size()]; - for (int i = 0; i < ret.length; i++) { - ret[i] = data.get(i); - } - return ret; - } - - private String readFixedLengthString(int offset, int length) { - return readString(offset, length); - } - - public String readVariableLengthString(int offset) { - return readString(offset, Integer.MAX_VALUE); - } - - private void writeFixedLengthString(String str, int offset, int length) { - byte[] translated = translateString(str); - int len = Math.min(translated.length, length); - System.arraycopy(translated, 0, rom, offset, len); - if (len < length) { - rom[offset + len] = (byte) Gen3Constants.textTerminator; - len++; - } - while (len < length) { - rom[offset + len] = 0; - len++; - } - } - - private void writeVariableLengthString(String str, int offset) { - byte[] translated = translateString(str); - System.arraycopy(translated, 0, rom, offset, translated.length); - rom[offset + translated.length] = (byte) 0xFF; - } - - private int lengthOfStringAt(int offset) { - int len = 0; - while ((rom[offset + (len++)] & 0xFF) != 0xFF) { - } - return len - 1; - } - - public byte[] traduire(String str) { - return translateString(str); - } - - private static boolean romName(byte[] rom, String name) { - try { - int sigOffset = Gen3Constants.romNameOffset; - byte[] sigBytes = name.getBytes("US-ASCII"); - for (int i = 0; i < sigBytes.length; i++) { - if (rom[sigOffset + i] != sigBytes[i]) { - return false; - } - } - return true; - } catch (UnsupportedEncodingException ex) { - return false; - } - - } - - private static boolean romCode(byte[] rom, String codeToCheck) { - try { - int sigOffset = Gen3Constants.romCodeOffset; - byte[] sigBytes = codeToCheck.getBytes("US-ASCII"); - for (int i = 0; i < sigBytes.length; i++) { - if (rom[sigOffset + i] != sigBytes[i]) { - return false; - } - } - return true; - } catch (UnsupportedEncodingException ex) { - return false; - } - - } - - private int readPointer(int offset) { - return readLong(offset) - 0x8000000; - } - - private int readLong(int offset) { - return (rom[offset] & 0xFF) + ((rom[offset + 1] & 0xFF) << 8) - + ((rom[offset + 2] & 0xFF) << 16) - + (((rom[offset + 3] & 0xFF)) << 24); - } - - private void writePointer(int offset, int pointer) { - writeLong(offset, pointer + 0x8000000); - } - - private void writeLong(int offset, int value) { - rom[offset] = (byte) (value & 0xFF); - rom[offset + 1] = (byte) ((value >> 8) & 0xFF); - rom[offset + 2] = (byte) ((value >> 16) & 0xFF); - rom[offset + 3] = (byte) (((value >> 24) & 0xFF)); - } - - @Override - public List<Pokemon> getStarters() { - List<Pokemon> starters = new ArrayList<Pokemon>(); - int baseOffset = romEntry.getValue("StarterPokemon"); - if (romEntry.romType == Gen3Constants.RomType_Ruby - || romEntry.romType == Gen3Constants.RomType_Sapp - || romEntry.romType == Gen3Constants.RomType_Em) { - // do something - Pokemon starter1 = pokesInternal[readWord(baseOffset)]; - Pokemon starter2 = pokesInternal[readWord(baseOffset - + Gen3Constants.rseStarter2Offset)]; - Pokemon starter3 = pokesInternal[readWord(baseOffset - + Gen3Constants.rseStarter3Offset)]; - starters.add(starter1); - starters.add(starter2); - starters.add(starter3); - } else { - // do something else - Pokemon starter1 = pokesInternal[readWord(baseOffset)]; - Pokemon starter2 = pokesInternal[readWord(baseOffset - + Gen3Constants.frlgStarter2Offset)]; - Pokemon starter3 = pokesInternal[readWord(baseOffset - + Gen3Constants.frlgStarter3Offset)]; - starters.add(starter1); - starters.add(starter2); - starters.add(starter3); - } - return starters; - } - - @Override - public boolean setStarters(List<Pokemon> newStarters) { - if (newStarters.size() != 3) { - return false; - } - - // Support Deoxys/Mew starters in E/FR/LG - if (!havePatchedObedience) { - attemptObedienceEvolutionPatches(); - } - int baseOffset = romEntry.getValue("StarterPokemon"); - - int starter0 = pokedexToInternal[newStarters.get(0).number]; - int starter1 = pokedexToInternal[newStarters.get(1).number]; - int starter2 = pokedexToInternal[newStarters.get(2).number]; - if (romEntry.romType == Gen3Constants.RomType_Ruby - || romEntry.romType == Gen3Constants.RomType_Sapp - || romEntry.romType == Gen3Constants.RomType_Em) { - - // US - // order: 0, 1, 2 - writeWord(baseOffset, starter0); - writeWord(baseOffset + Gen3Constants.rseStarter2Offset, starter1); - writeWord(baseOffset + Gen3Constants.rseStarter3Offset, starter2); - - } else { - - // frlg: - - // US - // order: 0, 1, 2 - writeWord(baseOffset, starter0); - writeWord(baseOffset + Gen3Constants.frlgStarterRepeatOffset, - starter1); - - writeWord(baseOffset + Gen3Constants.frlgStarter2Offset, starter1); - writeWord(baseOffset + Gen3Constants.frlgStarter2Offset - + Gen3Constants.frlgStarterRepeatOffset, starter2); - - writeWord(baseOffset + Gen3Constants.frlgStarter3Offset, starter2); - writeWord(baseOffset + Gen3Constants.frlgStarter3Offset - + Gen3Constants.frlgStarterRepeatOffset, starter0); - - if (romEntry.romCode.charAt(3) != 'J' - && romEntry.romCode.charAt(3) != 'B') { - // Update PROF. Oak's descriptions for each starter - // First result for each STARTERNAME is the text we need - writeFRLGStarterText( - pokes[Gen3Constants.frlgBaseStarter1].name, - newStarters.get(0), "you want to go with\\nthe "); - writeFRLGStarterText( - pokes[Gen3Constants.frlgBaseStarter2].name, - newStarters.get(1), "you’re claiming the\\n"); - writeFRLGStarterText( - pokes[Gen3Constants.frlgBaseStarter3].name, - newStarters.get(2), "you’ve decided on the\\n"); - } - } - return true; - - } - - @Override - public List<Integer> getStarterHeldItems() { - List<Integer> sHeldItems = new ArrayList<Integer>(); - if (romEntry.romType == Gen3Constants.RomType_FRLG) { - // offset from normal starter offset as a word - int baseOffset = romEntry.getValue("StarterPokemon"); - sHeldItems.add(readWord(baseOffset - + Gen3Constants.frlgStarterItemsOffset)); - } else { - int baseOffset = romEntry.getValue("StarterItems"); - int i1 = rom[baseOffset] & 0xFF; - int i2 = rom[baseOffset + 2] & 0xFF; - if (i2 == 0) { - sHeldItems.add(i1); - } else { - sHeldItems.add(i2 + 0xFF); - } - } - return sHeldItems; - } - - @Override - public void setStarterHeldItems(List<Integer> items) { - if (items.size() != 1) { - return; - } - int item = items.get(0); - if (romEntry.romType == Gen3Constants.RomType_FRLG) { - // offset from normal starter offset as a word - int baseOffset = romEntry.getValue("StarterPokemon"); - writeWord(baseOffset + Gen3Constants.frlgStarterItemsOffset, item); - } else { - int baseOffset = romEntry.getValue("StarterItems"); - if (item <= 0xFF) { - rom[baseOffset] = (byte) item; - rom[baseOffset + 2] = 0; - rom[baseOffset + 3] = Gen3Constants.gbaAddRxOpcode - | Gen3Constants.gbaR2; - } else { - rom[baseOffset] = (byte) 0xFF; - rom[baseOffset + 2] = (byte) (item - 0xFF); - rom[baseOffset + 3] = Gen3Constants.gbaAddRxOpcode - | Gen3Constants.gbaR2; - } - } - } - - private void writeFRLGStarterText(String findName, Pokemon pkmn, - String oakText) { - List<Integer> foundTexts = RomFunctions.search(rom, traduire(findName)); - if (foundTexts.size() > 0) { - int offset = foundTexts.get(0); - String pokeName = pkmn.name; - String pokeType = pkmn.primaryType == null ? "???" - : pkmn.primaryType.toString(); - if (pokeType.equals("NORMAL") && pkmn.secondaryType != null) { - pokeType = pkmn.secondaryType.toString(); - } - String speech = pokeName + " is your choice.\\pSo, \\v01, " - + oakText + pokeType + " POKéMON " + pokeName + "?"; - writeFixedLengthString(speech, offset, lengthOfStringAt(offset) + 1); - } - } - - @Override - public void shufflePokemonStats() { - for (int i = 1; i < numRealPokemon; i++) { - pokemonList.get(i).shuffleStats(this.random); - } - } - - @Override - public List<EncounterSet> getEncounters(boolean useTimeOfDay) { - if (!mapLoadingDone) { - preprocessMaps(); - mapLoadingDone = true; - } - - int startOffs = romEntry.getValue("WildPokemon"); - List<EncounterSet> encounterAreas = new ArrayList<EncounterSet>(); - Set<Integer> seenOffsets = new TreeSet<Integer>(); - int offs = startOffs; - while (true) { - // Read pointers - int bank = rom[offs] & 0xFF; - int map = rom[offs + 1] & 0xFF; - if (bank == 0xFF && map == 0xFF) { - break; - } - - String mapName = mapNames[bank][map]; - - int grassPokes = readPointer(offs + 4); - int waterPokes = readPointer(offs + 8); - int treePokes = readPointer(offs + 12); - int fishPokes = readPointer(offs + 16); - - // Add pokemanz - if (grassPokes >= 0 && grassPokes < rom.length - && rom[grassPokes] != 0 - && !seenOffsets.contains(readPointer(grassPokes + 4))) { - encounterAreas.add(readWildArea(grassPokes, - Gen3Constants.grassSlots, mapName + " Grass/Cave")); - seenOffsets.add(readPointer(grassPokes + 4)); - } - if (waterPokes >= 0 && waterPokes < rom.length - && rom[waterPokes] != 0 - && !seenOffsets.contains(readPointer(waterPokes + 4))) { - encounterAreas.add(readWildArea(waterPokes, - Gen3Constants.surfingSlots, mapName + " Surfing")); - seenOffsets.add(readPointer(waterPokes + 4)); - } - if (treePokes >= 0 && treePokes < rom.length && rom[treePokes] != 0 - && !seenOffsets.contains(readPointer(treePokes + 4))) { - encounterAreas.add(readWildArea(treePokes, - Gen3Constants.rockSmashSlots, mapName + " Rock Smash")); - seenOffsets.add(readPointer(treePokes + 4)); - } - if (fishPokes >= 0 && fishPokes < rom.length && rom[fishPokes] != 0 - && !seenOffsets.contains(readPointer(fishPokes + 4))) { - encounterAreas.add(readWildArea(fishPokes, - Gen3Constants.fishingSlots, mapName + " Fishing")); - seenOffsets.add(readPointer(fishPokes + 4)); - } - - offs += 20; - } - if (romEntry.arrayEntries.containsKey("BattleTrappersBanned")) { - // Some encounter sets aren't allowed to have Pokemon - // with Arena Trap, Shadow Tag etc. - int[] bannedAreas = romEntry.arrayEntries - .get("BattleTrappersBanned"); - Set<Pokemon> battleTrappers = new HashSet<Pokemon>(); - for (Pokemon pk : getPokemon()) { - if (hasBattleTrappingAbility(pk)) { - battleTrappers.add(pk); - } - } - for (int areaIdx : bannedAreas) { - encounterAreas.get(areaIdx).bannedPokemon - .addAll(battleTrappers); - } - } - return encounterAreas; - } - - private boolean hasBattleTrappingAbility(Pokemon pokemon) { - return pokemon != null - && (Gen3Constants.battleTrappingAbilities - .contains(pokemon.ability1) || Gen3Constants.battleTrappingAbilities - .contains(pokemon.ability2)); - } - - private EncounterSet readWildArea(int offset, int numOfEntries, - String setName) { - EncounterSet thisSet = new EncounterSet(); - thisSet.rate = rom[offset]; - thisSet.displayName = setName; - // Grab the *real* pointer to data - int dataOffset = readPointer(offset + 4); - // Read the entries - for (int i = 0; i < numOfEntries; i++) { - // min, max, species, species - Encounter enc = new Encounter(); - enc.level = rom[dataOffset + i * 4]; - enc.maxLevel = rom[dataOffset + i * 4 + 1]; - try { - enc.pokemon = pokesInternal[readWord(dataOffset + i * 4 + 2)]; - } catch (ArrayIndexOutOfBoundsException ex) { - throw ex; - } - thisSet.encounters.add(enc); - } - return thisSet; - } - - @Override - public void setEncounters(boolean useTimeOfDay, - List<EncounterSet> encounters) { - // Support Deoxys/Mew catches in E/FR/LG - if (!havePatchedObedience) { - attemptObedienceEvolutionPatches(); - } - - int startOffs = romEntry.getValue("WildPokemon"); - Iterator<EncounterSet> encounterAreas = encounters.iterator(); - Set<Integer> seenOffsets = new TreeSet<Integer>(); - int offs = startOffs; - while (true) { - // Read pointers - int bank = rom[offs] & 0xFF; - int map = rom[offs + 1] & 0xFF; - if (bank == 0xFF && map == 0xFF) { - break; - } - - int grassPokes = readPointer(offs + 4); - int waterPokes = readPointer(offs + 8); - int treePokes = readPointer(offs + 12); - int fishPokes = readPointer(offs + 16); - - // Add pokemanz - if (grassPokes >= 0 && grassPokes < rom.length - && rom[grassPokes] != 0 - && !seenOffsets.contains(readPointer(grassPokes + 4))) { - writeWildArea(grassPokes, Gen3Constants.grassSlots, - encounterAreas.next()); - seenOffsets.add(readPointer(grassPokes + 4)); - } - if (waterPokes >= 0 && waterPokes < rom.length - && rom[waterPokes] != 0 - && !seenOffsets.contains(readPointer(waterPokes + 4))) { - writeWildArea(waterPokes, Gen3Constants.surfingSlots, - encounterAreas.next()); - seenOffsets.add(readPointer(waterPokes + 4)); - } - if (treePokes >= 0 && treePokes < rom.length && rom[treePokes] != 0 - && !seenOffsets.contains(readPointer(treePokes + 4))) { - writeWildArea(treePokes, Gen3Constants.rockSmashSlots, - encounterAreas.next()); - seenOffsets.add(readPointer(treePokes + 4)); - } - if (fishPokes >= 0 && fishPokes < rom.length && rom[fishPokes] != 0 - && !seenOffsets.contains(readPointer(fishPokes + 4))) { - writeWildArea(fishPokes, Gen3Constants.fishingSlots, - encounterAreas.next()); - seenOffsets.add(readPointer(fishPokes + 4)); - } - - offs += 20; - } - } - - @Override - public List<Pokemon> bannedForWildEncounters() { - return Arrays.asList(pokes[Gen3Constants.unownIndex]); // Unown banned - } - - @Override - public List<Trainer> getTrainers() { - int baseOffset = romEntry.getValue("TrainerData"); - int amount = romEntry.getValue("TrainerCount"); - int entryLen = romEntry.getValue("TrainerEntrySize"); - List<Trainer> theTrainers = new ArrayList<Trainer>(); - List<String> tcnames = this.getTrainerClassNames(); - for (int i = 1; i < amount; i++) { - int trOffset = baseOffset + i * entryLen; - Trainer tr = new Trainer(); - tr.offset = trOffset; - int trainerclass = rom[trOffset + 1] & 0xFF; - tr.trainerclass = (rom[trOffset + 2] & 0x80) > 0 ? 1 : 0; - - int pokeDataType = rom[trOffset] & 0xFF; - int numPokes = rom[trOffset + (entryLen - 8)] & 0xFF; - int pointerToPokes = readPointer(trOffset + (entryLen - 4)); - tr.poketype = pokeDataType; - tr.name = this.readVariableLengthString(trOffset + 4); - tr.fullDisplayName = tcnames.get(trainerclass) + " " + tr.name; - // Pokemon data! - if (pokeDataType == 0) { - // blocks of 8 bytes - for (int poke = 0; poke < numPokes; poke++) { - TrainerPokemon thisPoke = new TrainerPokemon(); - thisPoke.AILevel = readWord(pointerToPokes + poke * 8); - thisPoke.level = readWord(pointerToPokes + poke * 8 + 2); - thisPoke.pokemon = pokesInternal[readWord(pointerToPokes - + poke * 8 + 4)]; - tr.pokemon.add(thisPoke); - } - } else if (pokeDataType == 2) { - // blocks of 8 bytes - for (int poke = 0; poke < numPokes; poke++) { - TrainerPokemon thisPoke = new TrainerPokemon(); - thisPoke.AILevel = readWord(pointerToPokes + poke * 8); - thisPoke.level = readWord(pointerToPokes + poke * 8 + 2); - thisPoke.pokemon = pokesInternal[readWord(pointerToPokes - + poke * 8 + 4)]; - thisPoke.heldItem = readWord(pointerToPokes + poke * 8 + 6); - tr.pokemon.add(thisPoke); - } - } else if (pokeDataType == 1) { - // blocks of 16 bytes - for (int poke = 0; poke < numPokes; poke++) { - TrainerPokemon thisPoke = new TrainerPokemon(); - thisPoke.AILevel = readWord(pointerToPokes + poke * 16); - thisPoke.level = readWord(pointerToPokes + poke * 16 + 2); - thisPoke.pokemon = pokesInternal[readWord(pointerToPokes - + poke * 16 + 4)]; - thisPoke.move1 = readWord(pointerToPokes + poke * 16 + 6); - thisPoke.move2 = readWord(pointerToPokes + poke * 16 + 8); - thisPoke.move3 = readWord(pointerToPokes + poke * 16 + 10); - thisPoke.move4 = readWord(pointerToPokes + poke * 16 + 12); - tr.pokemon.add(thisPoke); - } - } else if (pokeDataType == 3) { - // blocks of 16 bytes - for (int poke = 0; poke < numPokes; poke++) { - TrainerPokemon thisPoke = new TrainerPokemon(); - thisPoke.AILevel = readWord(pointerToPokes + poke * 16); - thisPoke.level = readWord(pointerToPokes + poke * 16 + 2); - thisPoke.pokemon = pokesInternal[readWord(pointerToPokes - + poke * 16 + 4)]; - thisPoke.heldItem = readWord(pointerToPokes + poke * 16 + 6); - thisPoke.move1 = readWord(pointerToPokes + poke * 16 + 8); - thisPoke.move2 = readWord(pointerToPokes + poke * 16 + 10); - thisPoke.move3 = readWord(pointerToPokes + poke * 16 + 12); - thisPoke.move4 = readWord(pointerToPokes + poke * 16 + 14); - tr.pokemon.add(thisPoke); - } - } - theTrainers.add(tr); - } - - if (romEntry.romType == Gen3Constants.RomType_Ruby - || romEntry.romType == Gen3Constants.RomType_Sapp) { - Gen3Constants.trainerTagsRS(theTrainers, romEntry.romType); - } else if (romEntry.romType == Gen3Constants.RomType_Em) { - Gen3Constants.trainerTagsE(theTrainers); - } else { - Gen3Constants.trainerTagsFRLG(theTrainers); - } - return theTrainers; - } - - @Override - public void setTrainers(List<Trainer> trainerData) { - int baseOffset = romEntry.getValue("TrainerData"); - int amount = romEntry.getValue("TrainerCount"); - int entryLen = romEntry.getValue("TrainerEntrySize"); - Iterator<Trainer> theTrainers = trainerData.iterator(); - for (int i = 1; i < amount; i++) { - int trOffset = baseOffset + i * entryLen; - Trainer tr = theTrainers.next(); - // Write out the data as type 0 to avoid moves & hold items carrying - // over - rom[trOffset] = 0; - rom[trOffset + (entryLen - 8)] = (byte) tr.pokemon.size(); - int pointerToPokes = readPointer(trOffset + (entryLen - 4)); - Iterator<TrainerPokemon> pokes = tr.pokemon.iterator(); - // Pokemon data! - // if (pokeDataType == 0) { - // blocks of 8 bytes - for (int poke = 0; poke < tr.pokemon.size(); poke++) { - TrainerPokemon thisPoke = pokes.next(); - writeWord(pointerToPokes + poke * 8, thisPoke.AILevel); - writeWord(pointerToPokes + poke * 8 + 2, thisPoke.level); - writeWord(pointerToPokes + poke * 8 + 4, - pokedexToInternal[thisPoke.pokemon.number]); - writeWord(pointerToPokes + poke * 8 + 6, 0); - } - } - - } - - private void writeWildArea(int offset, int numOfEntries, - EncounterSet encounters) { - // Grab the *real* pointer to data - int dataOffset = readPointer(offset + 4); - // Write the entries - for (int i = 0; i < numOfEntries; i++) { - Encounter enc = encounters.encounters.get(i); - // min, max, species, species - writeWord(dataOffset + i * 4 + 2, - pokedexToInternal[enc.pokemon.number]); - } - } - - @Override - public List<Pokemon> getPokemon() { - return pokemonList; - } - - @Override - public Map<Pokemon, List<MoveLearnt>> getMovesLearnt() { - Map<Pokemon, List<MoveLearnt>> movesets = new TreeMap<Pokemon, List<MoveLearnt>>(); - int baseOffset = romEntry.getValue("PokemonMovesets"); - for (int i = 1; i < numRealPokemon; i++) { - Pokemon pkmn = pokemonList.get(i); - int offsToPtr = baseOffset + (pokedexToInternal[pkmn.number]) * 4; - int moveDataLoc = readPointer(offsToPtr); - List<MoveLearnt> moves = new ArrayList<MoveLearnt>(); - if (jamboMovesetHack) { - while ((rom[moveDataLoc] & 0xFF) != 0x00 - || (rom[moveDataLoc + 1] & 0xFF) != 0x00 - || (rom[moveDataLoc + 2] & 0xFF) != 0xFF) { - MoveLearnt ml = new MoveLearnt(); - ml.level = rom[moveDataLoc + 2] & 0xFF; - ml.move = readWord(moveDataLoc); - moves.add(ml); - moveDataLoc += 3; - } - } else { - while ((rom[moveDataLoc] & 0xFF) != 0xFF - || (rom[moveDataLoc + 1] & 0xFF) != 0xFF) { - int move = (rom[moveDataLoc] & 0xFF); - int level = (rom[moveDataLoc + 1] & 0xFE) >> 1; - if ((rom[moveDataLoc + 1] & 0x01) == 0x01) { - move += 0x100; - } - MoveLearnt ml = new MoveLearnt(); - ml.level = level; - ml.move = move; - moves.add(ml); - moveDataLoc += 2; - } - } - movesets.put(pkmn, moves); - } - return movesets; - } - - @Override - public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets) { - int baseOffset = romEntry.getValue("PokemonMovesets"); - int fso = romEntry.getValue("FreeSpace"); - for (int i = 1; i < numRealPokemon; i++) { - Pokemon pkmn = pokemonList.get(i); - int offsToPtr = baseOffset + (pokedexToInternal[pkmn.number]) * 4; - int moveDataLoc = readPointer(offsToPtr); - List<MoveLearnt> moves = movesets.get(pkmn); - int newMoveCount = moves.size(); - int mloc = moveDataLoc; - int entrySize; - if (jamboMovesetHack) { - while ((rom[mloc] & 0xFF) != 0x00 - || (rom[mloc + 1] & 0xFF) != 0x00 - || (rom[mloc + 2] & 0xFF) != 0xFF) { - mloc += 3; - } - entrySize = 3; - } else { - while ((rom[mloc] & 0xFF) != 0xFF - || (rom[mloc + 1] & 0xFF) != 0xFF) { - mloc += 2; - } - entrySize = 2; - } - int currentMoveCount = (mloc - moveDataLoc) / entrySize; - - if (newMoveCount > currentMoveCount) { - // Repoint for more space - int newBytesNeeded = newMoveCount * entrySize + entrySize * 2; - int writeSpace = RomFunctions.freeSpaceFinder(rom, - Gen3Constants.freeSpaceByte, newBytesNeeded, fso); - if (writeSpace < fso) { - throw new RuntimeException("ROM is full"); - } - writePointer(offsToPtr, writeSpace); - moveDataLoc = writeSpace; - } - - // Write new moveset now that space is ensured. - for (int mv = 0; mv < newMoveCount; mv++) { - MoveLearnt ml = moves.get(mv); - moveDataLoc += writeMLToOffset(moveDataLoc, ml); - } - - // If move count changed, new terminator is required - // In the repoint enough space was reserved to add some padding to - // make sure the terminator isn't detected as free space. - // If no repoint, the padding goes over the old moves/terminator. - if (newMoveCount != currentMoveCount) { - if (jamboMovesetHack) { - rom[moveDataLoc] = 0x00; - rom[moveDataLoc + 1] = 0x00; - rom[moveDataLoc + 2] = (byte) 0xFF; - rom[moveDataLoc + 3] = 0x00; - rom[moveDataLoc + 4] = 0x00; - rom[moveDataLoc + 5] = 0x00; - } else { - rom[moveDataLoc] = (byte) 0xFF; - rom[moveDataLoc + 1] = (byte) 0xFF; - rom[moveDataLoc + 2] = 0x00; - rom[moveDataLoc + 3] = 0x00; - } - } - - } - - } - - private int writeMLToOffset(int offset, MoveLearnt ml) { - if (jamboMovesetHack) { - writeWord(offset, ml.move); - rom[offset + 2] = (byte) ml.level; - return 3; - } else { - rom[offset] = (byte) (ml.move & 0xFF); - int levelPart = (ml.level << 1) & 0xFE; - if (ml.move > 255) { - levelPart++; - } - rom[offset + 1] = (byte) levelPart; - return 2; - } - } - - private static class StaticPokemon { - private int[] offsets; - - public StaticPokemon(int... offsets) { - this.offsets = offsets; - } - - public Pokemon getPokemon(Gen3RomHandler parent) { - return parent.pokesInternal[parent.readWord(offsets[0])]; - } - - public void setPokemon(Gen3RomHandler parent, Pokemon pkmn) { - int value = parent.pokedexToInternal[pkmn.number]; - for (int offset : offsets) { - parent.writeWord(offset, value); - } - } - } - - @Override - public List<Pokemon> getStaticPokemon() { - List<Pokemon> statics = new ArrayList<Pokemon>(); - List<StaticPokemon> staticsHere = romEntry.staticPokemon; - for (StaticPokemon staticPK : staticsHere) { - statics.add(staticPK.getPokemon(this)); - } - return statics; - } - - @Override - public boolean setStaticPokemon(List<Pokemon> staticPokemon) { - // Support Deoxys/Mew gifts/catches in E/FR/LG - if (!havePatchedObedience) { - attemptObedienceEvolutionPatches(); - } - - List<StaticPokemon> staticsHere = romEntry.staticPokemon; - if (staticPokemon.size() != staticsHere.size()) { - return false; - } - - for (int i = 0; i < staticsHere.size(); i++) { - staticsHere.get(i).setPokemon(this, staticPokemon.get(i)); - } - return true; - } - - @Override - public List<Integer> getTMMoves() { - List<Integer> tms = new ArrayList<Integer>(); - int offset = romEntry.getValue("TmMoves"); - for (int i = 1; i <= Gen3Constants.tmCount; i++) { - tms.add(readWord(offset + (i - 1) * 2)); - } - return tms; - } - - @Override - public List<Integer> getHMMoves() { - return Gen3Constants.hmMoves; - } - - @Override - public void setTMMoves(List<Integer> moveIndexes) { - if (!mapLoadingDone) { - preprocessMaps(); - mapLoadingDone = true; - } - int offset = romEntry.getValue("TmMoves"); - for (int i = 1; i <= Gen3Constants.tmCount; i++) { - writeWord(offset + (i - 1) * 2, moveIndexes.get(i - 1)); - } - int otherOffset = romEntry.getValue("TmMovesDuplicate"); - if (otherOffset > 0) { - // Emerald/FR/LG have *two* TM tables - System.arraycopy(rom, offset, rom, otherOffset, - Gen3Constants.tmCount * 2); - } - - int iiOffset = romEntry.getValue("ItemImages"); - if (iiOffset > 0) { - int[] pals = romEntry.arrayEntries.get("TmPals"); - // Update the item image palettes - // Gen3 TMs are 289-338 - for (int i = 0; i < 50; i++) { - Move mv = moves[moveIndexes.get(i)]; - int typeID = Gen3Constants.typeToByte(mv.type); - writePointer(iiOffset + (Gen3Constants.tmItemOffset + i) * 8 - + 4, pals[typeID]); - } - } - - int fsOffset = romEntry.getValue("FreeSpace"); - - // Item descriptions - if (romEntry.getValue("MoveDescriptions") > 0) { - // JP blocked for now - uses different item structure anyway - int idOffset = romEntry.getValue("ItemData"); - int mdOffset = romEntry.getValue("MoveDescriptions"); - int entrySize = romEntry.getValue("ItemEntrySize"); - int limitPerLine = (romEntry.romType == Gen3Constants.RomType_FRLG) ? Gen3Constants.frlgItemDescCharsPerLine - : Gen3Constants.rseItemDescCharsPerLine; - for (int i = 0; i < Gen3Constants.tmCount; i++) { - int itemBaseOffset = idOffset - + (i + Gen3Constants.tmItemOffset) * entrySize; - int moveBaseOffset = mdOffset + (moveIndexes.get(i) - 1) * 4; - int moveTextPointer = readPointer(moveBaseOffset); - String moveDesc = readVariableLengthString(moveTextPointer); - String newItemDesc = RomFunctions - .rewriteDescriptionForNewLineSize(moveDesc, "\\n", - limitPerLine, ssd); - // Find freespace - int fsBytesNeeded = translateString(newItemDesc).length + 1; - int newItemDescOffset = RomFunctions.freeSpaceFinder(rom, - Gen3Constants.freeSpaceByte, fsBytesNeeded, fsOffset); - if (newItemDescOffset < fsOffset) { - String nl = System.getProperty("line.separator"); - log("Couldn't insert new item description." + nl); - return; - } - writeVariableLengthString(newItemDesc, newItemDescOffset); - writePointer(itemBaseOffset - + Gen3Constants.itemDataDescriptionOffset, - newItemDescOffset); - } - } - - // TM Text? - for (TMOrMTTextEntry tte : romEntry.tmmtTexts) { - if (tte.actualOffset > 0 && !tte.isMoveTutor) { - // create the new TM text - int oldPointer = readPointer(tte.actualOffset); - if (oldPointer < 0 || oldPointer >= rom.length) { - throw new RuntimeException( - "TM Text update failed: couldn't read a TM text pointer."); - } - String moveName = this.moves[moveIndexes.get(tte.number - 1)].name; - // temporarily use underscores to stop the move name being split - String tmpMoveName = moveName.replace(' ', '_'); - String unformatted = tte.template - .replace("[move]", tmpMoveName); - String newText = RomFunctions.formatTextWithReplacements( - unformatted, null, "\\n", "\\l", "\\p", - Gen3Constants.regularTextboxCharsPerLine, ssd); - // get rid of the underscores - newText = newText.replace(tmpMoveName, moveName); - // insert the new text into free space - int fsBytesNeeded = translateString(newText).length + 1; - int newOffset = RomFunctions.freeSpaceFinder(rom, (byte) 0xFF, - fsBytesNeeded, fsOffset); - if (newOffset < fsOffset) { - String nl = System.getProperty("line.separator"); - log("Couldn't insert new TM text." + nl); - return; - } - writeVariableLengthString(newText, newOffset); - // search for copies of the pointer: - // make a needle of the pointer - byte[] searchNeedle = new byte[4]; - System.arraycopy(rom, tte.actualOffset, searchNeedle, 0, 4); - // find copies within 500 bytes either way of actualOffset - int minOffset = Math.max(0, tte.actualOffset - - Gen3Constants.pointerSearchRadius); - int maxOffset = Math.min(rom.length, tte.actualOffset - + Gen3Constants.pointerSearchRadius); - List<Integer> pointerLocs = RomFunctions.search(rom, minOffset, - maxOffset, searchNeedle); - for (int pointerLoc : pointerLocs) { - // write the new pointer - writePointer(pointerLoc, newOffset); - } - } - } - } - - private RomFunctions.StringSizeDeterminer ssd = new RomFunctions.StringSizeDeterminer() { - - @Override - public int lengthFor(String encodedText) { - return translateString(encodedText).length; - } - }; - - @Override - public int getTMCount() { - return Gen3Constants.tmCount; - } - - @Override - public int getHMCount() { - return Gen3Constants.hmCount; - } - - @Override - public Map<Pokemon, boolean[]> getTMHMCompatibility() { - Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); - int offset = romEntry.getValue("PokemonTMHMCompat"); - for (int i = 1; i < numRealPokemon; i++) { - Pokemon pkmn = pokemonList.get(i); - int compatOffset = offset + (pokedexToInternal[pkmn.number]) * 8; - boolean[] flags = new boolean[Gen3Constants.tmCount - + Gen3Constants.hmCount + 1]; - for (int j = 0; j < 8; j++) { - readByteIntoFlags(flags, j * 8 + 1, compatOffset + j); - } - compat.put(pkmn, flags); - } - return compat; - } - - @Override - public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData) { - int offset = romEntry.getValue("PokemonTMHMCompat"); - for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { - Pokemon pkmn = compatEntry.getKey(); - boolean[] flags = compatEntry.getValue(); - int compatOffset = offset + (pokedexToInternal[pkmn.number]) * 8; - for (int j = 0; j < 8; j++) { - rom[compatOffset + j] = getByteFromFlags(flags, j * 8 + 1); - } - } - } - - @Override - public boolean hasMoveTutors() { - return (romEntry.romType == Gen3Constants.RomType_Em || romEntry.romType == Gen3Constants.RomType_FRLG); - } - - @Override - public List<Integer> getMoveTutorMoves() { - if (!hasMoveTutors()) { - return new ArrayList<Integer>(); - } - List<Integer> mts = new ArrayList<Integer>(); - int moveCount = romEntry.getValue("MoveTutorMoves"); - int offset = romEntry.getValue("MoveTutorData"); - for (int i = 0; i < moveCount; i++) { - mts.add(readWord(offset + i * 2)); - } - return mts; - } - - @Override - public void setMoveTutorMoves(List<Integer> moves) { - if (!hasMoveTutors()) { - return; - } - int moveCount = romEntry.getValue("MoveTutorMoves"); - int offset = romEntry.getValue("MoveTutorData"); - if (moveCount != moves.size()) { - return; - } - for (int i = 0; i < moveCount; i++) { - writeWord(offset + i * 2, moves.get(i)); - } - int fsOffset = romEntry.getValue("FreeSpace"); - - // Move Tutor Text? - for (TMOrMTTextEntry tte : romEntry.tmmtTexts) { - if (tte.actualOffset > 0 && tte.isMoveTutor) { - // create the new MT text - int oldPointer = readPointer(tte.actualOffset); - if (oldPointer < 0 || oldPointer >= rom.length) { - throw new RuntimeException( - "Move Tutor Text update failed: couldn't read a move tutor text pointer."); - } - String moveName = this.moves[moves.get(tte.number)].name; - // temporarily use underscores to stop the move name being split - String tmpMoveName = moveName.replace(' ', '_'); - String unformatted = tte.template - .replace("[move]", tmpMoveName); - String newText = RomFunctions.formatTextWithReplacements( - unformatted, null, "\\n", "\\l", "\\p", - Gen3Constants.regularTextboxCharsPerLine, ssd); - // get rid of the underscores - newText = newText.replace(tmpMoveName, moveName); - // insert the new text into free space - int fsBytesNeeded = translateString(newText).length + 1; - int newOffset = RomFunctions.freeSpaceFinder(rom, - Gen3Constants.freeSpaceByte, fsBytesNeeded, fsOffset); - if (newOffset < fsOffset) { - String nl = System.getProperty("line.separator"); - log("Couldn't insert new Move Tutor text." + nl); - return; - } - writeVariableLengthString(newText, newOffset); - // search for copies of the pointer: - // make a needle of the pointer - byte[] searchNeedle = new byte[4]; - System.arraycopy(rom, tte.actualOffset, searchNeedle, 0, 4); - // find copies within 500 bytes either way of actualOffset - int minOffset = Math.max(0, tte.actualOffset - - Gen3Constants.pointerSearchRadius); - int maxOffset = Math.min(rom.length, tte.actualOffset - + Gen3Constants.pointerSearchRadius); - List<Integer> pointerLocs = RomFunctions.search(rom, minOffset, - maxOffset, searchNeedle); - for (int pointerLoc : pointerLocs) { - // write the new pointer - writePointer(pointerLoc, newOffset); - } - } - } - } - - @Override - public Map<Pokemon, boolean[]> getMoveTutorCompatibility() { - if (!hasMoveTutors()) { - return new TreeMap<Pokemon, boolean[]>(); - } - Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); - int moveCount = romEntry.getValue("MoveTutorMoves"); - int offset = romEntry.getValue("MoveTutorCompatibility"); - int bytesRequired = ((moveCount + 7) & ~7) / 8; - for (int i = 1; i < numRealPokemon; i++) { - Pokemon pkmn = pokemonList.get(i); - int compatOffset = offset + pokedexToInternal[pkmn.number] - * bytesRequired; - boolean[] flags = new boolean[moveCount + 1]; - for (int j = 0; j < bytesRequired; j++) { - readByteIntoFlags(flags, j * 8 + 1, compatOffset + j); - } - compat.put(pkmn, flags); - } - return compat; - } - - @Override - public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData) { - if (!hasMoveTutors()) { - return; - } - int moveCount = romEntry.getValue("MoveTutorMoves"); - int offset = romEntry.getValue("MoveTutorCompatibility"); - int bytesRequired = ((moveCount + 7) & ~7) / 8; - for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { - Pokemon pkmn = compatEntry.getKey(); - boolean[] flags = compatEntry.getValue(); - int compatOffset = offset + pokedexToInternal[pkmn.number] - * bytesRequired; - for (int j = 0; j < bytesRequired; j++) { - rom[compatOffset + j] = getByteFromFlags(flags, j * 8 + 1); - } - } - } - - @Override - public String getROMName() { - return romEntry.name + (this.isRomHack ? " (ROM Hack)" : ""); - } - - @Override - public String getROMCode() { - return romEntry.romCode; - } - - @Override - public String getSupportLevel() { - return (romEntry.getValue("StaticPokemonSupport") > 0) ? "Complete" - : "No Static Pokemon"; - } - - // For dynamic offsets later - private int find(String hexString) { - return find(rom, hexString); - } - - private static int find(byte[] haystack, String hexString) { - if (hexString.length() % 2 != 0) { - return -3; // error - } - byte[] searchFor = new byte[hexString.length() / 2]; - for (int i = 0; i < searchFor.length; i++) { - searchFor[i] = (byte) Integer.parseInt( - hexString.substring(i * 2, i * 2 + 2), 16); - } - List<Integer> found = RomFunctions.search(haystack, searchFor); - if (found.size() == 0) { - return -1; // not found - } else if (found.size() > 1) { - return -2; // not unique - } else { - return found.get(0); - } - } - - private List<Integer> findMultiple(String hexString) { - return findMultiple(rom, hexString); - } - - private static List<Integer> findMultiple(byte[] haystack, String hexString) { - if (hexString.length() % 2 != 0) { - return new ArrayList<Integer>(); // 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(haystack, searchFor); - return found; - } - - private void writeHexString(String hexString, int offset) { - if (hexString.length() % 2 != 0) { - return; // error - } - for (int i = 0; i < hexString.length() / 2; i++) { - rom[offset + i] = (byte) Integer.parseInt( - hexString.substring(i * 2, i * 2 + 2), 16); - } - } - - private void attemptObedienceEvolutionPatches() { - havePatchedObedience = true; - // This routine *appears* to only exist in E/FR/LG... - // Look for the deoxys part which is - // MOVS R1, 0x19A - // CMP R0, R1 - // BEQ <mew/deoxys case> - // Hex is CD214900 8842 0FD0 - int deoxysObOffset = find(Gen3Constants.deoxysObeyCode); - if (deoxysObOffset > 0) { - // We found the deoxys check... - // Replacing it with MOVS R1, 0x0 would work fine. - // This would make it so species 0x0 (glitch only) would disobey. - // But MOVS R1, 0x0 (the version I know) is 2-byte - // So we just use it twice... - // the equivalent of nop'ing the second time. - rom[deoxysObOffset] = 0x00; - rom[deoxysObOffset + 1] = Gen3Constants.gbaSetRxOpcode - | Gen3Constants.gbaR1; - rom[deoxysObOffset + 2] = 0x00; - rom[deoxysObOffset + 3] = Gen3Constants.gbaSetRxOpcode - | Gen3Constants.gbaR1; - // Look for the mew check too... it's 0x16 ahead - if (readWord(deoxysObOffset - + Gen3Constants.mewObeyOffsetFromDeoxysObey) == (((Gen3Constants.gbaCmpRxOpcode | Gen3Constants.gbaR0) << 8) | (Gen3Constants.mewIndex))) { - // Bingo, thats CMP R0, 0x97 - // change to CMP R0, 0x0 - writeWord( - deoxysObOffset - + Gen3Constants.mewObeyOffsetFromDeoxysObey, - (((Gen3Constants.gbaCmpRxOpcode | Gen3Constants.gbaR0) << 8) | (0))); - } - } - - // Look for evolutions too - if (romEntry.romType == Gen3Constants.RomType_FRLG) { - int evoJumpOffset = find(Gen3Constants.levelEvoKantoDexCheckCode); - if (evoJumpOffset > 0) { - // This currently compares species to 0x97 and then allows - // evolution if it's <= that. - // Allow it regardless by using an unconditional jump instead - writeWord(evoJumpOffset, Gen3Constants.gbaNopOpcode); - writeWord( - evoJumpOffset + 2, - ((Gen3Constants.gbaUnconditionalJumpOpcode << 8) | (Gen3Constants.levelEvoKantoDexJumpAmount))); - } - - int stoneJumpOffset = find(Gen3Constants.stoneEvoKantoDexCheckCode); - if (stoneJumpOffset > 0) { - // same as the above, but for stone evos - writeWord(stoneJumpOffset, Gen3Constants.gbaNopOpcode); - writeWord( - stoneJumpOffset + 2, - ((Gen3Constants.gbaUnconditionalJumpOpcode << 8) | (Gen3Constants.stoneEvoKantoDexJumpAmount))); - } - } - } - - private void patchForNationalDex() { - log("--Patching for National Dex at Start of Game--"); - String nl = System.getProperty("line.separator"); - int fso = romEntry.getValue("FreeSpace"); - if (romEntry.romType == Gen3Constants.RomType_Ruby - || romEntry.romType == Gen3Constants.RomType_Sapp) { - // Find the original pokedex script - int pkDexOffset = find(Gen3Constants.rsPokedexScriptIdentifier); - if (pkDexOffset < 0) { - log("Patch unsuccessful." + nl); - return; - } - int textPointer = readPointer(pkDexOffset - 4); - int realScriptLocation = pkDexOffset - 8; - int pointerLocToScript = find(pointerToHexString(realScriptLocation)); - if (pointerLocToScript < 0) { - log("Patch unsuccessful." + nl); - return; - } - // Find free space for our new routine - int writeSpace = RomFunctions.freeSpaceFinder(rom, - Gen3Constants.freeSpaceByte, 44, fso); - if (writeSpace < fso) { - log("Patch unsuccessful." + nl); - // Somehow this ROM is full - return; - } - writePointer(pointerLocToScript, writeSpace); - writeHexString(Gen3Constants.rsNatDexScriptPart1, writeSpace); - writePointer(writeSpace + 4, textPointer); - writeHexString(Gen3Constants.rsNatDexScriptPart2, writeSpace + 8); - - } else if (romEntry.romType == Gen3Constants.RomType_FRLG) { - // Find the original pokedex script - int pkDexOffset = find(Gen3Constants.frlgPokedexScriptIdentifier); - if (pkDexOffset < 0) { - log("Patch unsuccessful." + nl); - return; - } - // Find free space for our new routine - int writeSpace = RomFunctions.freeSpaceFinder(rom, - Gen3Constants.freeSpaceByte, 10, fso); - if (writeSpace < fso) { - // Somehow this ROM is full - log("Patch unsuccessful." + nl); - return; - } - rom[pkDexOffset] = 4; - writePointer(pkDexOffset + 1, writeSpace); - rom[pkDexOffset + 5] = 0; // NOP - - // Now write our new routine - writeHexString(Gen3Constants.frlgNatDexScript, writeSpace); - - // Fix people using the national dex flag - List<Integer> ndexChecks = findMultiple(Gen3Constants.frlgNatDexFlagChecker); - for (int ndexCheckOffset : ndexChecks) { - // change to a flag-check - // 82C = "beaten e4/gary once" - writeHexString(Gen3Constants.frlgE4FlagChecker, ndexCheckOffset); - } - - // Fix oak in his lab - int oakLabCheckOffs = find(Gen3Constants.frlgOaksLabKantoDexChecker); - if (oakLabCheckOffs > 0) { - // replace it - writeHexString(Gen3Constants.frlgOaksLabFix, oakLabCheckOffs); - } - - // Fix oak outside your house - int oakHouseCheckOffs = find(Gen3Constants.frlgOakOutsideHouseCheck); - if (oakHouseCheckOffs > 0) { - // fix him to use ndex count - writeHexString(Gen3Constants.frlgOakOutsideHouseFix, - oakHouseCheckOffs); - } - } else { - // Find the original pokedex script - int pkDexOffset = find(Gen3Constants.ePokedexScriptIdentifier); - if (pkDexOffset < 0) { - log("Patch unsuccessful." + nl); - return; - } - int textPointer = readPointer(pkDexOffset - 4); - int realScriptLocation = pkDexOffset - 8; - int pointerLocToScript = find(pointerToHexString(realScriptLocation)); - if (pointerLocToScript < 0) { - log("Patch unsuccessful." + nl); - return; - } - // Find free space for our new routine - int writeSpace = RomFunctions.freeSpaceFinder(rom, - Gen3Constants.freeSpaceByte, 27, fso); - if (writeSpace < fso) { - // Somehow this ROM is full - log("Patch unsuccessful." + nl); - return; - } - writePointer(pointerLocToScript, writeSpace); - writeHexString(Gen3Constants.eNatDexScriptPart1, writeSpace); - writePointer(writeSpace + 4, textPointer); - writeHexString(Gen3Constants.eNatDexScriptPart2, writeSpace + 8); - } - log("Patch successful!" + nl); - } - - public String pointerToHexString(int pointer) { - String hex = String.format("%08X", pointer + 0x08000000); - return new String(new char[] { hex.charAt(6), hex.charAt(7), - hex.charAt(4), hex.charAt(5), hex.charAt(2), hex.charAt(3), - hex.charAt(0), hex.charAt(1) }); - } - - private void populateEvolutions() { - for (Pokemon pkmn : pokes) { - if (pkmn != null) { - pkmn.evolutionsFrom.clear(); - pkmn.evolutionsTo.clear(); - } - } - - int baseOffset = romEntry.getValue("PokemonEvolutions"); - int numInternalPokes = romEntry.getValue("PokemonCount"); - for (int i = 1; i < numRealPokemon; i++) { - Pokemon pk = pokemonList.get(i); - int idx = pokedexToInternal[pk.number]; - int evoOffset = baseOffset + (idx) * 0x28; - for (int j = 0; j < 5; j++) { - int method = readWord(evoOffset + j * 8); - int evolvingTo = readWord(evoOffset + j * 8 + 4); - if (method >= 1 && method <= Gen3Constants.evolutionMethodCount - && evolvingTo >= 1 && evolvingTo <= numInternalPokes) { - int extraInfo = readWord(evoOffset + j * 8 + 2); - EvolutionType et = EvolutionType.fromIndex(3, method); - Evolution evo = new Evolution(pk, - pokesInternal[evolvingTo], true, et, extraInfo); - if (!pk.evolutionsFrom.contains(evo)) { - pk.evolutionsFrom.add(evo); - pokesInternal[evolvingTo].evolutionsTo.add(evo); - } - } - } - // split evos don't carry stats - if (pk.evolutionsFrom.size() > 1) { - for (Evolution e : pk.evolutionsFrom) { - e.carryStats = false; - } - } - } - } - - private void writeEvolutions() { - int baseOffset = romEntry.getValue("PokemonEvolutions"); - for (int i = 1; i < numRealPokemon; i++) { - Pokemon pk = pokemonList.get(i); - int idx = pokedexToInternal[pk.number]; - int evoOffset = baseOffset + (idx) * 0x28; - int evosWritten = 0; - for (Evolution evo : pk.evolutionsFrom) { - writeWord(evoOffset, evo.type.toIndex(3)); - writeWord(evoOffset + 2, evo.extraInfo); - writeWord(evoOffset + 4, pokedexToInternal[evo.to.number]); - writeWord(evoOffset + 6, 0); - evoOffset += 8; - evosWritten++; - if (evosWritten == 5) { - break; - } - } - while (evosWritten < 5) { - writeWord(evoOffset, 0); - writeWord(evoOffset + 2, 0); - writeWord(evoOffset + 4, 0); - writeWord(evoOffset + 6, 0); - evoOffset += 8; - evosWritten++; - } - } - } - - @Override - public void removeTradeEvolutions(boolean changeMoveEvos) { - // no move evos, so no need to check for those - log("--Removing Trade Evolutions--"); - for (Pokemon pkmn : pokes) { - if (pkmn != null) { - for (Evolution evo : pkmn.evolutionsFrom) { - // Not trades, but impossible without trading - if (evo.type == EvolutionType.HAPPINESS_DAY - && romEntry.romType == Gen3Constants.RomType_FRLG) { - // happiness day change to Sun Stone - evo.type = EvolutionType.STONE; - evo.extraInfo = Gen3Constants.sunStoneIndex; // sun - // stone - logEvoChangeStone(evo.from.name, evo.to.name, - itemNames[Gen3Constants.sunStoneIndex]); - } - if (evo.type == EvolutionType.HAPPINESS_NIGHT - && romEntry.romType == Gen3Constants.RomType_FRLG) { - // happiness night change to Moon Stone - evo.type = EvolutionType.STONE; - evo.extraInfo = Gen3Constants.moonStoneIndex; // moon - // stone - logEvoChangeStone(evo.from.name, evo.to.name, - itemNames[Gen3Constants.moonStoneIndex]); - } - if (evo.type == EvolutionType.LEVEL_HIGH_BEAUTY - && romEntry.romType == Gen3Constants.RomType_FRLG) { - // beauty change to level 35 - evo.type = EvolutionType.LEVEL; - evo.extraInfo = 35; - logEvoChangeLevel(evo.from.name, evo.to.name, 35); - } - // Pure Trade - if (evo.type == EvolutionType.TRADE) { - // Haunter, Machoke, Kadabra, Graveler - // Make it into level 37, we're done. - evo.type = EvolutionType.LEVEL; - evo.extraInfo = 37; - logEvoChangeLevel(evo.from.name, evo.to.name, 37); - } - // Trade w/ Held Item - if (evo.type == EvolutionType.TRADE_ITEM) { - if (evo.from.number == Gen3Constants.poliwhirlIndex) { - // Poliwhirl: Lv 37 - evo.type = EvolutionType.LEVEL; - evo.extraInfo = 37; - logEvoChangeLevel(evo.from.name, evo.to.name, 37); - } else if (evo.from.number == Gen3Constants.slowpokeIndex) { - // Slowpoke: Water Stone - evo.type = EvolutionType.STONE; - evo.extraInfo = Gen3Constants.waterStoneIndex; // water - // stone - logEvoChangeStone(evo.from.name, evo.to.name, - itemNames[Gen3Constants.waterStoneIndex]); - } else if (evo.from.number == Gen3Constants.seadraIndex) { - // Seadra: Lv 40 - evo.type = EvolutionType.LEVEL; - evo.extraInfo = 40; - logEvoChangeLevel(evo.from.name, evo.to.name, 40); - } else if (evo.from.number == Gen3Constants.clamperlIndex - && evo.to.number == Gen3Constants.huntailIndex) { - // Clamperl -> Huntail: Lv30 - evo.type = EvolutionType.LEVEL; - evo.extraInfo = 30; - logEvoChangeLevel(evo.from.name, evo.to.name, 30); - } else if (evo.from.number == Gen3Constants.clamperlIndex - && evo.to.number == Gen3Constants.gorebyssIndex) { - // Clamperl -> Gorebyss: Water Stone - evo.type = EvolutionType.STONE; - evo.extraInfo = Gen3Constants.waterStoneIndex; // water - // stone - logEvoChangeStone(evo.from.name, evo.to.name, - itemNames[Gen3Constants.waterStoneIndex]); - } else { - // Onix, Scyther or Porygon: Lv30 - evo.type = EvolutionType.LEVEL; - evo.extraInfo = 30; - logEvoChangeLevel(evo.from.name, evo.to.name, 30); - } - } - } - } - } - logBlankLine(); - } - - @Override - public List<String> getTrainerNames() { - int baseOffset = romEntry.getValue("TrainerData"); - int amount = romEntry.getValue("TrainerCount"); - int entryLen = romEntry.getValue("TrainerEntrySize"); - List<String> theTrainers = new ArrayList<String>(); - for (int i = 1; i < amount; i++) { - theTrainers.add(readVariableLengthString(baseOffset + i * entryLen - + 4)); - } - return theTrainers; - } - - @Override - public void setTrainerNames(List<String> trainerNames) { - int baseOffset = romEntry.getValue("TrainerData"); - int amount = romEntry.getValue("TrainerCount"); - int entryLen = romEntry.getValue("TrainerEntrySize"); - int nameLen = romEntry.getValue("TrainerNameLength"); - Iterator<String> theTrainers = trainerNames.iterator(); - for (int i = 1; i < amount; i++) { - String newName = theTrainers.next(); - writeFixedLengthString(newName, baseOffset + i * entryLen + 4, - nameLen); - } - - } - - @Override - public TrainerNameMode trainerNameMode() { - return TrainerNameMode.MAX_LENGTH; - } - - @Override - public List<Integer> getTCNameLengthsByTrainer() { - // not needed - return new ArrayList<Integer>(); - } - - @Override - public int maxTrainerNameLength() { - return romEntry.getValue("TrainerNameLength") - 1; - } - - @Override - public List<String> getTrainerClassNames() { - int baseOffset = romEntry.getValue("TrainerClassNames"); - int amount = romEntry.getValue("TrainerClassCount"); - int length = romEntry.getValue("TrainerClassNameLength"); - List<String> trainerClasses = new ArrayList<String>(); - for (int i = 0; i < amount; i++) { - trainerClasses - .add(readVariableLengthString(baseOffset + i * length)); - } - return trainerClasses; - } - - @Override - public void setTrainerClassNames(List<String> trainerClassNames) { - int baseOffset = romEntry.getValue("TrainerClassNames"); - int amount = romEntry.getValue("TrainerClassCount"); - int length = romEntry.getValue("TrainerClassNameLength"); - Iterator<String> trainerClasses = trainerClassNames.iterator(); - for (int i = 0; i < amount; i++) { - writeFixedLengthString(trainerClasses.next(), baseOffset + i - * length, length); - } - } - - @Override - public int maxTrainerClassNameLength() { - return romEntry.getValue("TrainerClassNameLength") - 1; - } - - @Override - public boolean fixedTrainerClassNamesLength() { - return false; - } - - @Override - public boolean canChangeStaticPokemon() { - return (romEntry.getValue("StaticPokemonSupport") > 0); - } - - @Override - public String getDefaultExtension() { - return "gba"; - } - - @Override - public int abilitiesPerPokemon() { - return 2; - } - - @Override - public int highestAbilityIndex() { - return Gen3Constants.highestAbilityIndex; - } - - private void loadAbilityNames() { - int nameoffs = romEntry.getValue("AbilityNames"); - int namelen = romEntry.getValue("AbilityNameLength"); - abilityNames = new String[Gen3Constants.highestAbilityIndex + 1]; - for (int i = 0; i <= Gen3Constants.highestAbilityIndex; i++) { - abilityNames[i] = readFixedLengthString(nameoffs + namelen * i, - namelen); - } - } - - @Override - public String abilityName(int number) { - return abilityNames[number]; - } - - @Override - public int internalStringLength(String string) { - return translateString(string).length; - } - - @Override - public void applySignature() { - // FRLG - if (romEntry.romType == Gen3Constants.RomType_FRLG) { - // intro sprites : first 255 only due to size - Pokemon introPk = randomPokemonLimited(255, false); - if (introPk == null) { - return; - } - int introPokemon = pokedexToInternal[introPk.number]; - int frontSprites = romEntry.getValue("FrontSprites"); - int palettes = romEntry.getValue("PokemonPalettes"); - - rom[romEntry.getValue("IntroCryOffset")] = (byte) introPokemon; - rom[romEntry.getValue("IntroOtherOffset")] = (byte) introPokemon; - - int spriteBase = romEntry.getValue("IntroSpriteOffset"); - writePointer(spriteBase, frontSprites + introPokemon * 8); - writePointer(spriteBase + 4, palettes + introPokemon * 8); - } else if (romEntry.romType == Gen3Constants.RomType_Ruby - || romEntry.romType == Gen3Constants.RomType_Sapp) { - // intro sprites : hoenn pokes only - int numInternalPokes = romEntry.getValue("PokemonCount"); - int introPokemon = this.random.nextInt(Math.min(numInternalPokes, - 509)) + 1; - while (internalToPokedex[introPokemon] < Gen3Constants.hoennPokesStart) { - introPokemon = this.random.nextInt(Math.min(numInternalPokes, - 509)) + 1; - } - int frontSprites = romEntry.getValue("PokemonFrontSprites"); - int palettes = romEntry.getValue("PokemonNormalPalettes"); - int cryCommand = romEntry.getValue("IntroCryOffset"); - int otherCommand = romEntry.getValue("IntroOtherOffset"); - - if (introPokemon > 255) { - rom[cryCommand] = (byte) 0xFF; - rom[cryCommand + 1] = Gen3Constants.gbaSetRxOpcode - | Gen3Constants.gbaR0; - - rom[cryCommand + 2] = (byte) (introPokemon - 0xFF); - rom[cryCommand + 3] = Gen3Constants.gbaAddRxOpcode - | Gen3Constants.gbaR0; - - rom[otherCommand] = (byte) 0xFF; - rom[otherCommand + 1] = Gen3Constants.gbaSetRxOpcode - | Gen3Constants.gbaR4; - - rom[otherCommand + 2] = (byte) (introPokemon - 0xFF); - rom[otherCommand + 3] = Gen3Constants.gbaAddRxOpcode - | Gen3Constants.gbaR4; - } else { - rom[cryCommand] = (byte) introPokemon; - rom[cryCommand + 1] = Gen3Constants.gbaSetRxOpcode - | Gen3Constants.gbaR0; - - writeWord(cryCommand + 2, Gen3Constants.gbaNopOpcode); - - rom[otherCommand] = (byte) introPokemon; - rom[otherCommand + 1] = Gen3Constants.gbaSetRxOpcode - | Gen3Constants.gbaR4; - - writeWord(otherCommand + 2, Gen3Constants.gbaNopOpcode); - } - - writePointer(romEntry.getValue("IntroSpriteOffset"), frontSprites - + introPokemon * 8); - writePointer(romEntry.getValue("IntroPaletteOffset"), palettes - + introPokemon * 8); - } else { - // Emerald, intro sprite: any Pokemon. - int introPokemon = pokedexToInternal[randomPokemon().number]; - writeWord(romEntry.getValue("IntroSpriteOffset"), introPokemon); - writeWord(romEntry.getValue("IntroCryOffset"), introPokemon); - } - - } - - private Pokemon randomPokemonLimited(int maxValue, boolean blockNonMales) { - checkPokemonRestrictions(); - List<Pokemon> validPokemon = new ArrayList<Pokemon>(); - for (Pokemon pk : this.mainPokemonList) { - if (pokedexToInternal[pk.number] <= maxValue - && (!blockNonMales || pk.genderRatio <= 0xFD)) { - validPokemon.add(pk); - } - } - if (validPokemon.size() == 0) { - return null; - } else { - return validPokemon.get(random.nextInt(validPokemon.size())); - } - } - - private void determineMapBankSizes() { - int mbpsOffset = romEntry.getValue("MapHeaders"); - List<Integer> mapBankOffsets = new ArrayList<Integer>(); - - int offset = mbpsOffset; - - // find map banks - while (true) { - boolean valid = true; - for (int mbOffset : mapBankOffsets) { - if (mbpsOffset < mbOffset && offset >= mbOffset) { - valid = false; - break; - } - } - if (!valid) { - break; - } - int newMBOffset = readPointer(offset); - if (newMBOffset < 0 || newMBOffset >= rom.length) { - break; - } - mapBankOffsets.add(newMBOffset); - offset += 4; - } - int bankCount = mapBankOffsets.size(); - int[] bankMapCounts = new int[bankCount]; - for (int bank = 0; bank < bankCount; bank++) { - int baseBankOffset = mapBankOffsets.get(bank); - int count = 0; - offset = baseBankOffset; - while (true) { - boolean valid = true; - for (int mbOffset : mapBankOffsets) { - if (baseBankOffset < mbOffset && offset >= mbOffset) { - valid = false; - break; - } - } - if (!valid) { - break; - } - if (baseBankOffset < mbpsOffset && offset >= mbpsOffset) { - break; - } - int newMapOffset = readPointer(offset); - if (newMapOffset < 0 || newMapOffset >= rom.length) { - break; - } - count++; - offset += 4; - } - bankMapCounts[bank] = count; - } - - romEntry.entries.put("MapBankCount", bankCount); - romEntry.arrayEntries.put("MapBankSizes", bankMapCounts); - } - - private void preprocessMaps() { - itemOffs = new ArrayList<Integer>(); - int bankCount = romEntry.getValue("MapBankCount"); - int[] bankMapCounts = romEntry.arrayEntries.get("MapBankSizes"); - int itemBall = romEntry.getValue("ItemBallPic"); - mapNames = new String[bankCount][]; - int mbpsOffset = romEntry.getValue("MapHeaders"); - int mapLabels = romEntry.getValue("MapLabels"); - Map<Integer, String> mapLabelsM = new HashMap<Integer, String>(); - for (int bank = 0; bank < bankCount; bank++) { - int bankOffset = readPointer(mbpsOffset + bank * 4); - mapNames[bank] = new String[bankMapCounts[bank]]; - for (int map = 0; map < bankMapCounts[bank]; map++) { - int mhOffset = readPointer(bankOffset + map * 4); - - // map name - int mapLabel = rom[mhOffset + 0x14] & 0xFF; - if (mapLabelsM.containsKey(mapLabel)) { - mapNames[bank][map] = mapLabelsM.get(mapLabel); - } else { - if (romEntry.romType == Gen3Constants.RomType_FRLG) { - mapNames[bank][map] = readVariableLengthString(readPointer(mapLabels - + (mapLabel - Gen3Constants.frlgMapLabelsStart) - * 4)); - } else { - mapNames[bank][map] = readVariableLengthString(readPointer(mapLabels - + mapLabel * 8 + 4)); - } - mapLabelsM.put(mapLabel, mapNames[bank][map]); - } - - // events - int eventOffset = readPointer(mhOffset + 4); - if (eventOffset >= 0 && eventOffset < rom.length) { - - int pCount = rom[eventOffset] & 0xFF; - int spCount = rom[eventOffset + 3] & 0xFF; - - if (pCount > 0) { - int peopleOffset = readPointer(eventOffset + 4); - for (int p = 0; p < pCount; p++) { - int pSprite = rom[peopleOffset + p * 24 + 1]; - if (pSprite == itemBall - && readPointer(peopleOffset + p * 24 + 16) >= 0) { - // Get script and look inside - int scriptOffset = readPointer(peopleOffset + p - * 24 + 16); - if (rom[scriptOffset] == 0x1A - && rom[scriptOffset + 1] == 0x00 - && (rom[scriptOffset + 2] & 0xFF) == 0x80 - && rom[scriptOffset + 5] == 0x1A - && rom[scriptOffset + 6] == 0x01 - && (rom[scriptOffset + 7] & 0xFF) == 0x80 - && rom[scriptOffset + 10] == 0x09 - && (rom[scriptOffset + 11] == 0x00 || rom[scriptOffset + 11] == 0x01)) { - // item ball script - itemOffs.add(scriptOffset + 3); - } - } - } - // TM Text? - for (TMOrMTTextEntry tte : romEntry.tmmtTexts) { - if (tte.mapBank == bank && tte.mapNumber == map) { - // process this one - int scriptOffset = readPointer(peopleOffset - + (tte.personNum - 1) * 24 + 16); - if (scriptOffset >= 0) { - if (romEntry.romType == Gen3Constants.RomType_FRLG - && tte.isMoveTutor - && (tte.number == 5 || (tte.number >= 8 && tte.number <= 11))) { - scriptOffset = readPointer(scriptOffset + 1); - } else if (romEntry.romType == Gen3Constants.RomType_FRLG - && tte.isMoveTutor - && tte.number == 7) { - scriptOffset = readPointer(scriptOffset + 0x1F); - } - int lookAt = scriptOffset - + tte.offsetInScript; - // make sure this actually looks like a text - // pointer - if (lookAt >= 0 && lookAt < rom.length - 2) { - if (rom[lookAt + 3] == 0x08 - || rom[lookAt + 3] == 0x09) { - // okay, it passes the basic test - tte.actualOffset = lookAt; - } - } - } - } - } - } - - if (spCount > 0) { - int signpostsOffset = readPointer(eventOffset + 16); - for (int sp = 0; sp < spCount; sp++) { - int spType = rom[signpostsOffset + sp * 12 + 5]; - if (spType >= 5 && spType <= 7) { - // hidden item - int itemHere = readWord(signpostsOffset + sp - * 12 + 8); - if (itemHere != 0) { - // itemid 0 is coins - itemOffs.add(signpostsOffset + sp * 12 + 8); - } - } - } - } - } - } - } - } - - @Override - public ItemList getAllowedItems() { - return Gen3Constants.allowedItems; - } - - @Override - public ItemList getNonBadItems() { - return Gen3Constants.nonBadItems; - } - - private void loadItemNames() { - int nameoffs = romEntry.getValue("ItemData"); - int structlen = romEntry.getValue("ItemEntrySize"); - int maxcount = romEntry.getValue("ItemCount"); - itemNames = new String[maxcount + 1]; - for (int i = 0; i <= maxcount; i++) { - itemNames[i] = readVariableLengthString(nameoffs + structlen * i); - } - } - - @Override - public String[] getItemNames() { - return itemNames; - } - - @Override - public List<Integer> getRequiredFieldTMs() { - if (romEntry.romType == Gen3Constants.RomType_FRLG) { - return Gen3Constants.frlgRequiredFieldTMs; - } else if (romEntry.romType == Gen3Constants.RomType_Ruby - || romEntry.romType == Gen3Constants.RomType_Sapp) { - return Gen3Constants.rsRequiredFieldTMs; - } else { - // emerald has a few TMs from pickup - return Gen3Constants.eRequiredFieldTMs; - } - } - - @Override - public List<Integer> getCurrentFieldTMs() { - if (!mapLoadingDone) { - preprocessMaps(); - mapLoadingDone = true; - } - List<Integer> fieldTMs = new ArrayList<Integer>(); - - for (int offset : itemOffs) { - int itemHere = readWord(offset); - if (Gen3Constants.allowedItems.isTM(itemHere)) { - int thisTM = itemHere - Gen3Constants.tmItemOffset + 1; - // hack for repeat TMs - if (fieldTMs.contains(thisTM) == false) { - fieldTMs.add(thisTM); - } - } - } - return fieldTMs; - } - - @Override - public void setFieldTMs(List<Integer> fieldTMs) { - if (!mapLoadingDone) { - preprocessMaps(); - mapLoadingDone = true; - } - Iterator<Integer> iterTMs = fieldTMs.iterator(); - int[] givenTMs = new int[512]; - - for (int offset : itemOffs) { - int itemHere = readWord(offset); - if (Gen3Constants.allowedItems.isTM(itemHere)) { - // Cache replaced TMs to duplicate repeats - if (givenTMs[itemHere] != 0) { - rom[offset] = (byte) givenTMs[itemHere]; - } else { - // Replace this with a TM from the list - int tm = iterTMs.next(); - tm += Gen3Constants.tmItemOffset - 1; - givenTMs[itemHere] = tm; - writeWord(offset, tm); - } - } - } - } - - @Override - public List<Integer> getRegularFieldItems() { - if (!mapLoadingDone) { - preprocessMaps(); - mapLoadingDone = true; - } - List<Integer> fieldItems = new ArrayList<Integer>(); - - for (int offset : itemOffs) { - int itemHere = readWord(offset); - if (Gen3Constants.allowedItems.isAllowed(itemHere) - && !(Gen3Constants.allowedItems.isTM(itemHere))) { - fieldItems.add(itemHere); - } - } - return fieldItems; - } - - @Override - public void setRegularFieldItems(List<Integer> items) { - if (!mapLoadingDone) { - preprocessMaps(); - mapLoadingDone = true; - } - Iterator<Integer> iterItems = items.iterator(); - - for (int offset : itemOffs) { - int itemHere = readWord(offset); - if (Gen3Constants.allowedItems.isAllowed(itemHere) - && !(Gen3Constants.allowedItems.isTM(itemHere))) { - // Replace it - writeWord(offset, iterItems.next()); - } - } - - } - - @Override - public List<IngameTrade> getIngameTrades() { - List<IngameTrade> trades = new ArrayList<IngameTrade>(); - - // info - int tableOffset = romEntry.getValue("TradeTableOffset"); - int tableSize = romEntry.getValue("TradeTableSize"); - int[] unused = romEntry.arrayEntries.get("TradesUnused"); - int unusedOffset = 0; - int entryLength = 60; - - for (int entry = 0; entry < tableSize; entry++) { - if (unusedOffset < unused.length && unused[unusedOffset] == entry) { - unusedOffset++; - continue; - } - IngameTrade trade = new IngameTrade(); - int entryOffset = tableOffset + entry * entryLength; - trade.nickname = readVariableLengthString(entryOffset); - trade.givenPokemon = pokesInternal[readWord(entryOffset + 12)]; - trade.ivs = new int[6]; - for (int i = 0; i < 6; i++) { - trade.ivs[i] = rom[entryOffset + 14 + i] & 0xFF; - } - trade.otId = readWord(entryOffset + 24); - trade.item = readWord(entryOffset + 40); - trade.otName = readVariableLengthString(entryOffset + 43); - trade.requestedPokemon = pokesInternal[readWord(entryOffset + 56)]; - trades.add(trade); - } - - return trades; - - } - - @Override - public void setIngameTrades(List<IngameTrade> trades) { - // info - int tableOffset = romEntry.getValue("TradeTableOffset"); - int tableSize = romEntry.getValue("TradeTableSize"); - int[] unused = romEntry.arrayEntries.get("TradesUnused"); - int unusedOffset = 0; - int entryLength = 60; - int tradeOffset = 0; - - for (int entry = 0; entry < tableSize; entry++) { - if (unusedOffset < unused.length && unused[unusedOffset] == entry) { - unusedOffset++; - continue; - } - IngameTrade trade = trades.get(tradeOffset++); - int entryOffset = tableOffset + entry * entryLength; - writeFixedLengthString(trade.nickname, entryOffset, 12); - writeWord(entryOffset + 12, - pokedexToInternal[trade.givenPokemon.number]); - for (int i = 0; i < 6; i++) { - rom[entryOffset + 14 + i] = (byte) trade.ivs[i]; - } - writeWord(entryOffset + 24, trade.otId); - writeWord(entryOffset + 40, trade.item); - writeFixedLengthString(trade.otName, entryOffset + 43, 11); - writeWord(entryOffset + 56, - pokedexToInternal[trade.requestedPokemon.number]); - } - } - - @Override - public boolean hasDVs() { - return false; - } - - @Override - public int generationOfPokemon() { - return 3; - } - - @Override - public void removeEvosForPokemonPool() { - List<Pokemon> pokemonIncluded = this.mainPokemonList; - Set<Evolution> keepEvos = new HashSet<Evolution>(); - for (Pokemon pk : pokes) { - if (pk != null) { - keepEvos.clear(); - for (Evolution evol : pk.evolutionsFrom) { - if (pokemonIncluded.contains(evol.from) - && pokemonIncluded.contains(evol.to)) { - keepEvos.add(evol); - } else { - evol.to.evolutionsTo.remove(evol); - } - } - pk.evolutionsFrom.retainAll(keepEvos); - } - } - } - - @Override - public boolean supportsFourStartingMoves() { - return true; - } - - @Override - public List<Integer> getFieldMoves() { - // cut, fly, surf, strength, flash, - // dig, teleport, waterfall, - // rock smash, sweet scent - // not softboiled or milk drink - // dive and secret power in RSE only - if (romEntry.romType == Gen3Constants.RomType_FRLG) { - return Gen3Constants.frlgFieldMoves; - } else { - return Gen3Constants.rseFieldMoves; - } - } - - @Override - public List<Integer> getEarlyRequiredHMMoves() { - // RSE: rock smash - // FRLG: cut - if (romEntry.romType == Gen3Constants.RomType_FRLG) { - return Gen3Constants.frlgEarlyRequiredHMMoves; - } else { - return Gen3Constants.rseEarlyRequiredHMMoves; - } - } - - @Override - public int miscTweaksAvailable() { - int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); - available |= MiscTweak.NATIONAL_DEX_AT_START.getValue(); - if (romEntry.getValue("RunIndoorsTweakOffset") > 0) { - available |= MiscTweak.RUNNING_SHOES_INDOORS.getValue(); - } - if (romEntry.getValue("TextSpeedValuesOffset") > 0) { - available |= MiscTweak.FASTEST_TEXT.getValue(); - } - if (romEntry.getValue("CatchingTutorialOpponentMonOffset") > 0 - || romEntry.getValue("CatchingTutorialPlayerMonOffset") > 0) { - available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); - } - - return available; - } - - @Override - public void applyMiscTweak(MiscTweak tweak) { - if (tweak == MiscTweak.RUNNING_SHOES_INDOORS) { - applyRunningShoesIndoorsPatch(); - } else if (tweak == MiscTweak.FASTEST_TEXT) { - applyFastestTextPatch(); - } else if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { - applyCamelCaseNames(); - } else if (tweak == MiscTweak.NATIONAL_DEX_AT_START) { - patchForNationalDex(); - } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { - randomizeCatchingTutorial(); - } - } - - private void randomizeCatchingTutorial() { - if (romEntry.getValue("CatchingTutorialOpponentMonOffset") > 0) { - int oppOffset = romEntry - .getValue("CatchingTutorialOpponentMonOffset"); - if (romEntry.romType == Gen3Constants.RomType_FRLG) { - Pokemon opponent = randomPokemonLimited(255, true); - if (opponent != null) { - - int oppValue = pokedexToInternal[opponent.number]; - rom[oppOffset] = (byte) oppValue; - rom[oppOffset + 1] = Gen3Constants.gbaSetRxOpcode - | Gen3Constants.gbaR1; - } - } else { - Pokemon opponent = randomPokemonLimited(510, true); - if (opponent != null) { - int oppValue = pokedexToInternal[opponent.number]; - if (oppValue > 255) { - rom[oppOffset] = (byte) 0xFF; - rom[oppOffset + 1] = Gen3Constants.gbaSetRxOpcode - | Gen3Constants.gbaR1; - - rom[oppOffset + 2] = (byte) (oppValue - 0xFF); - rom[oppOffset + 3] = Gen3Constants.gbaAddRxOpcode - | Gen3Constants.gbaR1; - } else { - rom[oppOffset] = (byte) oppValue; - rom[oppOffset + 1] = Gen3Constants.gbaSetRxOpcode - | Gen3Constants.gbaR1; - - writeWord(oppOffset + 2, Gen3Constants.gbaNopOpcode); - } - } - } - } - - if (romEntry.getValue("CatchingTutorialPlayerMonOffset") > 0) { - int playerOffset = romEntry - .getValue("CatchingTutorialPlayerMonOffset"); - Pokemon playerMon = randomPokemonLimited(510, false); - if (playerMon != null) { - int plyValue = pokedexToInternal[playerMon.number]; - if (plyValue > 255) { - rom[playerOffset] = (byte) 0xFF; - rom[playerOffset + 1] = Gen3Constants.gbaSetRxOpcode - | Gen3Constants.gbaR1; - - rom[playerOffset + 2] = (byte) (plyValue - 0xFF); - rom[playerOffset + 3] = Gen3Constants.gbaAddRxOpcode - | Gen3Constants.gbaR1; - } else { - rom[playerOffset] = (byte) plyValue; - rom[playerOffset + 1] = Gen3Constants.gbaSetRxOpcode - | Gen3Constants.gbaR1; - - writeWord(playerOffset + 2, Gen3Constants.gbaNopOpcode); - } - } - } - - } - - private void applyRunningShoesIndoorsPatch() { - if (romEntry.getValue("RunIndoorsTweakOffset") != 0) { - rom[romEntry.getValue("RunIndoorsTweakOffset")] = 0x00; - } - } - - private void applyFastestTextPatch() { - if (romEntry.getValue("TextSpeedValuesOffset") > 0) { - int tsvOffset = romEntry.getValue("TextSpeedValuesOffset"); - rom[tsvOffset] = 4; // slow = medium - rom[tsvOffset + 1] = 1; // medium = fast - rom[tsvOffset + 2] = 0; // fast = instant - } - } - - @Override - public boolean isROMHack() { - return this.isRomHack; - } - - @Override - public BufferedImage getMascotImage() { - Pokemon mascotPk = randomPokemon(); - int mascotPokemon = pokedexToInternal[mascotPk.number]; - int frontSprites = romEntry.getValue("FrontSprites"); - int palettes = romEntry.getValue("PokemonPalettes"); - int fsOffset = readPointer(frontSprites + mascotPokemon * 8); - int palOffset = readPointer(palettes + mascotPokemon * 8); - - byte[] trueFrontSprite = DSDecmp.Decompress(rom, fsOffset); - byte[] truePalette = DSDecmp.Decompress(rom, palOffset); - - // Convert palette into RGB - int[] convPalette = new int[16]; - // Leave palette[0] as 00000000 for transparency - for (int i = 0; i < 15; i++) { - int palValue = readWord(truePalette, i * 2 + 2); - convPalette[i + 1] = GFXFunctions.conv16BitColorToARGB(palValue); - } - - // Make image, 4bpp - BufferedImage bim = GFXFunctions.drawTiledImage(trueFrontSprite, - convPalette, 64, 64, 4); - return bim; - } + public static class Factory extends RomHandler.Factory { + + @Override + public Gen3RomHandler create(Random random, PrintStream logStream) { + return new Gen3RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + long fileLength = new File(filename).length(); + if (fileLength > 32 * 1024 * 1024) { + return false; + } + byte[] loaded = loadFilePartial(filename, 0x100000); + if (loaded.length == 0) { + // nope + return false; + } + return detectRomInner(loaded, (int) fileLength); + } + } + + public Gen3RomHandler(Random random) { + super(random, null); + } + + public Gen3RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + private static class RomEntry { + private String name; + private String romCode; + private String tableFile; + private int version; + private int romType; + private boolean copyStaticPokemon; + private Map<String, Integer> entries = new HashMap<String, Integer>(); + private Map<String, int[]> arrayEntries = new HashMap<String, int[]>(); + private List<StaticPokemon> staticPokemon = new ArrayList<StaticPokemon>(); + private List<TMOrMTTextEntry> tmmtTexts = new ArrayList<TMOrMTTextEntry>(); + + public RomEntry() { + + } + + public RomEntry(RomEntry toCopy) { + this.name = toCopy.name; + this.romCode = toCopy.romCode; + this.tableFile = toCopy.tableFile; + this.version = toCopy.version; + this.romType = toCopy.romType; + this.copyStaticPokemon = toCopy.copyStaticPokemon; + this.entries.putAll(toCopy.entries); + this.arrayEntries.putAll(toCopy.arrayEntries); + this.staticPokemon.addAll(toCopy.staticPokemon); + this.tmmtTexts.addAll(toCopy.tmmtTexts); + } + + private int getValue(String key) { + if (!entries.containsKey(key)) { + entries.put(key, 0); + } + return entries.get(key); + } + } + + private static class TMOrMTTextEntry { + private int number; + private int mapBank, mapNumber; + private int personNum; + private int offsetInScript; + private int actualOffset; + private String template; + private boolean isMoveTutor; + } + + private static List<RomEntry> roms; + + static { + loadROMInfo(); + } + + private static void loadROMInfo() { + roms = new ArrayList<RomEntry>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen3_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + // Static Pokemon? + if (r[0].equals("StaticPokemon[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.staticPokemon.add(new StaticPokemon(offs)); + } else { + int offs = parseRIInt(r[1]); + current.staticPokemon.add(new StaticPokemon(offs)); + } + } else if (r[0].equals("TMText[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] parts = r[1].substring(1, r[1].length() - 1).split(",", 6); + TMOrMTTextEntry tte = new TMOrMTTextEntry(); + tte.number = parseRIInt(parts[0]); + tte.mapBank = parseRIInt(parts[1]); + tte.mapNumber = parseRIInt(parts[2]); + tte.personNum = parseRIInt(parts[3]); + tte.offsetInScript = parseRIInt(parts[4]); + tte.template = parts[5]; + tte.isMoveTutor = false; + current.tmmtTexts.add(tte); + } + } else if (r[0].equals("MoveTutorText[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] parts = r[1].substring(1, r[1].length() - 1).split(",", 6); + TMOrMTTextEntry tte = new TMOrMTTextEntry(); + tte.number = parseRIInt(parts[0]); + tte.mapBank = parseRIInt(parts[1]); + tte.mapNumber = parseRIInt(parts[2]); + tte.personNum = parseRIInt(parts[3]); + tte.offsetInScript = parseRIInt(parts[4]); + tte.template = parts[5]; + tte.isMoveTutor = true; + current.tmmtTexts.add(tte); + } + } else if (r[0].equals("Game")) { + current.romCode = r[1]; + } else if (r[0].equals("Version")) { + current.version = parseRIInt(r[1]); + } else if (r[0].equals("Type")) { + if (r[1].equalsIgnoreCase("Ruby")) { + current.romType = Gen3Constants.RomType_Ruby; + } else if (r[1].equalsIgnoreCase("Sapp")) { + current.romType = Gen3Constants.RomType_Sapp; + } else if (r[1].equalsIgnoreCase("Em")) { + current.romType = Gen3Constants.RomType_Em; + } else if (r[1].equalsIgnoreCase("FRLG")) { + current.romType = Gen3Constants.RomType_FRLG; + } else { + System.err.println("unrecognised rom type: " + r[1]); + } + } else if (r[0].equals("TableFile")) { + current.tableFile = r[1]; + } else if (r[0].equals("CopyStaticPokemon")) { + int csp = parseRIInt(r[1]); + current.copyStaticPokemon = (csp > 0); + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.name)) { + // copy from here + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.entries.putAll(otherEntry.entries); + boolean cTT = (current.getValue("CopyTMText") == 1); + if (current.copyStaticPokemon) { + current.staticPokemon.addAll(otherEntry.staticPokemon); + current.entries.put("StaticPokemonSupport", 1); + } else { + current.entries.put("StaticPokemonSupport", 0); + } + if (cTT) { + current.tmmtTexts.addAll(otherEntry.tmmtTexts); + } + current.tableFile = otherEntry.tableFile; + } + } + } else { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.arrayEntries.put(r[0], offs); + } + } else { + int offs = parseRIInt(r[1]); + current.entries.put(r[0], offs); + } + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + } + + } + + 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 void loadTextTable(String filename) { + try { + Scanner sc = new Scanner(FileFunctions.openConfig(filename + ".tbl"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine(); + if (!q.trim().isEmpty()) { + String[] r = q.split("=", 2); + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + tb[Integer.parseInt(r[0], 16)] = r[1]; + d.put(r[1], (byte) Integer.parseInt(r[0], 16)); + } + } + sc.close(); + } catch (FileNotFoundException e) { + } + + } + + // This ROM's data + private Pokemon[] pokes, pokesInternal; + private List<Pokemon> pokemonList; + private int numRealPokemon; + private Move[] moves; + private boolean jamboMovesetHack; + private RomEntry romEntry; + private boolean havePatchedObedience; + public String[] tb; + public Map<String, Byte> d; + private String[] abilityNames; + private String[] itemNames; + private boolean mapLoadingDone; + private List<Integer> itemOffs; + private String[][] mapNames; + private boolean isRomHack; + private int[] internalToPokedex, pokedexToInternal; + private int pokedexCount; + private String[] pokeNames; + + @Override + public boolean detectRom(byte[] rom) { + return detectRomInner(rom, rom.length); + } + + private static boolean detectRomInner(byte[] rom, int romSize) { + if (romSize != Gen3Constants.size8M && romSize != Gen3Constants.size16M && romSize != Gen3Constants.size32M) { + return false; // size check + } + // Special case for Emerald unofficial translation + if (romName(rom, Gen3Constants.unofficialEmeraldROMName)) { + // give it a rom code so it can be detected + rom[Gen3Constants.romCodeOffset] = 'B'; + rom[Gen3Constants.romCodeOffset + 1] = 'P'; + rom[Gen3Constants.romCodeOffset + 2] = 'E'; + rom[Gen3Constants.romCodeOffset + 3] = 'T'; + rom[Gen3Constants.headerChecksumOffset] = 0x66; + } + // Wild Pokemon header + if (find(rom, Gen3Constants.wildPokemonPointerPrefix) == -1) { + return false; + } + // Map Banks header + if (find(rom, Gen3Constants.mapBanksPointerPrefix) == -1) { + return false; + } + // Pokedex Order header + if (findMultiple(rom, Gen3Constants.pokedexOrderPointerPrefix).size() != 3) { + return false; + } + for (RomEntry re : roms) { + if (romCode(rom, re.romCode) && (rom[Gen3Constants.romVersionOffset] & 0xFF) == re.version) { + return true; // match + } + } + return false; // GBA rom we don't support yet + } + + @Override + public void loadedRom() { + for (RomEntry re : roms) { + if (romCode(rom, re.romCode) && (rom[0xBC] & 0xFF) == re.version) { + romEntry = new RomEntry(re); // clone so we can modify + break; + } + } + + tb = new String[256]; + d = new HashMap<String, Byte>(); + isRomHack = false; + jamboMovesetHack = false; + + // Pokemon count stuff, needs to be available first + List<Integer> pokedexOrderPrefixes = findMultiple(rom, Gen3Constants.pokedexOrderPointerPrefix); + romEntry.entries.put("PokedexOrder", readPointer(pokedexOrderPrefixes.get(1) + 16)); + + // Pokemon names offset + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + int baseNomOffset = find(rom, Gen3Constants.rsPokemonNamesPointerSuffix); + romEntry.entries.put("PokemonNames", readPointer(baseNomOffset - 4)); + romEntry.entries.put( + "FrontSprites", + readPointer(findPointerPrefixAndSuffix(Gen3Constants.rsFrontSpritesPointerPrefix, + Gen3Constants.rsFrontSpritesPointerSuffix))); + romEntry.entries.put( + "PokemonPalettes", + readPointer(findPointerPrefixAndSuffix(Gen3Constants.rsPokemonPalettesPointerPrefix, + Gen3Constants.rsPokemonPalettesPointerSuffix))); + } else { + romEntry.entries.put("PokemonNames", readPointer(Gen3Constants.efrlgPokemonNamesPointer)); + romEntry.entries.put("MoveNames", readPointer(Gen3Constants.efrlgMoveNamesPointer)); + romEntry.entries.put("AbilityNames", readPointer(Gen3Constants.efrlgAbilityNamesPointer)); + romEntry.entries.put("ItemData", readPointer(Gen3Constants.efrlgItemDataPointer)); + romEntry.entries.put("MoveData", readPointer(Gen3Constants.efrlgMoveDataPointer)); + romEntry.entries.put("PokemonStats", readPointer(Gen3Constants.efrlgPokemonStatsPointer)); + romEntry.entries.put("FrontSprites", readPointer(Gen3Constants.efrlgFrontSpritesPointer)); + romEntry.entries.put("PokemonPalettes", readPointer(Gen3Constants.efrlgPokemonPalettesPointer)); + romEntry.entries.put("MoveTutorCompatibility", + romEntry.getValue("MoveTutorData") + romEntry.getValue("MoveTutorMoves") * 2); + } + + loadTextTable(romEntry.tableFile); + + if (romEntry.romCode.equals("BPRE") && romEntry.version == 0) { + basicBPRE10HackSupport(); + } + + loadPokemonNames(); + loadPokedex(); + loadPokemonStats(); + constructPokemonList(); + loadMoves(); + + // Get wild Pokemon offset + int baseWPOffset = findMultiple(rom, Gen3Constants.wildPokemonPointerPrefix).get(0); + romEntry.entries.put("WildPokemon", readPointer(baseWPOffset + 12)); + + // map banks + int baseMapsOffset = findMultiple(rom, Gen3Constants.mapBanksPointerPrefix).get(0); + romEntry.entries.put("MapHeaders", readPointer(baseMapsOffset + 12)); + this.determineMapBankSizes(); + + // map labels + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + int baseMLOffset = find(rom, Gen3Constants.frlgMapLabelsPointerPrefix); + romEntry.entries.put("MapLabels", readPointer(baseMLOffset + 12)); + } else { + int baseMLOffset = find(rom, Gen3Constants.rseMapLabelsPointerPrefix); + romEntry.entries.put("MapLabels", readPointer(baseMLOffset + 12)); + } + + mapLoadingDone = false; + loadAbilityNames(); + loadItemNames(); + + } + + private int findPointerPrefixAndSuffix(String prefix, String suffix) { + if (prefix.length() % 2 != 0 || suffix.length() % 2 != 0) { + return -1; + } + byte[] searchPref = new byte[prefix.length() / 2]; + for (int i = 0; i < searchPref.length; i++) { + searchPref[i] = (byte) Integer.parseInt(prefix.substring(i * 2, i * 2 + 2), 16); + } + byte[] searchSuff = new byte[suffix.length() / 2]; + for (int i = 0; i < searchSuff.length; i++) { + searchSuff[i] = (byte) Integer.parseInt(suffix.substring(i * 2, i * 2 + 2), 16); + } + if (searchPref.length >= searchSuff.length) { + // Prefix first + List<Integer> offsets = RomFunctions.search(rom, searchPref); + if (offsets.size() == 0) { + return -1; + } + for (int prefOffset : offsets) { + if (prefOffset + 4 + searchSuff.length > rom.length) { + continue; // not enough room for this to be valid + } + int ptrOffset = prefOffset + searchPref.length; + int pointerValue = readPointer(ptrOffset); + if (pointerValue < 0 || pointerValue >= rom.length) { + // Not a valid pointer + continue; + } + boolean suffixMatch = true; + for (int i = 0; i < searchSuff.length; i++) { + if (rom[ptrOffset + 4 + i] != searchSuff[i]) { + suffixMatch = false; + break; + } + } + if (suffixMatch) { + return ptrOffset; + } + } + return -1; // No match + } else { + // Suffix first + List<Integer> offsets = RomFunctions.search(rom, searchSuff); + if (offsets.size() == 0) { + return -1; + } + for (int suffOffset : offsets) { + if (suffOffset - 4 - searchPref.length < 0) { + continue; // not enough room for this to be valid + } + int ptrOffset = suffOffset - 4; + int pointerValue = readPointer(ptrOffset); + if (pointerValue < 0 || pointerValue >= rom.length) { + // Not a valid pointer + continue; + } + boolean prefixMatch = true; + for (int i = 0; i < searchPref.length; i++) { + if (rom[ptrOffset - searchPref.length + i] != searchPref[i]) { + prefixMatch = false; + break; + } + } + if (prefixMatch) { + return ptrOffset; + } + } + return -1; // No match + } + } + + private void basicBPRE10HackSupport() { + if (basicBPRE10HackDetection()) { + this.isRomHack = true; + // NUMBER OF POKEMON DETECTION + + // this is the most annoying bit + // we'll try to get it from the pokemon names, + // and sanity check it using other things + // this of course means we can't support + // any hack with extended length names + + int iPokemonCount = 0; + int namesOffset = romEntry.getValue("PokemonNames"); + int nameLen = romEntry.getValue("PokemonNameLength"); + while (true) { + int nameOffset = namesOffset + (iPokemonCount + 1) * nameLen; + int nameStrLen = lengthOfStringAt(nameOffset); + if (nameStrLen > 0 && nameStrLen < nameLen && rom[nameOffset] != 0) { + iPokemonCount++; + } else { + break; + } + } + + // Is there an unused egg slot at the end? + String lastName = readVariableLengthString(namesOffset + iPokemonCount * nameLen); + if (lastName.equals("?") || lastName.equals("-")) { + iPokemonCount--; + } + + // Jambo's Moves Learnt table hack? + // need to check this before using moveset pointers + int movesetsTable; + if (readLong(0x3EB20) == 0x47084918) { + // Hack applied, adjust accordingly + int firstRoutinePtr = readPointer(0x3EB84); + movesetsTable = readPointer(firstRoutinePtr + 75); + jamboMovesetHack = true; + } else { + movesetsTable = readPointer(0x3EA7C); + jamboMovesetHack = false; + } + + // secondary check: moveset pointers + // if a slot has an invalid moveset pointer, it's not a real slot + // Before that, grab the moveset table from a known pointer to it. + romEntry.entries.put("PokemonMovesets", movesetsTable); + while (iPokemonCount >= 0) { + int movesetPtr = readPointer(movesetsTable + iPokemonCount * 4); + if (movesetPtr < 0 || movesetPtr >= rom.length) { + iPokemonCount--; + } else { + break; + } + } + + // sanity check: pokedex order + // pokedex entries have to be within 0-1023 + // even after extending the dex + // (at least with conventional methods) + // so if we run into an invalid one + // then we can cut off the count + int pdOffset = romEntry.getValue("PokedexOrder"); + for (int i = 1; i <= iPokemonCount; i++) { + int pdEntry = readWord(pdOffset + (i - 1) * 2); + if (pdEntry > 1023) { + iPokemonCount = i - 1; + break; + } + } + + // write new pokemon count + romEntry.entries.put("PokemonCount", iPokemonCount); + + // update some key offsets from known pointers + romEntry.entries.put("PokemonTMHMCompat", readPointer(0x43C68)); + romEntry.entries.put("PokemonEvolutions", readPointer(0x42F6C)); + romEntry.entries.put("MoveTutorCompatibility", readPointer(0x120C30)); + int descsTable = readPointer(0xE5440); + romEntry.entries.put("MoveDescriptions", descsTable); + int trainersTable = readPointer(0xFC00); + romEntry.entries.put("TrainerData", trainersTable); + + // try to detect number of moves using the descriptions + int moveCount = 0; + while (true) { + int descPointer = readPointer(descsTable + (moveCount) * 4); + if (descPointer >= 0 && descPointer < rom.length) { + int descStrLen = lengthOfStringAt(descPointer); + if (descStrLen > 0 && descStrLen < 100) { + // okay, this does seem fine + moveCount++; + continue; + } + } + break; + } + romEntry.entries.put("MoveCount", moveCount); + + // attempt to detect number of trainers using various tells + int trainerCount = 1; + int tEntryLen = romEntry.getValue("TrainerEntrySize"); + int tNameLen = romEntry.getValue("TrainerNameLength"); + while (true) { + int trOffset = trainersTable + tEntryLen * trainerCount; + int pokeDataType = rom[trOffset] & 0xFF; + if (pokeDataType >= 4) { + // only allowed 0-3 + break; + } + int numPokes = rom[trOffset + (tEntryLen - 8)] & 0xFF; + if (numPokes == 0 || numPokes > 6) { + break; + } + int pointerToPokes = readPointer(trOffset + (tEntryLen - 4)); + if (pointerToPokes < 0 || pointerToPokes >= rom.length) { + break; + } + int nameLength = lengthOfStringAt(trOffset + 4); + if (nameLength >= tNameLen) { + break; + } + // found a valid trainer entry, recognize it + trainerCount++; + } + romEntry.entries.put("TrainerCount", trainerCount); + + // disable static pokemon & move tutor/tm text + romEntry.entries.put("StaticPokemonSupport", 0); + romEntry.staticPokemon.clear(); + romEntry.tmmtTexts.clear(); + + } + + } + + private boolean basicBPRE10HackDetection() { + if (rom.length != Gen3Constants.size16M) { + return true; + } + CRC32 checksum = new CRC32(); + checksum.update(rom); + long csum = checksum.getValue(); + // TODO: there might be more valid BPRE 1.0 checksums? + return csum != 3716707868L; + } + + @Override + public void savingRom() { + savePokemonStats(); + saveMoves(); + } + + private void loadPokedex() { + int pdOffset = romEntry.getValue("PokedexOrder"); + int numInternalPokes = romEntry.getValue("PokemonCount"); + int maxPokedex = 0; + internalToPokedex = new int[numInternalPokes + 1]; + pokedexToInternal = new int[numInternalPokes + 1]; + for (int i = 1; i <= numInternalPokes; i++) { + int dexEntry = readWord(rom, pdOffset + (i - 1) * 2); + if (dexEntry != 0) { + internalToPokedex[i] = dexEntry; + // take the first pokemon only for each dex entry + if (pokedexToInternal[dexEntry] == 0) { + pokedexToInternal[dexEntry] = i; + } + maxPokedex = Math.max(maxPokedex, dexEntry); + } + } + if (maxPokedex == Gen3Constants.unhackedMaxPokedex) { + // see if the slots between johto and hoenn are in use + // old rom hacks use them instead of expanding pokes + int offs = romEntry.getValue("PokemonStats"); + int usedSlots = 0; + for (int i = 0; i < Gen3Constants.unhackedMaxPokedex - Gen3Constants.unhackedRealPokedex; i++) { + int pokeSlot = Gen3Constants.hoennPokesStart + i; + int pokeOffs = offs + pokeSlot * Gen3Constants.baseStatsEntrySize; + String lowerName = pokeNames[pokeSlot].toLowerCase(); + if (!this.matches(rom, pokeOffs, Gen3Constants.emptyPokemonSig) && !lowerName.contains("unused") + && !lowerName.equals("?") && !lowerName.equals("-")) { + usedSlots++; + pokedexToInternal[Gen3Constants.unhackedRealPokedex + usedSlots] = pokeSlot; + internalToPokedex[pokeSlot] = Gen3Constants.unhackedRealPokedex + usedSlots; + } else { + internalToPokedex[pokeSlot] = 0; + } + } + // remove the fake extra slots + for (int i = usedSlots + 1; i <= Gen3Constants.unhackedMaxPokedex - Gen3Constants.unhackedRealPokedex; i++) { + pokedexToInternal[Gen3Constants.unhackedRealPokedex + i] = 0; + } + // if any slots were used at all, this is a rom hack + if (usedSlots > 0) { + this.isRomHack = true; + } + this.pokedexCount = Gen3Constants.unhackedRealPokedex + usedSlots; + } else { + this.isRomHack = true; + this.pokedexCount = maxPokedex; + } + + } + + private void constructPokemonList() { + if (!this.isRomHack) { + // simple behavior: all pokes in the dex are valid + pokemonList = Arrays.asList(pokes); + } else { + // only include "valid" pokes + pokemonList = new ArrayList<Pokemon>(); + pokemonList.add(null); + for (int i = 1; i < pokes.length; i++) { + Pokemon pk = pokes[i]; + if (pk != null) { + String lowerName = pk.name.toLowerCase(); + if (!lowerName.contains("unused") && !lowerName.equals("?")) { + pokemonList.add(pk); + } + } + } + } + numRealPokemon = pokemonList.size() - 1; + + } + + private void loadPokemonStats() { + pokes = new Pokemon[this.pokedexCount + 1]; + int numInternalPokes = romEntry.getValue("PokemonCount"); + pokesInternal = new Pokemon[numInternalPokes + 1]; + int offs = romEntry.getValue("PokemonStats"); + for (int i = 1; i <= numInternalPokes; i++) { + Pokemon pk = new Pokemon(); + pk.name = pokeNames[i]; + pk.number = internalToPokedex[i]; + if (pk.number != 0) { + pokes[pk.number] = pk; + } + pokesInternal[i] = pk; + int pkoffs = offs + i * Gen3Constants.baseStatsEntrySize; + loadBasicPokeStats(pk, pkoffs); + } + populateEvolutions(); + } + + private void savePokemonStats() { + // Write pokemon names & stats + int offs = romEntry.getValue("PokemonNames"); + int nameLen = romEntry.getValue("PokemonNameLength"); + int offs2 = romEntry.getValue("PokemonStats"); + int numInternalPokes = romEntry.getValue("PokemonCount"); + for (int i = 1; i <= numInternalPokes; i++) { + Pokemon pk = pokesInternal[i]; + int stringOffset = offs + i * nameLen; + writeFixedLengthString(pk.name, stringOffset, nameLen); + saveBasicPokeStats(pk, offs2 + i * Gen3Constants.baseStatsEntrySize); + } + writeEvolutions(); + } + + private void loadMoves() { + int moveCount = romEntry.getValue("MoveCount"); + moves = new Move[moveCount + 1]; + int offs = romEntry.getValue("MoveData"); + int nameoffs = romEntry.getValue("MoveNames"); + int namelen = romEntry.getValue("MoveNameLength"); + for (int i = 1; i <= moveCount; i++) { + moves[i] = new Move(); + moves[i].name = readFixedLengthString(nameoffs + i * namelen, namelen); + moves[i].number = i; + moves[i].internalId = i; + moves[i].effectIndex = rom[offs + i * 0xC] & 0xFF; + moves[i].hitratio = ((rom[offs + i * 0xC + 3] & 0xFF) + 0); + moves[i].power = rom[offs + i * 0xC + 1] & 0xFF; + moves[i].pp = rom[offs + i * 0xC + 4] & 0xFF; + moves[i].type = Gen3Constants.typeTable[rom[offs + i * 0xC + 2]]; + } + + } + + private void saveMoves() { + int moveCount = romEntry.getValue("MoveCount"); + int offs = romEntry.getValue("MoveData"); + for (int i = 1; i <= moveCount; i++) { + rom[offs + i * 0xC] = (byte) moves[i].effectIndex; + rom[offs + i * 0xC + 1] = (byte) moves[i].power; + rom[offs + i * 0xC + 2] = Gen3Constants.typeToByte(moves[i].type); + int hitratio = (int) Math.round(moves[i].hitratio); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 100) { + hitratio = 100; + } + rom[offs + i * 0xC + 3] = (byte) hitratio; + rom[offs + i * 0xC + 4] = (byte) moves[i].pp; + } + } + + public List<Move> getMoves() { + return Arrays.asList(moves); + } + + private void loadBasicPokeStats(Pokemon pkmn, int offset) { + pkmn.hp = rom[offset + Gen3Constants.bsHPOffset] & 0xFF; + pkmn.attack = rom[offset + Gen3Constants.bsAttackOffset] & 0xFF; + pkmn.defense = rom[offset + Gen3Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = rom[offset + Gen3Constants.bsSpeedOffset] & 0xFF; + pkmn.spatk = rom[offset + Gen3Constants.bsSpAtkOffset] & 0xFF; + pkmn.spdef = rom[offset + Gen3Constants.bsSpDefOffset] & 0xFF; + // Type + pkmn.primaryType = Gen3Constants.typeTable[rom[offset + Gen3Constants.bsPrimaryTypeOffset] & 0xFF]; + pkmn.secondaryType = Gen3Constants.typeTable[rom[offset + Gen3Constants.bsSecondaryTypeOffset] & 0xFF]; + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + pkmn.catchRate = rom[offset + Gen3Constants.bsCatchRateOffset] & 0xFF; + pkmn.growthCurve = ExpCurve.fromByte(rom[offset + Gen3Constants.bsGrowthCurveOffset]); + // Abilities + pkmn.ability1 = rom[offset + Gen3Constants.bsAbility1Offset] & 0xFF; + pkmn.ability2 = rom[offset + Gen3Constants.bsAbility2Offset] & 0xFF; + + // Held Items? + int item1 = readWord(offset + Gen3Constants.bsCommonHeldItemOffset); + int item2 = readWord(offset + Gen3Constants.bsRareHeldItemOffset); + + if (item1 == item2) { + // guaranteed + pkmn.guaranteedHeldItem = item1; + pkmn.commonHeldItem = 0; + pkmn.rareHeldItem = 0; + } else { + pkmn.guaranteedHeldItem = 0; + pkmn.commonHeldItem = item1; + pkmn.rareHeldItem = item2; + } + pkmn.darkGrassHeldItem = -1; + + pkmn.genderRatio = rom[offset + Gen3Constants.bsGenderRatioOffset] & 0xFF; + } + + private void saveBasicPokeStats(Pokemon pkmn, int offset) { + rom[offset + Gen3Constants.bsHPOffset] = (byte) pkmn.hp; + rom[offset + Gen3Constants.bsAttackOffset] = (byte) pkmn.attack; + rom[offset + Gen3Constants.bsDefenseOffset] = (byte) pkmn.defense; + rom[offset + Gen3Constants.bsSpeedOffset] = (byte) pkmn.speed; + rom[offset + Gen3Constants.bsSpAtkOffset] = (byte) pkmn.spatk; + rom[offset + Gen3Constants.bsSpDefOffset] = (byte) pkmn.spdef; + rom[offset + Gen3Constants.bsPrimaryTypeOffset] = Gen3Constants.typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + rom[offset + Gen3Constants.bsSecondaryTypeOffset] = rom[offset + Gen3Constants.bsPrimaryTypeOffset]; + } else { + rom[offset + Gen3Constants.bsSecondaryTypeOffset] = Gen3Constants.typeToByte(pkmn.secondaryType); + } + rom[offset + Gen3Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + rom[offset + Gen3Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + + rom[offset + Gen3Constants.bsAbility1Offset] = (byte) pkmn.ability1; + if (pkmn.ability2 == 0) { + // required to not break evos with random ability + rom[offset + Gen3Constants.bsAbility2Offset] = (byte) pkmn.ability1; + } else { + rom[offset + Gen3Constants.bsAbility2Offset] = (byte) pkmn.ability2; + } + + // Held items + if (pkmn.guaranteedHeldItem > 0) { + writeWord(offset + Gen3Constants.bsCommonHeldItemOffset, pkmn.guaranteedHeldItem); + writeWord(offset + Gen3Constants.bsRareHeldItemOffset, pkmn.guaranteedHeldItem); + } else { + writeWord(offset + Gen3Constants.bsCommonHeldItemOffset, pkmn.commonHeldItem); + writeWord(offset + Gen3Constants.bsRareHeldItemOffset, pkmn.rareHeldItem); + } + + rom[offset + Gen3Constants.bsGenderRatioOffset] = (byte) pkmn.genderRatio; + } + + private void loadPokemonNames() { + int offs = romEntry.getValue("PokemonNames"); + int nameLen = romEntry.getValue("PokemonNameLength"); + int numInternalPokes = romEntry.getValue("PokemonCount"); + pokeNames = new String[numInternalPokes + 1]; + for (int i = 1; i <= numInternalPokes; i++) { + pokeNames[i] = readFixedLengthString(offs + i * nameLen, nameLen); + } + } + + private String readString(int offset, int maxLength) { + StringBuilder string = new StringBuilder(); + for (int c = 0; c < maxLength; c++) { + int currChar = rom[offset + c] & 0xFF; + if (tb[currChar] != null) { + string.append(tb[currChar]); + } else { + if (currChar == Gen3Constants.textTerminator) { + break; + } else if (currChar == Gen3Constants.textVariable) { + int nextChar = rom[offset + c + 1] & 0xFF; + string.append("\\v" + String.format("%02X", nextChar)); + c++; + } else { + string.append("\\x" + String.format("%02X", currChar)); + } + } + } + return string.toString(); + } + + private byte[] translateString(String text) { + List<Byte> data = new ArrayList<Byte>(); + while (text.length() != 0) { + int i = Math.max(0, 4 - text.length()); + if (text.charAt(0) == '\\' && text.charAt(1) == 'x') { + data.add((byte) Integer.parseInt(text.substring(2, 4), 16)); + text = text.substring(4); + } else if (text.charAt(0) == '\\' && text.charAt(1) == 'v') { + data.add((byte) Gen3Constants.textVariable); + data.add((byte) Integer.parseInt(text.substring(2, 4), 16)); + text = text.substring(4); + } else { + while (!(d.containsKey(text.substring(0, 4 - i)) || (i == 4))) { + i++; + } + if (i == 4) { + text = text.substring(1); + } else { + data.add(d.get(text.substring(0, 4 - i))); + text = text.substring(4 - i); + } + } + } + byte[] ret = new byte[data.size()]; + for (int i = 0; i < ret.length; i++) { + ret[i] = data.get(i); + } + return ret; + } + + private String readFixedLengthString(int offset, int length) { + return readString(offset, length); + } + + public String readVariableLengthString(int offset) { + return readString(offset, Integer.MAX_VALUE); + } + + private void writeFixedLengthString(String str, int offset, int length) { + byte[] translated = translateString(str); + int len = Math.min(translated.length, length); + System.arraycopy(translated, 0, rom, offset, len); + if (len < length) { + rom[offset + len] = (byte) Gen3Constants.textTerminator; + len++; + } + while (len < length) { + rom[offset + len] = 0; + len++; + } + } + + private void writeVariableLengthString(String str, int offset) { + byte[] translated = translateString(str); + System.arraycopy(translated, 0, rom, offset, translated.length); + rom[offset + translated.length] = (byte) 0xFF; + } + + private int lengthOfStringAt(int offset) { + int len = 0; + while ((rom[offset + (len++)] & 0xFF) != 0xFF) { + } + return len - 1; + } + + public byte[] traduire(String str) { + return translateString(str); + } + + private static boolean romName(byte[] rom, String name) { + try { + int sigOffset = Gen3Constants.romNameOffset; + byte[] sigBytes = name.getBytes("US-ASCII"); + for (int i = 0; i < sigBytes.length; i++) { + if (rom[sigOffset + i] != sigBytes[i]) { + return false; + } + } + return true; + } catch (UnsupportedEncodingException ex) { + return false; + } + + } + + private static boolean romCode(byte[] rom, String codeToCheck) { + try { + int sigOffset = Gen3Constants.romCodeOffset; + byte[] sigBytes = codeToCheck.getBytes("US-ASCII"); + for (int i = 0; i < sigBytes.length; i++) { + if (rom[sigOffset + i] != sigBytes[i]) { + return false; + } + } + return true; + } catch (UnsupportedEncodingException ex) { + return false; + } + + } + + private int readPointer(int offset) { + return readLong(offset) - 0x8000000; + } + + private int readLong(int offset) { + return (rom[offset] & 0xFF) + ((rom[offset + 1] & 0xFF) << 8) + ((rom[offset + 2] & 0xFF) << 16) + + (((rom[offset + 3] & 0xFF)) << 24); + } + + private void writePointer(int offset, int pointer) { + writeLong(offset, pointer + 0x8000000); + } + + private void writeLong(int offset, int value) { + rom[offset] = (byte) (value & 0xFF); + rom[offset + 1] = (byte) ((value >> 8) & 0xFF); + rom[offset + 2] = (byte) ((value >> 16) & 0xFF); + rom[offset + 3] = (byte) (((value >> 24) & 0xFF)); + } + + @Override + public List<Pokemon> getStarters() { + List<Pokemon> starters = new ArrayList<Pokemon>(); + int baseOffset = romEntry.getValue("StarterPokemon"); + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp + || romEntry.romType == Gen3Constants.RomType_Em) { + // do something + Pokemon starter1 = pokesInternal[readWord(baseOffset)]; + Pokemon starter2 = pokesInternal[readWord(baseOffset + Gen3Constants.rseStarter2Offset)]; + Pokemon starter3 = pokesInternal[readWord(baseOffset + Gen3Constants.rseStarter3Offset)]; + starters.add(starter1); + starters.add(starter2); + starters.add(starter3); + } else { + // do something else + Pokemon starter1 = pokesInternal[readWord(baseOffset)]; + Pokemon starter2 = pokesInternal[readWord(baseOffset + Gen3Constants.frlgStarter2Offset)]; + Pokemon starter3 = pokesInternal[readWord(baseOffset + Gen3Constants.frlgStarter3Offset)]; + starters.add(starter1); + starters.add(starter2); + starters.add(starter3); + } + return starters; + } + + @Override + public boolean setStarters(List<Pokemon> newStarters) { + if (newStarters.size() != 3) { + return false; + } + + // Support Deoxys/Mew starters in E/FR/LG + if (!havePatchedObedience) { + attemptObedienceEvolutionPatches(); + } + int baseOffset = romEntry.getValue("StarterPokemon"); + + int starter0 = pokedexToInternal[newStarters.get(0).number]; + int starter1 = pokedexToInternal[newStarters.get(1).number]; + int starter2 = pokedexToInternal[newStarters.get(2).number]; + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp + || romEntry.romType == Gen3Constants.RomType_Em) { + + // US + // order: 0, 1, 2 + writeWord(baseOffset, starter0); + writeWord(baseOffset + Gen3Constants.rseStarter2Offset, starter1); + writeWord(baseOffset + Gen3Constants.rseStarter3Offset, starter2); + + } else { + + // frlg: + + // US + // order: 0, 1, 2 + writeWord(baseOffset, starter0); + writeWord(baseOffset + Gen3Constants.frlgStarterRepeatOffset, starter1); + + writeWord(baseOffset + Gen3Constants.frlgStarter2Offset, starter1); + writeWord(baseOffset + Gen3Constants.frlgStarter2Offset + Gen3Constants.frlgStarterRepeatOffset, starter2); + + writeWord(baseOffset + Gen3Constants.frlgStarter3Offset, starter2); + writeWord(baseOffset + Gen3Constants.frlgStarter3Offset + Gen3Constants.frlgStarterRepeatOffset, starter0); + + if (romEntry.romCode.charAt(3) != 'J' && romEntry.romCode.charAt(3) != 'B') { + // Update PROF. Oak's descriptions for each starter + // First result for each STARTERNAME is the text we need + writeFRLGStarterText(pokes[Gen3Constants.frlgBaseStarter1].name, newStarters.get(0), + "you want to go with\\nthe "); + writeFRLGStarterText(pokes[Gen3Constants.frlgBaseStarter2].name, newStarters.get(1), + "you’re claiming the\\n"); + writeFRLGStarterText(pokes[Gen3Constants.frlgBaseStarter3].name, newStarters.get(2), + "you’ve decided on the\\n"); + } + } + return true; + + } + + @Override + public List<Integer> getStarterHeldItems() { + List<Integer> sHeldItems = new ArrayList<Integer>(); + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + // offset from normal starter offset as a word + int baseOffset = romEntry.getValue("StarterPokemon"); + sHeldItems.add(readWord(baseOffset + Gen3Constants.frlgStarterItemsOffset)); + } else { + int baseOffset = romEntry.getValue("StarterItems"); + int i1 = rom[baseOffset] & 0xFF; + int i2 = rom[baseOffset + 2] & 0xFF; + if (i2 == 0) { + sHeldItems.add(i1); + } else { + sHeldItems.add(i2 + 0xFF); + } + } + return sHeldItems; + } + + @Override + public void setStarterHeldItems(List<Integer> items) { + if (items.size() != 1) { + return; + } + int item = items.get(0); + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + // offset from normal starter offset as a word + int baseOffset = romEntry.getValue("StarterPokemon"); + writeWord(baseOffset + Gen3Constants.frlgStarterItemsOffset, item); + } else { + int baseOffset = romEntry.getValue("StarterItems"); + if (item <= 0xFF) { + rom[baseOffset] = (byte) item; + rom[baseOffset + 2] = 0; + rom[baseOffset + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR2; + } else { + rom[baseOffset] = (byte) 0xFF; + rom[baseOffset + 2] = (byte) (item - 0xFF); + rom[baseOffset + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR2; + } + } + } + + private void writeFRLGStarterText(String findName, Pokemon pkmn, String oakText) { + List<Integer> foundTexts = RomFunctions.search(rom, traduire(findName)); + if (foundTexts.size() > 0) { + int offset = foundTexts.get(0); + String pokeName = pkmn.name; + String pokeType = pkmn.primaryType == null ? "???" : pkmn.primaryType.toString(); + if (pokeType.equals("NORMAL") && pkmn.secondaryType != null) { + pokeType = pkmn.secondaryType.toString(); + } + String speech = pokeName + " is your choice.\\pSo, \\v01, " + oakText + pokeType + " POKéMON " + pokeName + + "?"; + writeFixedLengthString(speech, offset, lengthOfStringAt(offset) + 1); + } + } + + @Override + public void shufflePokemonStats() { + for (int i = 1; i < numRealPokemon; i++) { + pokemonList.get(i).shuffleStats(this.random); + } + } + + @Override + public List<EncounterSet> getEncounters(boolean useTimeOfDay) { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + + int startOffs = romEntry.getValue("WildPokemon"); + List<EncounterSet> encounterAreas = new ArrayList<EncounterSet>(); + Set<Integer> seenOffsets = new TreeSet<Integer>(); + int offs = startOffs; + while (true) { + // Read pointers + int bank = rom[offs] & 0xFF; + int map = rom[offs + 1] & 0xFF; + if (bank == 0xFF && map == 0xFF) { + break; + } + + String mapName = mapNames[bank][map]; + + int grassPokes = readPointer(offs + 4); + int waterPokes = readPointer(offs + 8); + int treePokes = readPointer(offs + 12); + int fishPokes = readPointer(offs + 16); + + // Add pokemanz + if (grassPokes >= 0 && grassPokes < rom.length && rom[grassPokes] != 0 + && !seenOffsets.contains(readPointer(grassPokes + 4))) { + encounterAreas.add(readWildArea(grassPokes, Gen3Constants.grassSlots, mapName + " Grass/Cave")); + seenOffsets.add(readPointer(grassPokes + 4)); + } + if (waterPokes >= 0 && waterPokes < rom.length && rom[waterPokes] != 0 + && !seenOffsets.contains(readPointer(waterPokes + 4))) { + encounterAreas.add(readWildArea(waterPokes, Gen3Constants.surfingSlots, mapName + " Surfing")); + seenOffsets.add(readPointer(waterPokes + 4)); + } + if (treePokes >= 0 && treePokes < rom.length && rom[treePokes] != 0 + && !seenOffsets.contains(readPointer(treePokes + 4))) { + encounterAreas.add(readWildArea(treePokes, Gen3Constants.rockSmashSlots, mapName + " Rock Smash")); + seenOffsets.add(readPointer(treePokes + 4)); + } + if (fishPokes >= 0 && fishPokes < rom.length && rom[fishPokes] != 0 + && !seenOffsets.contains(readPointer(fishPokes + 4))) { + encounterAreas.add(readWildArea(fishPokes, Gen3Constants.fishingSlots, mapName + " Fishing")); + seenOffsets.add(readPointer(fishPokes + 4)); + } + + offs += 20; + } + if (romEntry.arrayEntries.containsKey("BattleTrappersBanned")) { + // Some encounter sets aren't allowed to have Pokemon + // with Arena Trap, Shadow Tag etc. + int[] bannedAreas = romEntry.arrayEntries.get("BattleTrappersBanned"); + Set<Pokemon> battleTrappers = new HashSet<Pokemon>(); + for (Pokemon pk : getPokemon()) { + if (hasBattleTrappingAbility(pk)) { + battleTrappers.add(pk); + } + } + for (int areaIdx : bannedAreas) { + encounterAreas.get(areaIdx).bannedPokemon.addAll(battleTrappers); + } + } + return encounterAreas; + } + + private boolean hasBattleTrappingAbility(Pokemon pokemon) { + return pokemon != null + && (Gen3Constants.battleTrappingAbilities.contains(pokemon.ability1) || Gen3Constants.battleTrappingAbilities + .contains(pokemon.ability2)); + } + + private EncounterSet readWildArea(int offset, int numOfEntries, String setName) { + EncounterSet thisSet = new EncounterSet(); + thisSet.rate = rom[offset]; + thisSet.displayName = setName; + // Grab the *real* pointer to data + int dataOffset = readPointer(offset + 4); + // Read the entries + for (int i = 0; i < numOfEntries; i++) { + // min, max, species, species + Encounter enc = new Encounter(); + enc.level = rom[dataOffset + i * 4]; + enc.maxLevel = rom[dataOffset + i * 4 + 1]; + try { + enc.pokemon = pokesInternal[readWord(dataOffset + i * 4 + 2)]; + } catch (ArrayIndexOutOfBoundsException ex) { + throw ex; + } + thisSet.encounters.add(enc); + } + return thisSet; + } + + @Override + public void setEncounters(boolean useTimeOfDay, List<EncounterSet> encounters) { + // Support Deoxys/Mew catches in E/FR/LG + if (!havePatchedObedience) { + attemptObedienceEvolutionPatches(); + } + + int startOffs = romEntry.getValue("WildPokemon"); + Iterator<EncounterSet> encounterAreas = encounters.iterator(); + Set<Integer> seenOffsets = new TreeSet<Integer>(); + int offs = startOffs; + while (true) { + // Read pointers + int bank = rom[offs] & 0xFF; + int map = rom[offs + 1] & 0xFF; + if (bank == 0xFF && map == 0xFF) { + break; + } + + int grassPokes = readPointer(offs + 4); + int waterPokes = readPointer(offs + 8); + int treePokes = readPointer(offs + 12); + int fishPokes = readPointer(offs + 16); + + // Add pokemanz + if (grassPokes >= 0 && grassPokes < rom.length && rom[grassPokes] != 0 + && !seenOffsets.contains(readPointer(grassPokes + 4))) { + writeWildArea(grassPokes, Gen3Constants.grassSlots, encounterAreas.next()); + seenOffsets.add(readPointer(grassPokes + 4)); + } + if (waterPokes >= 0 && waterPokes < rom.length && rom[waterPokes] != 0 + && !seenOffsets.contains(readPointer(waterPokes + 4))) { + writeWildArea(waterPokes, Gen3Constants.surfingSlots, encounterAreas.next()); + seenOffsets.add(readPointer(waterPokes + 4)); + } + if (treePokes >= 0 && treePokes < rom.length && rom[treePokes] != 0 + && !seenOffsets.contains(readPointer(treePokes + 4))) { + writeWildArea(treePokes, Gen3Constants.rockSmashSlots, encounterAreas.next()); + seenOffsets.add(readPointer(treePokes + 4)); + } + if (fishPokes >= 0 && fishPokes < rom.length && rom[fishPokes] != 0 + && !seenOffsets.contains(readPointer(fishPokes + 4))) { + writeWildArea(fishPokes, Gen3Constants.fishingSlots, encounterAreas.next()); + seenOffsets.add(readPointer(fishPokes + 4)); + } + + offs += 20; + } + } + + @Override + public List<Pokemon> bannedForWildEncounters() { + return Arrays.asList(pokes[Gen3Constants.unownIndex]); // Unown banned + } + + @Override + public List<Trainer> getTrainers() { + int baseOffset = romEntry.getValue("TrainerData"); + int amount = romEntry.getValue("TrainerCount"); + int entryLen = romEntry.getValue("TrainerEntrySize"); + List<Trainer> theTrainers = new ArrayList<Trainer>(); + List<String> tcnames = this.getTrainerClassNames(); + for (int i = 1; i < amount; i++) { + int trOffset = baseOffset + i * entryLen; + Trainer tr = new Trainer(); + tr.offset = trOffset; + int trainerclass = rom[trOffset + 1] & 0xFF; + tr.trainerclass = (rom[trOffset + 2] & 0x80) > 0 ? 1 : 0; + + int pokeDataType = rom[trOffset] & 0xFF; + int numPokes = rom[trOffset + (entryLen - 8)] & 0xFF; + int pointerToPokes = readPointer(trOffset + (entryLen - 4)); + tr.poketype = pokeDataType; + tr.name = this.readVariableLengthString(trOffset + 4); + tr.fullDisplayName = tcnames.get(trainerclass) + " " + tr.name; + // Pokemon data! + if (pokeDataType == 0) { + // blocks of 8 bytes + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon thisPoke = new TrainerPokemon(); + thisPoke.AILevel = readWord(pointerToPokes + poke * 8); + thisPoke.level = readWord(pointerToPokes + poke * 8 + 2); + thisPoke.pokemon = pokesInternal[readWord(pointerToPokes + poke * 8 + 4)]; + tr.pokemon.add(thisPoke); + } + } else if (pokeDataType == 2) { + // blocks of 8 bytes + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon thisPoke = new TrainerPokemon(); + thisPoke.AILevel = readWord(pointerToPokes + poke * 8); + thisPoke.level = readWord(pointerToPokes + poke * 8 + 2); + thisPoke.pokemon = pokesInternal[readWord(pointerToPokes + poke * 8 + 4)]; + thisPoke.heldItem = readWord(pointerToPokes + poke * 8 + 6); + tr.pokemon.add(thisPoke); + } + } else if (pokeDataType == 1) { + // blocks of 16 bytes + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon thisPoke = new TrainerPokemon(); + thisPoke.AILevel = readWord(pointerToPokes + poke * 16); + thisPoke.level = readWord(pointerToPokes + poke * 16 + 2); + thisPoke.pokemon = pokesInternal[readWord(pointerToPokes + poke * 16 + 4)]; + thisPoke.move1 = readWord(pointerToPokes + poke * 16 + 6); + thisPoke.move2 = readWord(pointerToPokes + poke * 16 + 8); + thisPoke.move3 = readWord(pointerToPokes + poke * 16 + 10); + thisPoke.move4 = readWord(pointerToPokes + poke * 16 + 12); + tr.pokemon.add(thisPoke); + } + } else if (pokeDataType == 3) { + // blocks of 16 bytes + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon thisPoke = new TrainerPokemon(); + thisPoke.AILevel = readWord(pointerToPokes + poke * 16); + thisPoke.level = readWord(pointerToPokes + poke * 16 + 2); + thisPoke.pokemon = pokesInternal[readWord(pointerToPokes + poke * 16 + 4)]; + thisPoke.heldItem = readWord(pointerToPokes + poke * 16 + 6); + thisPoke.move1 = readWord(pointerToPokes + poke * 16 + 8); + thisPoke.move2 = readWord(pointerToPokes + poke * 16 + 10); + thisPoke.move3 = readWord(pointerToPokes + poke * 16 + 12); + thisPoke.move4 = readWord(pointerToPokes + poke * 16 + 14); + tr.pokemon.add(thisPoke); + } + } + theTrainers.add(tr); + } + + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + Gen3Constants.trainerTagsRS(theTrainers, romEntry.romType); + } else if (romEntry.romType == Gen3Constants.RomType_Em) { + Gen3Constants.trainerTagsE(theTrainers); + } else { + Gen3Constants.trainerTagsFRLG(theTrainers); + } + return theTrainers; + } + + @Override + public void setTrainers(List<Trainer> trainerData) { + int baseOffset = romEntry.getValue("TrainerData"); + int amount = romEntry.getValue("TrainerCount"); + int entryLen = romEntry.getValue("TrainerEntrySize"); + Iterator<Trainer> theTrainers = trainerData.iterator(); + for (int i = 1; i < amount; i++) { + int trOffset = baseOffset + i * entryLen; + Trainer tr = theTrainers.next(); + // Write out the data as type 0 to avoid moves & hold items carrying + // over + rom[trOffset] = 0; + rom[trOffset + (entryLen - 8)] = (byte) tr.pokemon.size(); + int pointerToPokes = readPointer(trOffset + (entryLen - 4)); + Iterator<TrainerPokemon> pokes = tr.pokemon.iterator(); + // Pokemon data! + // if (pokeDataType == 0) { + // blocks of 8 bytes + for (int poke = 0; poke < tr.pokemon.size(); poke++) { + TrainerPokemon thisPoke = pokes.next(); + writeWord(pointerToPokes + poke * 8, thisPoke.AILevel); + writeWord(pointerToPokes + poke * 8 + 2, thisPoke.level); + writeWord(pointerToPokes + poke * 8 + 4, pokedexToInternal[thisPoke.pokemon.number]); + writeWord(pointerToPokes + poke * 8 + 6, 0); + } + } + + } + + private void writeWildArea(int offset, int numOfEntries, EncounterSet encounters) { + // Grab the *real* pointer to data + int dataOffset = readPointer(offset + 4); + // Write the entries + for (int i = 0; i < numOfEntries; i++) { + Encounter enc = encounters.encounters.get(i); + // min, max, species, species + writeWord(dataOffset + i * 4 + 2, pokedexToInternal[enc.pokemon.number]); + } + } + + @Override + public List<Pokemon> getPokemon() { + return pokemonList; + } + + @Override + public Map<Pokemon, List<MoveLearnt>> getMovesLearnt() { + Map<Pokemon, List<MoveLearnt>> movesets = new TreeMap<Pokemon, List<MoveLearnt>>(); + int baseOffset = romEntry.getValue("PokemonMovesets"); + for (int i = 1; i < numRealPokemon; i++) { + Pokemon pkmn = pokemonList.get(i); + int offsToPtr = baseOffset + (pokedexToInternal[pkmn.number]) * 4; + int moveDataLoc = readPointer(offsToPtr); + List<MoveLearnt> moves = new ArrayList<MoveLearnt>(); + if (jamboMovesetHack) { + while ((rom[moveDataLoc] & 0xFF) != 0x00 || (rom[moveDataLoc + 1] & 0xFF) != 0x00 + || (rom[moveDataLoc + 2] & 0xFF) != 0xFF) { + MoveLearnt ml = new MoveLearnt(); + ml.level = rom[moveDataLoc + 2] & 0xFF; + ml.move = readWord(moveDataLoc); + moves.add(ml); + moveDataLoc += 3; + } + } else { + while ((rom[moveDataLoc] & 0xFF) != 0xFF || (rom[moveDataLoc + 1] & 0xFF) != 0xFF) { + int move = (rom[moveDataLoc] & 0xFF); + int level = (rom[moveDataLoc + 1] & 0xFE) >> 1; + if ((rom[moveDataLoc + 1] & 0x01) == 0x01) { + move += 0x100; + } + MoveLearnt ml = new MoveLearnt(); + ml.level = level; + ml.move = move; + moves.add(ml); + moveDataLoc += 2; + } + } + movesets.put(pkmn, moves); + } + return movesets; + } + + @Override + public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets) { + int baseOffset = romEntry.getValue("PokemonMovesets"); + int fso = romEntry.getValue("FreeSpace"); + for (int i = 1; i < numRealPokemon; i++) { + Pokemon pkmn = pokemonList.get(i); + int offsToPtr = baseOffset + (pokedexToInternal[pkmn.number]) * 4; + int moveDataLoc = readPointer(offsToPtr); + List<MoveLearnt> moves = movesets.get(pkmn); + int newMoveCount = moves.size(); + int mloc = moveDataLoc; + int entrySize; + if (jamboMovesetHack) { + while ((rom[mloc] & 0xFF) != 0x00 || (rom[mloc + 1] & 0xFF) != 0x00 || (rom[mloc + 2] & 0xFF) != 0xFF) { + mloc += 3; + } + entrySize = 3; + } else { + while ((rom[mloc] & 0xFF) != 0xFF || (rom[mloc + 1] & 0xFF) != 0xFF) { + mloc += 2; + } + entrySize = 2; + } + int currentMoveCount = (mloc - moveDataLoc) / entrySize; + + if (newMoveCount > currentMoveCount) { + // Repoint for more space + int newBytesNeeded = newMoveCount * entrySize + entrySize * 2; + int writeSpace = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, newBytesNeeded, fso); + if (writeSpace < fso) { + throw new RuntimeException("ROM is full"); + } + writePointer(offsToPtr, writeSpace); + moveDataLoc = writeSpace; + } + + // Write new moveset now that space is ensured. + for (int mv = 0; mv < newMoveCount; mv++) { + MoveLearnt ml = moves.get(mv); + moveDataLoc += writeMLToOffset(moveDataLoc, ml); + } + + // If move count changed, new terminator is required + // In the repoint enough space was reserved to add some padding to + // make sure the terminator isn't detected as free space. + // If no repoint, the padding goes over the old moves/terminator. + if (newMoveCount != currentMoveCount) { + if (jamboMovesetHack) { + rom[moveDataLoc] = 0x00; + rom[moveDataLoc + 1] = 0x00; + rom[moveDataLoc + 2] = (byte) 0xFF; + rom[moveDataLoc + 3] = 0x00; + rom[moveDataLoc + 4] = 0x00; + rom[moveDataLoc + 5] = 0x00; + } else { + rom[moveDataLoc] = (byte) 0xFF; + rom[moveDataLoc + 1] = (byte) 0xFF; + rom[moveDataLoc + 2] = 0x00; + rom[moveDataLoc + 3] = 0x00; + } + } + + } + + } + + private int writeMLToOffset(int offset, MoveLearnt ml) { + if (jamboMovesetHack) { + writeWord(offset, ml.move); + rom[offset + 2] = (byte) ml.level; + return 3; + } else { + rom[offset] = (byte) (ml.move & 0xFF); + int levelPart = (ml.level << 1) & 0xFE; + if (ml.move > 255) { + levelPart++; + } + rom[offset + 1] = (byte) levelPart; + return 2; + } + } + + private static class StaticPokemon { + private int[] offsets; + + public StaticPokemon(int... offsets) { + this.offsets = offsets; + } + + public Pokemon getPokemon(Gen3RomHandler parent) { + return parent.pokesInternal[parent.readWord(offsets[0])]; + } + + public void setPokemon(Gen3RomHandler parent, Pokemon pkmn) { + int value = parent.pokedexToInternal[pkmn.number]; + for (int offset : offsets) { + parent.writeWord(offset, value); + } + } + } + + @Override + public List<Pokemon> getStaticPokemon() { + List<Pokemon> statics = new ArrayList<Pokemon>(); + List<StaticPokemon> staticsHere = romEntry.staticPokemon; + for (StaticPokemon staticPK : staticsHere) { + statics.add(staticPK.getPokemon(this)); + } + return statics; + } + + @Override + public boolean setStaticPokemon(List<Pokemon> staticPokemon) { + // Support Deoxys/Mew gifts/catches in E/FR/LG + if (!havePatchedObedience) { + attemptObedienceEvolutionPatches(); + } + + List<StaticPokemon> staticsHere = romEntry.staticPokemon; + if (staticPokemon.size() != staticsHere.size()) { + return false; + } + + for (int i = 0; i < staticsHere.size(); i++) { + staticsHere.get(i).setPokemon(this, staticPokemon.get(i)); + } + return true; + } + + @Override + public List<Integer> getTMMoves() { + List<Integer> tms = new ArrayList<Integer>(); + int offset = romEntry.getValue("TmMoves"); + for (int i = 1; i <= Gen3Constants.tmCount; i++) { + tms.add(readWord(offset + (i - 1) * 2)); + } + return tms; + } + + @Override + public List<Integer> getHMMoves() { + return Gen3Constants.hmMoves; + } + + @Override + public void setTMMoves(List<Integer> moveIndexes) { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + int offset = romEntry.getValue("TmMoves"); + for (int i = 1; i <= Gen3Constants.tmCount; i++) { + writeWord(offset + (i - 1) * 2, moveIndexes.get(i - 1)); + } + int otherOffset = romEntry.getValue("TmMovesDuplicate"); + if (otherOffset > 0) { + // Emerald/FR/LG have *two* TM tables + System.arraycopy(rom, offset, rom, otherOffset, Gen3Constants.tmCount * 2); + } + + int iiOffset = romEntry.getValue("ItemImages"); + if (iiOffset > 0) { + int[] pals = romEntry.arrayEntries.get("TmPals"); + // Update the item image palettes + // Gen3 TMs are 289-338 + for (int i = 0; i < 50; i++) { + Move mv = moves[moveIndexes.get(i)]; + int typeID = Gen3Constants.typeToByte(mv.type); + writePointer(iiOffset + (Gen3Constants.tmItemOffset + i) * 8 + 4, pals[typeID]); + } + } + + int fsOffset = romEntry.getValue("FreeSpace"); + + // Item descriptions + if (romEntry.getValue("MoveDescriptions") > 0) { + // JP blocked for now - uses different item structure anyway + int idOffset = romEntry.getValue("ItemData"); + int mdOffset = romEntry.getValue("MoveDescriptions"); + int entrySize = romEntry.getValue("ItemEntrySize"); + int limitPerLine = (romEntry.romType == Gen3Constants.RomType_FRLG) ? Gen3Constants.frlgItemDescCharsPerLine + : Gen3Constants.rseItemDescCharsPerLine; + for (int i = 0; i < Gen3Constants.tmCount; i++) { + int itemBaseOffset = idOffset + (i + Gen3Constants.tmItemOffset) * entrySize; + int moveBaseOffset = mdOffset + (moveIndexes.get(i) - 1) * 4; + int moveTextPointer = readPointer(moveBaseOffset); + String moveDesc = readVariableLengthString(moveTextPointer); + String newItemDesc = RomFunctions.rewriteDescriptionForNewLineSize(moveDesc, "\\n", limitPerLine, ssd); + // Find freespace + int fsBytesNeeded = translateString(newItemDesc).length + 1; + int newItemDescOffset = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, fsBytesNeeded, + fsOffset); + if (newItemDescOffset < fsOffset) { + String nl = System.getProperty("line.separator"); + log("Couldn't insert new item description." + nl); + return; + } + writeVariableLengthString(newItemDesc, newItemDescOffset); + writePointer(itemBaseOffset + Gen3Constants.itemDataDescriptionOffset, newItemDescOffset); + } + } + + // TM Text? + for (TMOrMTTextEntry tte : romEntry.tmmtTexts) { + if (tte.actualOffset > 0 && !tte.isMoveTutor) { + // create the new TM text + int oldPointer = readPointer(tte.actualOffset); + if (oldPointer < 0 || oldPointer >= rom.length) { + throw new RuntimeException("TM Text update failed: couldn't read a TM text pointer."); + } + String moveName = this.moves[moveIndexes.get(tte.number - 1)].name; + // temporarily use underscores to stop the move name being split + String tmpMoveName = moveName.replace(' ', '_'); + String unformatted = tte.template.replace("[move]", tmpMoveName); + String newText = RomFunctions.formatTextWithReplacements(unformatted, null, "\\n", "\\l", "\\p", + Gen3Constants.regularTextboxCharsPerLine, ssd); + // get rid of the underscores + newText = newText.replace(tmpMoveName, moveName); + // insert the new text into free space + int fsBytesNeeded = translateString(newText).length + 1; + int newOffset = RomFunctions.freeSpaceFinder(rom, (byte) 0xFF, fsBytesNeeded, fsOffset); + if (newOffset < fsOffset) { + String nl = System.getProperty("line.separator"); + log("Couldn't insert new TM text." + nl); + return; + } + writeVariableLengthString(newText, newOffset); + // search for copies of the pointer: + // make a needle of the pointer + byte[] searchNeedle = new byte[4]; + System.arraycopy(rom, tte.actualOffset, searchNeedle, 0, 4); + // find copies within 500 bytes either way of actualOffset + int minOffset = Math.max(0, tte.actualOffset - Gen3Constants.pointerSearchRadius); + int maxOffset = Math.min(rom.length, tte.actualOffset + Gen3Constants.pointerSearchRadius); + List<Integer> pointerLocs = RomFunctions.search(rom, minOffset, maxOffset, searchNeedle); + for (int pointerLoc : pointerLocs) { + // write the new pointer + writePointer(pointerLoc, newOffset); + } + } + } + } + + private RomFunctions.StringSizeDeterminer ssd = new RomFunctions.StringSizeDeterminer() { + + @Override + public int lengthFor(String encodedText) { + return translateString(encodedText).length; + } + }; + + @Override + public int getTMCount() { + return Gen3Constants.tmCount; + } + + @Override + public int getHMCount() { + return Gen3Constants.hmCount; + } + + @Override + public Map<Pokemon, boolean[]> getTMHMCompatibility() { + Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); + int offset = romEntry.getValue("PokemonTMHMCompat"); + for (int i = 1; i < numRealPokemon; i++) { + Pokemon pkmn = pokemonList.get(i); + int compatOffset = offset + (pokedexToInternal[pkmn.number]) * 8; + boolean[] flags = new boolean[Gen3Constants.tmCount + Gen3Constants.hmCount + 1]; + for (int j = 0; j < 8; j++) { + readByteIntoFlags(flags, j * 8 + 1, compatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData) { + int offset = romEntry.getValue("PokemonTMHMCompat"); + for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int compatOffset = offset + (pokedexToInternal[pkmn.number]) * 8; + for (int j = 0; j < 8; j++) { + rom[compatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public boolean hasMoveTutors() { + return (romEntry.romType == Gen3Constants.RomType_Em || romEntry.romType == Gen3Constants.RomType_FRLG); + } + + @Override + public List<Integer> getMoveTutorMoves() { + if (!hasMoveTutors()) { + return new ArrayList<Integer>(); + } + List<Integer> mts = new ArrayList<Integer>(); + int moveCount = romEntry.getValue("MoveTutorMoves"); + int offset = romEntry.getValue("MoveTutorData"); + for (int i = 0; i < moveCount; i++) { + mts.add(readWord(offset + i * 2)); + } + return mts; + } + + @Override + public void setMoveTutorMoves(List<Integer> moves) { + if (!hasMoveTutors()) { + return; + } + int moveCount = romEntry.getValue("MoveTutorMoves"); + int offset = romEntry.getValue("MoveTutorData"); + if (moveCount != moves.size()) { + return; + } + for (int i = 0; i < moveCount; i++) { + writeWord(offset + i * 2, moves.get(i)); + } + int fsOffset = romEntry.getValue("FreeSpace"); + + // Move Tutor Text? + for (TMOrMTTextEntry tte : romEntry.tmmtTexts) { + if (tte.actualOffset > 0 && tte.isMoveTutor) { + // create the new MT text + int oldPointer = readPointer(tte.actualOffset); + if (oldPointer < 0 || oldPointer >= rom.length) { + throw new RuntimeException( + "Move Tutor Text update failed: couldn't read a move tutor text pointer."); + } + String moveName = this.moves[moves.get(tte.number)].name; + // temporarily use underscores to stop the move name being split + String tmpMoveName = moveName.replace(' ', '_'); + String unformatted = tte.template.replace("[move]", tmpMoveName); + String newText = RomFunctions.formatTextWithReplacements(unformatted, null, "\\n", "\\l", "\\p", + Gen3Constants.regularTextboxCharsPerLine, ssd); + // get rid of the underscores + newText = newText.replace(tmpMoveName, moveName); + // insert the new text into free space + int fsBytesNeeded = translateString(newText).length + 1; + int newOffset = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, fsBytesNeeded, fsOffset); + if (newOffset < fsOffset) { + String nl = System.getProperty("line.separator"); + log("Couldn't insert new Move Tutor text." + nl); + return; + } + writeVariableLengthString(newText, newOffset); + // search for copies of the pointer: + // make a needle of the pointer + byte[] searchNeedle = new byte[4]; + System.arraycopy(rom, tte.actualOffset, searchNeedle, 0, 4); + // find copies within 500 bytes either way of actualOffset + int minOffset = Math.max(0, tte.actualOffset - Gen3Constants.pointerSearchRadius); + int maxOffset = Math.min(rom.length, tte.actualOffset + Gen3Constants.pointerSearchRadius); + List<Integer> pointerLocs = RomFunctions.search(rom, minOffset, maxOffset, searchNeedle); + for (int pointerLoc : pointerLocs) { + // write the new pointer + writePointer(pointerLoc, newOffset); + } + } + } + } + + @Override + public Map<Pokemon, boolean[]> getMoveTutorCompatibility() { + if (!hasMoveTutors()) { + return new TreeMap<Pokemon, boolean[]>(); + } + Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); + int moveCount = romEntry.getValue("MoveTutorMoves"); + int offset = romEntry.getValue("MoveTutorCompatibility"); + int bytesRequired = ((moveCount + 7) & ~7) / 8; + for (int i = 1; i < numRealPokemon; i++) { + Pokemon pkmn = pokemonList.get(i); + int compatOffset = offset + pokedexToInternal[pkmn.number] * bytesRequired; + boolean[] flags = new boolean[moveCount + 1]; + for (int j = 0; j < bytesRequired; j++) { + readByteIntoFlags(flags, j * 8 + 1, compatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData) { + if (!hasMoveTutors()) { + return; + } + int moveCount = romEntry.getValue("MoveTutorMoves"); + int offset = romEntry.getValue("MoveTutorCompatibility"); + int bytesRequired = ((moveCount + 7) & ~7) / 8; + for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + int compatOffset = offset + pokedexToInternal[pkmn.number] * bytesRequired; + for (int j = 0; j < bytesRequired; j++) { + rom[compatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public String getROMName() { + return romEntry.name + (this.isRomHack ? " (ROM Hack)" : ""); + } + + @Override + public String getROMCode() { + return romEntry.romCode; + } + + @Override + public String getSupportLevel() { + return (romEntry.getValue("StaticPokemonSupport") > 0) ? "Complete" : "No Static Pokemon"; + } + + // For dynamic offsets later + private int find(String hexString) { + return find(rom, hexString); + } + + private static int find(byte[] haystack, String hexString) { + if (hexString.length() % 2 != 0) { + return -3; // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + List<Integer> found = RomFunctions.search(haystack, searchFor); + if (found.size() == 0) { + return -1; // not found + } else if (found.size() > 1) { + return -2; // not unique + } else { + return found.get(0); + } + } + + private List<Integer> findMultiple(String hexString) { + return findMultiple(rom, hexString); + } + + private static List<Integer> findMultiple(byte[] haystack, String hexString) { + if (hexString.length() % 2 != 0) { + return new ArrayList<Integer>(); // 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(haystack, searchFor); + return found; + } + + private void writeHexString(String hexString, int offset) { + if (hexString.length() % 2 != 0) { + return; // error + } + for (int i = 0; i < hexString.length() / 2; i++) { + rom[offset + i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + } + + private void attemptObedienceEvolutionPatches() { + havePatchedObedience = true; + // This routine *appears* to only exist in E/FR/LG... + // Look for the deoxys part which is + // MOVS R1, 0x19A + // CMP R0, R1 + // BEQ <mew/deoxys case> + // Hex is CD214900 8842 0FD0 + int deoxysObOffset = find(Gen3Constants.deoxysObeyCode); + if (deoxysObOffset > 0) { + // We found the deoxys check... + // Replacing it with MOVS R1, 0x0 would work fine. + // This would make it so species 0x0 (glitch only) would disobey. + // But MOVS R1, 0x0 (the version I know) is 2-byte + // So we just use it twice... + // the equivalent of nop'ing the second time. + rom[deoxysObOffset] = 0x00; + rom[deoxysObOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + rom[deoxysObOffset + 2] = 0x00; + rom[deoxysObOffset + 3] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + // Look for the mew check too... it's 0x16 ahead + if (readWord(deoxysObOffset + Gen3Constants.mewObeyOffsetFromDeoxysObey) == (((Gen3Constants.gbaCmpRxOpcode | Gen3Constants.gbaR0) << 8) | (Gen3Constants.mewIndex))) { + // Bingo, thats CMP R0, 0x97 + // change to CMP R0, 0x0 + writeWord(deoxysObOffset + Gen3Constants.mewObeyOffsetFromDeoxysObey, + (((Gen3Constants.gbaCmpRxOpcode | Gen3Constants.gbaR0) << 8) | (0))); + } + } + + // Look for evolutions too + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + int evoJumpOffset = find(Gen3Constants.levelEvoKantoDexCheckCode); + if (evoJumpOffset > 0) { + // This currently compares species to 0x97 and then allows + // evolution if it's <= that. + // Allow it regardless by using an unconditional jump instead + writeWord(evoJumpOffset, Gen3Constants.gbaNopOpcode); + writeWord(evoJumpOffset + 2, + ((Gen3Constants.gbaUnconditionalJumpOpcode << 8) | (Gen3Constants.levelEvoKantoDexJumpAmount))); + } + + int stoneJumpOffset = find(Gen3Constants.stoneEvoKantoDexCheckCode); + if (stoneJumpOffset > 0) { + // same as the above, but for stone evos + writeWord(stoneJumpOffset, Gen3Constants.gbaNopOpcode); + writeWord(stoneJumpOffset + 2, + ((Gen3Constants.gbaUnconditionalJumpOpcode << 8) | (Gen3Constants.stoneEvoKantoDexJumpAmount))); + } + } + } + + private void patchForNationalDex() { + log("--Patching for National Dex at Start of Game--"); + String nl = System.getProperty("line.separator"); + int fso = romEntry.getValue("FreeSpace"); + if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + // Find the original pokedex script + int pkDexOffset = find(Gen3Constants.rsPokedexScriptIdentifier); + if (pkDexOffset < 0) { + log("Patch unsuccessful." + nl); + return; + } + int textPointer = readPointer(pkDexOffset - 4); + int realScriptLocation = pkDexOffset - 8; + int pointerLocToScript = find(pointerToHexString(realScriptLocation)); + if (pointerLocToScript < 0) { + log("Patch unsuccessful." + nl); + return; + } + // Find free space for our new routine + int writeSpace = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, 44, fso); + if (writeSpace < fso) { + log("Patch unsuccessful." + nl); + // Somehow this ROM is full + return; + } + writePointer(pointerLocToScript, writeSpace); + writeHexString(Gen3Constants.rsNatDexScriptPart1, writeSpace); + writePointer(writeSpace + 4, textPointer); + writeHexString(Gen3Constants.rsNatDexScriptPart2, writeSpace + 8); + + } else if (romEntry.romType == Gen3Constants.RomType_FRLG) { + // Find the original pokedex script + int pkDexOffset = find(Gen3Constants.frlgPokedexScriptIdentifier); + if (pkDexOffset < 0) { + log("Patch unsuccessful." + nl); + return; + } + // Find free space for our new routine + int writeSpace = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, 10, fso); + if (writeSpace < fso) { + // Somehow this ROM is full + log("Patch unsuccessful." + nl); + return; + } + rom[pkDexOffset] = 4; + writePointer(pkDexOffset + 1, writeSpace); + rom[pkDexOffset + 5] = 0; // NOP + + // Now write our new routine + writeHexString(Gen3Constants.frlgNatDexScript, writeSpace); + + // Fix people using the national dex flag + List<Integer> ndexChecks = findMultiple(Gen3Constants.frlgNatDexFlagChecker); + for (int ndexCheckOffset : ndexChecks) { + // change to a flag-check + // 82C = "beaten e4/gary once" + writeHexString(Gen3Constants.frlgE4FlagChecker, ndexCheckOffset); + } + + // Fix oak in his lab + int oakLabCheckOffs = find(Gen3Constants.frlgOaksLabKantoDexChecker); + if (oakLabCheckOffs > 0) { + // replace it + writeHexString(Gen3Constants.frlgOaksLabFix, oakLabCheckOffs); + } + + // Fix oak outside your house + int oakHouseCheckOffs = find(Gen3Constants.frlgOakOutsideHouseCheck); + if (oakHouseCheckOffs > 0) { + // fix him to use ndex count + writeHexString(Gen3Constants.frlgOakOutsideHouseFix, oakHouseCheckOffs); + } + } else { + // Find the original pokedex script + int pkDexOffset = find(Gen3Constants.ePokedexScriptIdentifier); + if (pkDexOffset < 0) { + log("Patch unsuccessful." + nl); + return; + } + int textPointer = readPointer(pkDexOffset - 4); + int realScriptLocation = pkDexOffset - 8; + int pointerLocToScript = find(pointerToHexString(realScriptLocation)); + if (pointerLocToScript < 0) { + log("Patch unsuccessful." + nl); + return; + } + // Find free space for our new routine + int writeSpace = RomFunctions.freeSpaceFinder(rom, Gen3Constants.freeSpaceByte, 27, fso); + if (writeSpace < fso) { + // Somehow this ROM is full + log("Patch unsuccessful." + nl); + return; + } + writePointer(pointerLocToScript, writeSpace); + writeHexString(Gen3Constants.eNatDexScriptPart1, writeSpace); + writePointer(writeSpace + 4, textPointer); + writeHexString(Gen3Constants.eNatDexScriptPart2, writeSpace + 8); + } + log("Patch successful!" + nl); + } + + public String pointerToHexString(int pointer) { + String hex = String.format("%08X", pointer + 0x08000000); + return new String(new char[] { hex.charAt(6), hex.charAt(7), hex.charAt(4), hex.charAt(5), hex.charAt(2), + hex.charAt(3), hex.charAt(0), hex.charAt(1) }); + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + int baseOffset = romEntry.getValue("PokemonEvolutions"); + int numInternalPokes = romEntry.getValue("PokemonCount"); + for (int i = 1; i < numRealPokemon; i++) { + Pokemon pk = pokemonList.get(i); + int idx = pokedexToInternal[pk.number]; + int evoOffset = baseOffset + (idx) * 0x28; + for (int j = 0; j < 5; j++) { + int method = readWord(evoOffset + j * 8); + int evolvingTo = readWord(evoOffset + j * 8 + 4); + if (method >= 1 && method <= Gen3Constants.evolutionMethodCount && evolvingTo >= 1 + && evolvingTo <= numInternalPokes) { + int extraInfo = readWord(evoOffset + j * 8 + 2); + EvolutionType et = EvolutionType.fromIndex(3, method); + Evolution evo = new Evolution(pk, pokesInternal[evolvingTo], true, et, extraInfo); + if (!pk.evolutionsFrom.contains(evo)) { + pk.evolutionsFrom.add(evo); + pokesInternal[evolvingTo].evolutionsTo.add(evo); + } + } + } + // split evos don't carry stats + if (pk.evolutionsFrom.size() > 1) { + for (Evolution e : pk.evolutionsFrom) { + e.carryStats = false; + } + } + } + } + + private void writeEvolutions() { + int baseOffset = romEntry.getValue("PokemonEvolutions"); + for (int i = 1; i < numRealPokemon; i++) { + Pokemon pk = pokemonList.get(i); + int idx = pokedexToInternal[pk.number]; + int evoOffset = baseOffset + (idx) * 0x28; + int evosWritten = 0; + for (Evolution evo : pk.evolutionsFrom) { + writeWord(evoOffset, evo.type.toIndex(3)); + writeWord(evoOffset + 2, evo.extraInfo); + writeWord(evoOffset + 4, pokedexToInternal[evo.to.number]); + writeWord(evoOffset + 6, 0); + evoOffset += 8; + evosWritten++; + if (evosWritten == 5) { + break; + } + } + while (evosWritten < 5) { + writeWord(evoOffset, 0); + writeWord(evoOffset + 2, 0); + writeWord(evoOffset + 4, 0); + writeWord(evoOffset + 6, 0); + evoOffset += 8; + evosWritten++; + } + } + } + + @Override + public void removeTradeEvolutions(boolean changeMoveEvos) { + // no move evos, so no need to check for those + log("--Removing Trade Evolutions--"); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + for (Evolution evo : pkmn.evolutionsFrom) { + // Not trades, but impossible without trading + if (evo.type == EvolutionType.HAPPINESS_DAY && romEntry.romType == Gen3Constants.RomType_FRLG) { + // happiness day change to Sun Stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Gen3Constants.sunStoneIndex; // sun + // stone + logEvoChangeStone(evo.from.name, evo.to.name, itemNames[Gen3Constants.sunStoneIndex]); + } + if (evo.type == EvolutionType.HAPPINESS_NIGHT && romEntry.romType == Gen3Constants.RomType_FRLG) { + // happiness night change to Moon Stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Gen3Constants.moonStoneIndex; // moon + // stone + logEvoChangeStone(evo.from.name, evo.to.name, itemNames[Gen3Constants.moonStoneIndex]); + } + if (evo.type == EvolutionType.LEVEL_HIGH_BEAUTY && romEntry.romType == Gen3Constants.RomType_FRLG) { + // beauty change to level 35 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 35; + logEvoChangeLevel(evo.from.name, evo.to.name, 35); + } + // Pure Trade + if (evo.type == EvolutionType.TRADE) { + // Haunter, Machoke, Kadabra, Graveler + // Make it into level 37, we're done. + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + logEvoChangeLevel(evo.from.name, evo.to.name, 37); + } + // Trade w/ Held Item + if (evo.type == EvolutionType.TRADE_ITEM) { + if (evo.from.number == Gen3Constants.poliwhirlIndex) { + // Poliwhirl: Lv 37 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + logEvoChangeLevel(evo.from.name, evo.to.name, 37); + } else if (evo.from.number == Gen3Constants.slowpokeIndex) { + // Slowpoke: Water Stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Gen3Constants.waterStoneIndex; // water + // stone + logEvoChangeStone(evo.from.name, evo.to.name, itemNames[Gen3Constants.waterStoneIndex]); + } else if (evo.from.number == Gen3Constants.seadraIndex) { + // Seadra: Lv 40 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 40; + logEvoChangeLevel(evo.from.name, evo.to.name, 40); + } else if (evo.from.number == Gen3Constants.clamperlIndex + && evo.to.number == Gen3Constants.huntailIndex) { + // Clamperl -> Huntail: Lv30 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 30; + logEvoChangeLevel(evo.from.name, evo.to.name, 30); + } else if (evo.from.number == Gen3Constants.clamperlIndex + && evo.to.number == Gen3Constants.gorebyssIndex) { + // Clamperl -> Gorebyss: Water Stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Gen3Constants.waterStoneIndex; // water + // stone + logEvoChangeStone(evo.from.name, evo.to.name, itemNames[Gen3Constants.waterStoneIndex]); + } else { + // Onix, Scyther or Porygon: Lv30 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 30; + logEvoChangeLevel(evo.from.name, evo.to.name, 30); + } + } + } + } + } + logBlankLine(); + } + + @Override + public List<String> getTrainerNames() { + int baseOffset = romEntry.getValue("TrainerData"); + int amount = romEntry.getValue("TrainerCount"); + int entryLen = romEntry.getValue("TrainerEntrySize"); + List<String> theTrainers = new ArrayList<String>(); + for (int i = 1; i < amount; i++) { + theTrainers.add(readVariableLengthString(baseOffset + i * entryLen + 4)); + } + return theTrainers; + } + + @Override + public void setTrainerNames(List<String> trainerNames) { + int baseOffset = romEntry.getValue("TrainerData"); + int amount = romEntry.getValue("TrainerCount"); + int entryLen = romEntry.getValue("TrainerEntrySize"); + int nameLen = romEntry.getValue("TrainerNameLength"); + Iterator<String> theTrainers = trainerNames.iterator(); + for (int i = 1; i < amount; i++) { + String newName = theTrainers.next(); + writeFixedLengthString(newName, baseOffset + i * entryLen + 4, nameLen); + } + + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.MAX_LENGTH; + } + + @Override + public List<Integer> getTCNameLengthsByTrainer() { + // not needed + return new ArrayList<Integer>(); + } + + @Override + public int maxTrainerNameLength() { + return romEntry.getValue("TrainerNameLength") - 1; + } + + @Override + public List<String> getTrainerClassNames() { + int baseOffset = romEntry.getValue("TrainerClassNames"); + int amount = romEntry.getValue("TrainerClassCount"); + int length = romEntry.getValue("TrainerClassNameLength"); + List<String> trainerClasses = new ArrayList<String>(); + for (int i = 0; i < amount; i++) { + trainerClasses.add(readVariableLengthString(baseOffset + i * length)); + } + return trainerClasses; + } + + @Override + public void setTrainerClassNames(List<String> trainerClassNames) { + int baseOffset = romEntry.getValue("TrainerClassNames"); + int amount = romEntry.getValue("TrainerClassCount"); + int length = romEntry.getValue("TrainerClassNameLength"); + Iterator<String> trainerClasses = trainerClassNames.iterator(); + for (int i = 0; i < amount; i++) { + writeFixedLengthString(trainerClasses.next(), baseOffset + i * length, length); + } + } + + @Override + public int maxTrainerClassNameLength() { + return romEntry.getValue("TrainerClassNameLength") - 1; + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return false; + } + + @Override + public boolean canChangeStaticPokemon() { + return (romEntry.getValue("StaticPokemonSupport") > 0); + } + + @Override + public String getDefaultExtension() { + return "gba"; + } + + @Override + public int abilitiesPerPokemon() { + return 2; + } + + @Override + public int highestAbilityIndex() { + return Gen3Constants.highestAbilityIndex; + } + + private void loadAbilityNames() { + int nameoffs = romEntry.getValue("AbilityNames"); + int namelen = romEntry.getValue("AbilityNameLength"); + abilityNames = new String[Gen3Constants.highestAbilityIndex + 1]; + for (int i = 0; i <= Gen3Constants.highestAbilityIndex; i++) { + abilityNames[i] = readFixedLengthString(nameoffs + namelen * i, namelen); + } + } + + @Override + public String abilityName(int number) { + return abilityNames[number]; + } + + @Override + public int internalStringLength(String string) { + return translateString(string).length; + } + + @Override + public void applySignature() { + // FRLG + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + // intro sprites : first 255 only due to size + Pokemon introPk = randomPokemonLimited(255, false); + if (introPk == null) { + return; + } + int introPokemon = pokedexToInternal[introPk.number]; + int frontSprites = romEntry.getValue("FrontSprites"); + int palettes = romEntry.getValue("PokemonPalettes"); + + rom[romEntry.getValue("IntroCryOffset")] = (byte) introPokemon; + rom[romEntry.getValue("IntroOtherOffset")] = (byte) introPokemon; + + int spriteBase = romEntry.getValue("IntroSpriteOffset"); + writePointer(spriteBase, frontSprites + introPokemon * 8); + writePointer(spriteBase + 4, palettes + introPokemon * 8); + } else if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + // intro sprites : hoenn pokes only + int numInternalPokes = romEntry.getValue("PokemonCount"); + int introPokemon = this.random.nextInt(Math.min(numInternalPokes, 509)) + 1; + while (internalToPokedex[introPokemon] < Gen3Constants.hoennPokesStart) { + introPokemon = this.random.nextInt(Math.min(numInternalPokes, 509)) + 1; + } + int frontSprites = romEntry.getValue("PokemonFrontSprites"); + int palettes = romEntry.getValue("PokemonNormalPalettes"); + int cryCommand = romEntry.getValue("IntroCryOffset"); + int otherCommand = romEntry.getValue("IntroOtherOffset"); + + if (introPokemon > 255) { + rom[cryCommand] = (byte) 0xFF; + rom[cryCommand + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR0; + + rom[cryCommand + 2] = (byte) (introPokemon - 0xFF); + rom[cryCommand + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR0; + + rom[otherCommand] = (byte) 0xFF; + rom[otherCommand + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR4; + + rom[otherCommand + 2] = (byte) (introPokemon - 0xFF); + rom[otherCommand + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR4; + } else { + rom[cryCommand] = (byte) introPokemon; + rom[cryCommand + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR0; + + writeWord(cryCommand + 2, Gen3Constants.gbaNopOpcode); + + rom[otherCommand] = (byte) introPokemon; + rom[otherCommand + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR4; + + writeWord(otherCommand + 2, Gen3Constants.gbaNopOpcode); + } + + writePointer(romEntry.getValue("IntroSpriteOffset"), frontSprites + introPokemon * 8); + writePointer(romEntry.getValue("IntroPaletteOffset"), palettes + introPokemon * 8); + } else { + // Emerald, intro sprite: any Pokemon. + int introPokemon = pokedexToInternal[randomPokemon().number]; + writeWord(romEntry.getValue("IntroSpriteOffset"), introPokemon); + writeWord(romEntry.getValue("IntroCryOffset"), introPokemon); + } + + } + + private Pokemon randomPokemonLimited(int maxValue, boolean blockNonMales) { + checkPokemonRestrictions(); + List<Pokemon> validPokemon = new ArrayList<Pokemon>(); + for (Pokemon pk : this.mainPokemonList) { + if (pokedexToInternal[pk.number] <= maxValue && (!blockNonMales || pk.genderRatio <= 0xFD)) { + validPokemon.add(pk); + } + } + if (validPokemon.size() == 0) { + return null; + } else { + return validPokemon.get(random.nextInt(validPokemon.size())); + } + } + + private void determineMapBankSizes() { + int mbpsOffset = romEntry.getValue("MapHeaders"); + List<Integer> mapBankOffsets = new ArrayList<Integer>(); + + int offset = mbpsOffset; + + // find map banks + while (true) { + boolean valid = true; + for (int mbOffset : mapBankOffsets) { + if (mbpsOffset < mbOffset && offset >= mbOffset) { + valid = false; + break; + } + } + if (!valid) { + break; + } + int newMBOffset = readPointer(offset); + if (newMBOffset < 0 || newMBOffset >= rom.length) { + break; + } + mapBankOffsets.add(newMBOffset); + offset += 4; + } + int bankCount = mapBankOffsets.size(); + int[] bankMapCounts = new int[bankCount]; + for (int bank = 0; bank < bankCount; bank++) { + int baseBankOffset = mapBankOffsets.get(bank); + int count = 0; + offset = baseBankOffset; + while (true) { + boolean valid = true; + for (int mbOffset : mapBankOffsets) { + if (baseBankOffset < mbOffset && offset >= mbOffset) { + valid = false; + break; + } + } + if (!valid) { + break; + } + if (baseBankOffset < mbpsOffset && offset >= mbpsOffset) { + break; + } + int newMapOffset = readPointer(offset); + if (newMapOffset < 0 || newMapOffset >= rom.length) { + break; + } + count++; + offset += 4; + } + bankMapCounts[bank] = count; + } + + romEntry.entries.put("MapBankCount", bankCount); + romEntry.arrayEntries.put("MapBankSizes", bankMapCounts); + } + + private void preprocessMaps() { + itemOffs = new ArrayList<Integer>(); + int bankCount = romEntry.getValue("MapBankCount"); + int[] bankMapCounts = romEntry.arrayEntries.get("MapBankSizes"); + int itemBall = romEntry.getValue("ItemBallPic"); + mapNames = new String[bankCount][]; + int mbpsOffset = romEntry.getValue("MapHeaders"); + int mapLabels = romEntry.getValue("MapLabels"); + Map<Integer, String> mapLabelsM = new HashMap<Integer, String>(); + for (int bank = 0; bank < bankCount; bank++) { + int bankOffset = readPointer(mbpsOffset + bank * 4); + mapNames[bank] = new String[bankMapCounts[bank]]; + for (int map = 0; map < bankMapCounts[bank]; map++) { + int mhOffset = readPointer(bankOffset + map * 4); + + // map name + int mapLabel = rom[mhOffset + 0x14] & 0xFF; + if (mapLabelsM.containsKey(mapLabel)) { + mapNames[bank][map] = mapLabelsM.get(mapLabel); + } else { + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + mapNames[bank][map] = readVariableLengthString(readPointer(mapLabels + + (mapLabel - Gen3Constants.frlgMapLabelsStart) * 4)); + } else { + mapNames[bank][map] = readVariableLengthString(readPointer(mapLabels + mapLabel * 8 + 4)); + } + mapLabelsM.put(mapLabel, mapNames[bank][map]); + } + + // events + int eventOffset = readPointer(mhOffset + 4); + if (eventOffset >= 0 && eventOffset < rom.length) { + + int pCount = rom[eventOffset] & 0xFF; + int spCount = rom[eventOffset + 3] & 0xFF; + + if (pCount > 0) { + int peopleOffset = readPointer(eventOffset + 4); + for (int p = 0; p < pCount; p++) { + int pSprite = rom[peopleOffset + p * 24 + 1]; + if (pSprite == itemBall && readPointer(peopleOffset + p * 24 + 16) >= 0) { + // Get script and look inside + int scriptOffset = readPointer(peopleOffset + p * 24 + 16); + if (rom[scriptOffset] == 0x1A && rom[scriptOffset + 1] == 0x00 + && (rom[scriptOffset + 2] & 0xFF) == 0x80 && rom[scriptOffset + 5] == 0x1A + && rom[scriptOffset + 6] == 0x01 && (rom[scriptOffset + 7] & 0xFF) == 0x80 + && rom[scriptOffset + 10] == 0x09 + && (rom[scriptOffset + 11] == 0x00 || rom[scriptOffset + 11] == 0x01)) { + // item ball script + itemOffs.add(scriptOffset + 3); + } + } + } + // TM Text? + for (TMOrMTTextEntry tte : romEntry.tmmtTexts) { + if (tte.mapBank == bank && tte.mapNumber == map) { + // process this one + int scriptOffset = readPointer(peopleOffset + (tte.personNum - 1) * 24 + 16); + if (scriptOffset >= 0) { + if (romEntry.romType == Gen3Constants.RomType_FRLG && tte.isMoveTutor + && (tte.number == 5 || (tte.number >= 8 && tte.number <= 11))) { + scriptOffset = readPointer(scriptOffset + 1); + } else if (romEntry.romType == Gen3Constants.RomType_FRLG && tte.isMoveTutor + && tte.number == 7) { + scriptOffset = readPointer(scriptOffset + 0x1F); + } + int lookAt = scriptOffset + tte.offsetInScript; + // make sure this actually looks like a text + // pointer + if (lookAt >= 0 && lookAt < rom.length - 2) { + if (rom[lookAt + 3] == 0x08 || rom[lookAt + 3] == 0x09) { + // okay, it passes the basic test + tte.actualOffset = lookAt; + } + } + } + } + } + } + + if (spCount > 0) { + int signpostsOffset = readPointer(eventOffset + 16); + for (int sp = 0; sp < spCount; sp++) { + int spType = rom[signpostsOffset + sp * 12 + 5]; + if (spType >= 5 && spType <= 7) { + // hidden item + int itemHere = readWord(signpostsOffset + sp * 12 + 8); + if (itemHere != 0) { + // itemid 0 is coins + itemOffs.add(signpostsOffset + sp * 12 + 8); + } + } + } + } + } + } + } + } + + @Override + public ItemList getAllowedItems() { + return Gen3Constants.allowedItems; + } + + @Override + public ItemList getNonBadItems() { + return Gen3Constants.nonBadItems; + } + + private void loadItemNames() { + int nameoffs = romEntry.getValue("ItemData"); + int structlen = romEntry.getValue("ItemEntrySize"); + int maxcount = romEntry.getValue("ItemCount"); + itemNames = new String[maxcount + 1]; + for (int i = 0; i <= maxcount; i++) { + itemNames[i] = readVariableLengthString(nameoffs + structlen * i); + } + } + + @Override + public String[] getItemNames() { + return itemNames; + } + + @Override + public List<Integer> getRequiredFieldTMs() { + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + return Gen3Constants.frlgRequiredFieldTMs; + } else if (romEntry.romType == Gen3Constants.RomType_Ruby || romEntry.romType == Gen3Constants.RomType_Sapp) { + return Gen3Constants.rsRequiredFieldTMs; + } else { + // emerald has a few TMs from pickup + return Gen3Constants.eRequiredFieldTMs; + } + } + + @Override + public List<Integer> getCurrentFieldTMs() { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + List<Integer> fieldTMs = new ArrayList<Integer>(); + + for (int offset : itemOffs) { + int itemHere = readWord(offset); + if (Gen3Constants.allowedItems.isTM(itemHere)) { + int thisTM = itemHere - Gen3Constants.tmItemOffset + 1; + // hack for repeat TMs + if (fieldTMs.contains(thisTM) == false) { + fieldTMs.add(thisTM); + } + } + } + return fieldTMs; + } + + @Override + public void setFieldTMs(List<Integer> fieldTMs) { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + Iterator<Integer> iterTMs = fieldTMs.iterator(); + int[] givenTMs = new int[512]; + + for (int offset : itemOffs) { + int itemHere = readWord(offset); + if (Gen3Constants.allowedItems.isTM(itemHere)) { + // Cache replaced TMs to duplicate repeats + if (givenTMs[itemHere] != 0) { + rom[offset] = (byte) givenTMs[itemHere]; + } else { + // Replace this with a TM from the list + int tm = iterTMs.next(); + tm += Gen3Constants.tmItemOffset - 1; + givenTMs[itemHere] = tm; + writeWord(offset, tm); + } + } + } + } + + @Override + public List<Integer> getRegularFieldItems() { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + List<Integer> fieldItems = new ArrayList<Integer>(); + + for (int offset : itemOffs) { + int itemHere = readWord(offset); + if (Gen3Constants.allowedItems.isAllowed(itemHere) && !(Gen3Constants.allowedItems.isTM(itemHere))) { + fieldItems.add(itemHere); + } + } + return fieldItems; + } + + @Override + public void setRegularFieldItems(List<Integer> items) { + if (!mapLoadingDone) { + preprocessMaps(); + mapLoadingDone = true; + } + Iterator<Integer> iterItems = items.iterator(); + + for (int offset : itemOffs) { + int itemHere = readWord(offset); + if (Gen3Constants.allowedItems.isAllowed(itemHere) && !(Gen3Constants.allowedItems.isTM(itemHere))) { + // Replace it + writeWord(offset, iterItems.next()); + } + } + + } + + @Override + public List<IngameTrade> getIngameTrades() { + List<IngameTrade> trades = new ArrayList<IngameTrade>(); + + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = 60; + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = new IngameTrade(); + int entryOffset = tableOffset + entry * entryLength; + trade.nickname = readVariableLengthString(entryOffset); + trade.givenPokemon = pokesInternal[readWord(entryOffset + 12)]; + trade.ivs = new int[6]; + for (int i = 0; i < 6; i++) { + trade.ivs[i] = rom[entryOffset + 14 + i] & 0xFF; + } + trade.otId = readWord(entryOffset + 24); + trade.item = readWord(entryOffset + 40); + trade.otName = readVariableLengthString(entryOffset + 43); + trade.requestedPokemon = pokesInternal[readWord(entryOffset + 56)]; + trades.add(trade); + } + + return trades; + + } + + @Override + public void setIngameTrades(List<IngameTrade> trades) { + // info + int tableOffset = romEntry.getValue("TradeTableOffset"); + int tableSize = romEntry.getValue("TradeTableSize"); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int entryLength = 60; + int tradeOffset = 0; + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = trades.get(tradeOffset++); + int entryOffset = tableOffset + entry * entryLength; + writeFixedLengthString(trade.nickname, entryOffset, 12); + writeWord(entryOffset + 12, pokedexToInternal[trade.givenPokemon.number]); + for (int i = 0; i < 6; i++) { + rom[entryOffset + 14 + i] = (byte) trade.ivs[i]; + } + writeWord(entryOffset + 24, trade.otId); + writeWord(entryOffset + 40, trade.item); + writeFixedLengthString(trade.otName, entryOffset + 43, 11); + writeWord(entryOffset + 56, pokedexToInternal[trade.requestedPokemon.number]); + } + } + + @Override + public boolean hasDVs() { + return false; + } + + @Override + public int generationOfPokemon() { + return 3; + } + + @Override + public void removeEvosForPokemonPool() { + List<Pokemon> pokemonIncluded = this.mainPokemonList; + Set<Evolution> keepEvos = new HashSet<Evolution>(); + for (Pokemon pk : pokes) { + if (pk != null) { + keepEvos.clear(); + for (Evolution evol : pk.evolutionsFrom) { + if (pokemonIncluded.contains(evol.from) && pokemonIncluded.contains(evol.to)) { + keepEvos.add(evol); + } else { + evol.to.evolutionsTo.remove(evol); + } + } + pk.evolutionsFrom.retainAll(keepEvos); + } + } + } + + @Override + public boolean supportsFourStartingMoves() { + return true; + } + + @Override + public List<Integer> getFieldMoves() { + // cut, fly, surf, strength, flash, + // dig, teleport, waterfall, + // rock smash, sweet scent + // not softboiled or milk drink + // dive and secret power in RSE only + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + return Gen3Constants.frlgFieldMoves; + } else { + return Gen3Constants.rseFieldMoves; + } + } + + @Override + public List<Integer> getEarlyRequiredHMMoves() { + // RSE: rock smash + // FRLG: cut + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + return Gen3Constants.frlgEarlyRequiredHMMoves; + } else { + return Gen3Constants.rseEarlyRequiredHMMoves; + } + } + + @Override + public int miscTweaksAvailable() { + int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); + available |= MiscTweak.NATIONAL_DEX_AT_START.getValue(); + if (romEntry.getValue("RunIndoorsTweakOffset") > 0) { + available |= MiscTweak.RUNNING_SHOES_INDOORS.getValue(); + } + if (romEntry.getValue("TextSpeedValuesOffset") > 0) { + available |= MiscTweak.FASTEST_TEXT.getValue(); + } + if (romEntry.getValue("CatchingTutorialOpponentMonOffset") > 0 + || romEntry.getValue("CatchingTutorialPlayerMonOffset") > 0) { + available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); + } + + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.RUNNING_SHOES_INDOORS) { + applyRunningShoesIndoorsPatch(); + } else if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestTextPatch(); + } else if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { + applyCamelCaseNames(); + } else if (tweak == MiscTweak.NATIONAL_DEX_AT_START) { + patchForNationalDex(); + } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { + randomizeCatchingTutorial(); + } + } + + private void randomizeCatchingTutorial() { + if (romEntry.getValue("CatchingTutorialOpponentMonOffset") > 0) { + int oppOffset = romEntry.getValue("CatchingTutorialOpponentMonOffset"); + if (romEntry.romType == Gen3Constants.RomType_FRLG) { + Pokemon opponent = randomPokemonLimited(255, true); + if (opponent != null) { + + int oppValue = pokedexToInternal[opponent.number]; + rom[oppOffset] = (byte) oppValue; + rom[oppOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + } + } else { + Pokemon opponent = randomPokemonLimited(510, true); + if (opponent != null) { + int oppValue = pokedexToInternal[opponent.number]; + if (oppValue > 255) { + rom[oppOffset] = (byte) 0xFF; + rom[oppOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + + rom[oppOffset + 2] = (byte) (oppValue - 0xFF); + rom[oppOffset + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR1; + } else { + rom[oppOffset] = (byte) oppValue; + rom[oppOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + + writeWord(oppOffset + 2, Gen3Constants.gbaNopOpcode); + } + } + } + } + + if (romEntry.getValue("CatchingTutorialPlayerMonOffset") > 0) { + int playerOffset = romEntry.getValue("CatchingTutorialPlayerMonOffset"); + Pokemon playerMon = randomPokemonLimited(510, false); + if (playerMon != null) { + int plyValue = pokedexToInternal[playerMon.number]; + if (plyValue > 255) { + rom[playerOffset] = (byte) 0xFF; + rom[playerOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + + rom[playerOffset + 2] = (byte) (plyValue - 0xFF); + rom[playerOffset + 3] = Gen3Constants.gbaAddRxOpcode | Gen3Constants.gbaR1; + } else { + rom[playerOffset] = (byte) plyValue; + rom[playerOffset + 1] = Gen3Constants.gbaSetRxOpcode | Gen3Constants.gbaR1; + + writeWord(playerOffset + 2, Gen3Constants.gbaNopOpcode); + } + } + } + + } + + private void applyRunningShoesIndoorsPatch() { + if (romEntry.getValue("RunIndoorsTweakOffset") != 0) { + rom[romEntry.getValue("RunIndoorsTweakOffset")] = 0x00; + } + } + + private void applyFastestTextPatch() { + if (romEntry.getValue("TextSpeedValuesOffset") > 0) { + int tsvOffset = romEntry.getValue("TextSpeedValuesOffset"); + rom[tsvOffset] = 4; // slow = medium + rom[tsvOffset + 1] = 1; // medium = fast + rom[tsvOffset + 2] = 0; // fast = instant + } + } + + @Override + public boolean isROMHack() { + return this.isRomHack; + } + + @Override + public BufferedImage getMascotImage() { + Pokemon mascotPk = randomPokemon(); + int mascotPokemon = pokedexToInternal[mascotPk.number]; + int frontSprites = romEntry.getValue("FrontSprites"); + int palettes = romEntry.getValue("PokemonPalettes"); + int fsOffset = readPointer(frontSprites + mascotPokemon * 8); + int palOffset = readPointer(palettes + mascotPokemon * 8); + + byte[] trueFrontSprite = DSDecmp.Decompress(rom, fsOffset); + byte[] truePalette = DSDecmp.Decompress(rom, palOffset); + + // Convert palette into RGB + int[] convPalette = new int[16]; + // Leave palette[0] as 00000000 for transparency + for (int i = 0; i < 15; i++) { + int palValue = readWord(truePalette, i * 2 + 2); + convPalette[i + 1] = GFXFunctions.conv16BitColorToARGB(palValue); + } + + // Make image, 4bpp + BufferedImage bim = GFXFunctions.drawTiledImage(trueFrontSprite, convPalette, 64, 64, 4); + return bim; + } } diff --git a/src/com/dabomstew/pkrandom/romhandlers/Gen4RomHandler.java b/src/com/dabomstew/pkrandom/romhandlers/Gen4RomHandler.java index 5fc8a5a..02319df 100755 --- a/src/com/dabomstew/pkrandom/romhandlers/Gen4RomHandler.java +++ b/src/com/dabomstew/pkrandom/romhandlers/Gen4RomHandler.java @@ -62,2802 +62,2619 @@ import com.dabomstew.pkrandom.pokemon.TrainerPokemon; public class Gen4RomHandler extends AbstractDSRomHandler { - public static class Factory extends RomHandler.Factory { - - @Override - public Gen4RomHandler create(Random random, PrintStream logStream) { - return new Gen4RomHandler(random, logStream); - } - - public boolean isLoadable(String filename) { - return detectNDSRomInner(getROMCodeFromFile(filename)); - } - } - - public Gen4RomHandler(Random random) { - super(random, null); - } - - public Gen4RomHandler(Random random, PrintStream logStream) { - super(random, logStream); - } - - private static class RomEntry { - private String name; - private String romCode; - private int romType; - private boolean staticPokemonSupport = false, - copyStaticPokemon = false; - private Map<String, String> strings = new HashMap<String, String>(); - private Map<String, String> tweakFiles = new HashMap<String, String>(); - private Map<String, Integer> numbers = new HashMap<String, Integer>(); - private Map<String, int[]> arrayEntries = new HashMap<String, int[]>(); - private List<StaticPokemon> staticPokemon = new ArrayList<StaticPokemon>(); - - 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 static List<RomEntry> roms; - - static { - loadROMInfo(); - - } - - private static void loadROMInfo() { - roms = new ArrayList<RomEntry>(); - RomEntry current = null; - try { - Scanner sc = new Scanner( - FileFunctions.openConfig("gen4_offsets.ini"), "UTF-8"); - while (sc.hasNextLine()) { - String q = sc.nextLine().trim(); - if (q.contains("//")) { - q = q.substring(0, q.indexOf("//")).trim(); - } - if (!q.isEmpty()) { - if (q.startsWith("[") && q.endsWith("]")) { - // New rom - current = new RomEntry(); - current.name = q.substring(1, q.length() - 1); - roms.add(current); - } else { - String[] r = q.split("=", 2); - if (r.length == 1) { - System.err.println("invalid entry " + q); - continue; - } - if (r[1].endsWith("\r\n")) { - r[1] = r[1].substring(0, r[1].length() - 2); - } - r[1] = r[1].trim(); - if (r[0].equals("Game")) { - current.romCode = r[1]; - } else if (r[0].equals("Type")) { - if (r[1].equalsIgnoreCase("DP")) { - current.romType = Gen4Constants.Type_DP; - } else if (r[1].equalsIgnoreCase("Plat")) { - current.romType = Gen4Constants.Type_Plat; - } else if (r[1].equalsIgnoreCase("HGSS")) { - current.romType = Gen4Constants.Type_HGSS; - } else { - System.err.println("unrecognised rom type: " - + r[1]); - } - } else if (r[0].equals("CopyFrom")) { - for (RomEntry otherEntry : roms) { - if (r[1].equalsIgnoreCase(otherEntry.romCode)) { - // copy from here - current.arrayEntries - .putAll(otherEntry.arrayEntries); - current.numbers.putAll(otherEntry.numbers); - current.strings.putAll(otherEntry.strings); - if (current.copyStaticPokemon) { - current.staticPokemon - .addAll(otherEntry.staticPokemon); - current.staticPokemonSupport = true; - } else { - current.staticPokemonSupport = false; - } - } - } - } else if (r[0].equals("StaticPokemon[]")) { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - int[] offs = new int[offsets.length]; - int[] files = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - String[] parts = off.split("\\:"); - files[c] = parseRIInt(parts[0]); - offs[c++] = parseRIInt(parts[1]); - } - StaticPokemon sp = new StaticPokemon(); - sp.files = files; - sp.offsets = offs; - current.staticPokemon.add(sp); - } else { - String[] parts = r[1].split("\\:"); - int files = parseRIInt(parts[0]); - int offs = parseRIInt(parts[1]); - StaticPokemon sp = new StaticPokemon(); - sp.files = new int[] { files }; - sp.offsets = new int[] { offs }; - } - } else if (r[0].equals("StaticPokemonSupport")) { - int spsupport = parseRIInt(r[1]); - current.staticPokemonSupport = (spsupport > 0); - } else if (r[0].equals("CopyStaticPokemon")) { - int csp = parseRIInt(r[1]); - current.copyStaticPokemon = (csp > 0); - } else if (r[0].endsWith("Tweak")) { - current.tweakFiles.put(r[0], r[1]); - } else { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - if (offsets.length == 1 - && offsets[0].trim().isEmpty()) { - current.arrayEntries.put(r[0], new int[0]); - } else { - int[] offs = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - offs[c++] = parseRIInt(off); - } - current.arrayEntries.put(r[0], offs); - } - } else if (r[0].endsWith("Offset") - || r[0].endsWith("Count") - || r[0].endsWith("Number") - || r[0].endsWith("Size")) { - int offs = parseRIInt(r[1]); - current.numbers.put(r[0], offs); - } else { - current.strings.put(r[0], r[1]); - } - } - } - } - } - sc.close(); - } catch (FileNotFoundException e) { - } - - } - - 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; - } - } - - // This rom - private Pokemon[] pokes; - private List<Pokemon> pokemonList; - private Move[] moves; - private NARCContents pokeNarc, moveNarc; - private NARCContents msgNarc; - private NARCContents scriptNarc; - private NARCContents eventNarc; - private byte[] arm9; - private List<String> abilityNames; - private List<String> itemNames; - private boolean loadedWildMapNames; - private Map<Integer, String> wildMapNames; - - private RomEntry romEntry; - - @Override - protected boolean detectNDSRom(String ndsCode) { - return detectNDSRomInner(ndsCode); - } - - private static boolean detectNDSRomInner(String ndsCode) { - return entryFor(ndsCode) != null; - } - - private static RomEntry entryFor(String ndsCode) { - for (RomEntry re : roms) { - if (ndsCode.equals(re.romCode)) { - return re; - } - } - return null; - } - - @Override - protected void loadedROM(String romCode) { - this.romEntry = entryFor(romCode); - try { - arm9 = readARM9(); - } catch (IOException e) { - throw new RuntimeException(e); - } - try { - msgNarc = readNARC(romEntry.getString("Text")); - } catch (IOException e) { - throw new RuntimeException(e); - } - try { - scriptNarc = readNARC(romEntry.getString("Scripts")); - } catch (IOException e) { - throw new RuntimeException(e); - } - try { - eventNarc = readNARC(romEntry.getString("Events")); - } catch (IOException e) { - throw new RuntimeException(e); - } - loadPokemonStats(); - pokemonList = Arrays.asList(pokes); - loadMoves(); - abilityNames = getStrings(romEntry.getInt("AbilityNamesTextOffset")); - itemNames = getStrings(romEntry.getInt("ItemNamesTextOffset")); - loadedWildMapNames = false; - } - - private void loadMoves() { - try { - moveNarc = this.readNARC(romEntry.getString("MoveData")); - moves = new Move[Gen4Constants.moveCount + 1]; - List<String> moveNames = getStrings(romEntry - .getInt("MoveNamesTextOffset")); - for (int i = 1; i <= Gen4Constants.moveCount; i++) { - byte[] moveData = moveNarc.files.get(i); - moves[i] = new Move(); - moves[i].name = moveNames.get(i); - moves[i].number = i; - moves[i].internalId = i; - moves[i].effectIndex = readWord(moveData, 0); - moves[i].hitratio = (moveData[5] & 0xFF); - moves[i].power = moveData[3] & 0xFF; - moves[i].pp = moveData[6] & 0xFF; - moves[i].type = Gen4Constants.typeTable[moveData[4] & 0xFF]; - moves[i].category = Gen4Constants.moveCategoryIndices[moveData[2] & 0xFF]; - } - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - private void loadPokemonStats() { - try { - String pstatsnarc = romEntry.getString("PokemonStats"); - pokeNarc = this.readNARC(pstatsnarc); - String[] pokeNames = readPokemonNames(); - pokes = new Pokemon[Gen4Constants.pokemonCount + 1]; - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - pokes[i] = new Pokemon(); - pokes[i].number = i; - loadBasicPokeStats(pokes[i], pokeNarc.files.get(i)); - // Name? - pokes[i].name = pokeNames[i]; - } - populateEvolutions(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - private void loadBasicPokeStats(Pokemon pkmn, byte[] stats) { - pkmn.hp = stats[Gen4Constants.bsHPOffset] & 0xFF; - pkmn.attack = stats[Gen4Constants.bsAttackOffset] & 0xFF; - pkmn.defense = stats[Gen4Constants.bsDefenseOffset] & 0xFF; - pkmn.speed = stats[Gen4Constants.bsSpeedOffset] & 0xFF; - pkmn.spatk = stats[Gen4Constants.bsSpAtkOffset] & 0xFF; - pkmn.spdef = stats[Gen4Constants.bsSpDefOffset] & 0xFF; - // Type - pkmn.primaryType = Gen4Constants.typeTable[stats[Gen4Constants.bsPrimaryTypeOffset] & 0xFF]; - pkmn.secondaryType = Gen4Constants.typeTable[stats[Gen4Constants.bsSecondaryTypeOffset] & 0xFF]; - // Only one type? - if (pkmn.secondaryType == pkmn.primaryType) { - pkmn.secondaryType = null; - } - pkmn.catchRate = stats[Gen4Constants.bsCatchRateOffset] & 0xFF; - pkmn.growthCurve = ExpCurve - .fromByte(stats[Gen4Constants.bsGrowthCurveOffset]); - - // Abilities - pkmn.ability1 = stats[Gen4Constants.bsAbility1Offset] & 0xFF; - pkmn.ability2 = stats[Gen4Constants.bsAbility2Offset] & 0xFF; - - // Held Items? - int item1 = readWord(stats, Gen4Constants.bsCommonHeldItemOffset); - int item2 = readWord(stats, Gen4Constants.bsRareHeldItemOffset); - - if (item1 == item2) { - // guaranteed - pkmn.guaranteedHeldItem = item1; - pkmn.commonHeldItem = 0; - pkmn.rareHeldItem = 0; - } else { - pkmn.guaranteedHeldItem = 0; - pkmn.commonHeldItem = item1; - pkmn.rareHeldItem = item2; - } - pkmn.darkGrassHeldItem = -1; - } - - private String[] readPokemonNames() { - String[] pokeNames = new String[Gen4Constants.pokemonCount + 1]; - List<String> nameList = getStrings(romEntry - .getInt("PokemonNamesTextOffset")); - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - pokeNames[i] = nameList.get(i); - } - return pokeNames; - } - - @Override - protected void savingROM() { - savePokemonStats(); - saveMoves(); - try { - writeARM9(arm9); - } catch (IOException e) { - throw new RuntimeException(e); - } - try { - writeNARC(romEntry.getString("Text"), msgNarc); - } catch (IOException e) { - throw new RuntimeException(e); - } - try { - writeNARC(romEntry.getString("Scripts"), scriptNarc); - } catch (IOException e) { - throw new RuntimeException(e); - } - try { - writeNARC(romEntry.getString("Events"), eventNarc); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void saveMoves() { - for (int i = 1; i <= Gen4Constants.moveCount; i++) { - byte[] data = moveNarc.files.get(i); - writeWord(data, 0, moves[i].effectIndex); - data[3] = (byte) moves[i].power; - data[4] = Gen4Constants.typeToByte(moves[i].type); - int hitratio = (int) Math.round(moves[i].hitratio); - if (hitratio < 0) { - hitratio = 0; - } - if (hitratio > 100) { - hitratio = 100; - } - data[5] = (byte) hitratio; - data[6] = (byte) moves[i].pp; - } - - try { - this.writeNARC(romEntry.getString("MoveData"), moveNarc); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - private void savePokemonStats() { - // Update the "a/an X" list too, if it exists - List<String> namesList = getStrings(romEntry - .getInt("PokemonNamesTextOffset")); - if (romEntry.getString("HasExtraPokemonNames").equalsIgnoreCase("Yes")) { - List<String> namesList2 = getStrings(romEntry - .getInt("PokemonNamesTextOffset") + 1); - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - saveBasicPokeStats(pokes[i], pokeNarc.files.get(i)); - String oldName = namesList.get(i); - namesList.set(i, pokes[i].name); - namesList2.set(i, - namesList2.get(i).replace(oldName, pokes[i].name)); - } - setStrings(romEntry.getInt("PokemonNamesTextOffset") + 1, - namesList2, false); - } else { - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - saveBasicPokeStats(pokes[i], pokeNarc.files.get(i)); - namesList.set(i, pokes[i].name); - } - } - setStrings(romEntry.getInt("PokemonNamesTextOffset"), namesList, false); - - try { - String pstatsnarc = romEntry.getString("PokemonStats"); - this.writeNARC(pstatsnarc, pokeNarc); - } catch (IOException e) { - throw new RuntimeException(e); - } - - writeEvolutions(); - - } - - private void saveBasicPokeStats(Pokemon pkmn, byte[] stats) { - stats[Gen4Constants.bsHPOffset] = (byte) pkmn.hp; - stats[Gen4Constants.bsAttackOffset] = (byte) pkmn.attack; - stats[Gen4Constants.bsDefenseOffset] = (byte) pkmn.defense; - stats[Gen4Constants.bsSpeedOffset] = (byte) pkmn.speed; - stats[Gen4Constants.bsSpAtkOffset] = (byte) pkmn.spatk; - stats[Gen4Constants.bsSpDefOffset] = (byte) pkmn.spdef; - stats[Gen4Constants.bsPrimaryTypeOffset] = Gen4Constants - .typeToByte(pkmn.primaryType); - if (pkmn.secondaryType == null) { - stats[Gen4Constants.bsSecondaryTypeOffset] = stats[Gen4Constants.bsPrimaryTypeOffset]; - } else { - stats[Gen4Constants.bsSecondaryTypeOffset] = Gen4Constants - .typeToByte(pkmn.secondaryType); - } - stats[Gen4Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; - stats[Gen4Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); - - stats[Gen4Constants.bsAbility1Offset] = (byte) pkmn.ability1; - stats[Gen4Constants.bsAbility2Offset] = (byte) pkmn.ability2; - - // Held items - if (pkmn.guaranteedHeldItem > 0) { - writeWord(stats, Gen4Constants.bsCommonHeldItemOffset, - pkmn.guaranteedHeldItem); - writeWord(stats, Gen4Constants.bsRareHeldItemOffset, - pkmn.guaranteedHeldItem); - } else { - writeWord(stats, Gen4Constants.bsCommonHeldItemOffset, - pkmn.commonHeldItem); - writeWord(stats, Gen4Constants.bsRareHeldItemOffset, - pkmn.rareHeldItem); - } - } - - @Override - public List<Pokemon> getPokemon() { - return pokemonList; - } - - @Override - public List<Pokemon> getStarters() { - if (romEntry.romType == Gen4Constants.Type_HGSS) { - List<Integer> tailOffsets = RomFunctions.search(arm9, - Gen4Constants.hgssStarterCodeSuffix); - if (tailOffsets.size() == 1) { - // Found starters - int starterOffset = tailOffsets.get(0) - 13; - int poke1 = readWord(arm9, starterOffset); - int poke2 = readWord(arm9, starterOffset + 4); - int poke3 = readWord(arm9, starterOffset + 8); - return Arrays.asList(pokes[poke1], pokes[poke2], pokes[poke3]); - } else { - return Arrays.asList(pokes[Gen4Constants.chikoritaIndex], - pokes[Gen4Constants.cyndaquilIndex], - pokes[Gen4Constants.totodileIndex]); - } - } else { - try { - byte[] starterData = readOverlay(romEntry - .getInt("StarterPokemonOvlNumber")); - int poke1 = readWord(starterData, - romEntry.getInt("StarterPokemonOffset")); - int poke2 = readWord(starterData, - romEntry.getInt("StarterPokemonOffset") + 4); - int poke3 = readWord(starterData, - romEntry.getInt("StarterPokemonOffset") + 8); - return Arrays.asList(pokes[poke1], pokes[poke2], pokes[poke3]); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - @Override - public boolean setStarters(List<Pokemon> newStarters) { - if (newStarters.size() != 3) { - return false; - } - - if (romEntry.romType == Gen4Constants.Type_HGSS) { - List<Integer> tailOffsets = RomFunctions.search(arm9, - Gen4Constants.hgssStarterCodeSuffix); - if (tailOffsets.size() == 1) { - // Found starters - int starterOffset = tailOffsets.get(0) - 13; - writeWord(arm9, starterOffset, newStarters.get(0).number); - writeWord(arm9, starterOffset + 4, newStarters.get(1).number); - writeWord(arm9, starterOffset + 8, newStarters.get(2).number); - // Go fix the rival scripts, which rely on fixed pokemon numbers - // The logic to be changed each time is roughly: - // Set 0x800C = player starter - // If(0x800C==152) { trainerbattle rival w/ cynda } - // ElseIf(0x800C==155) { trainerbattle rival w/ totodile } - // Else { trainerbattle rival w/ chiko } - // So we basically have to adjust the 152 and the 155. - int[] filesWithRivalScript = Gen4Constants.hgssFilesWithRivalScript; - // below code represents a rival script for sure - // it means: StoreStarter2 0x800C; If 0x800C 152; CheckLR B_!= - // <offset to follow> - byte[] magic = Gen4Constants.hgssRivalScriptMagic; - NARCContents scriptNARC = scriptNarc; - for (int i = 0; i < filesWithRivalScript.length; i++) { - int fileCheck = filesWithRivalScript[i]; - byte[] file = scriptNARC.files.get(fileCheck); - List<Integer> rivalOffsets = RomFunctions.search(file, - magic); - if (rivalOffsets.size() == 1) { - // found, adjust - int baseOffset = rivalOffsets.get(0); - // Replace 152 (chiko) with first starter - writeWord(file, baseOffset + 8, - newStarters.get(0).number); - int jumpAmount = readLong(file, baseOffset + 13); - int secondBase = jumpAmount + baseOffset + 17; - // TODO find out what this constant 0x11 is and remove - // it - if (file[secondBase] != 0x11 - || (file[secondBase + 4] & 0xFF) != Gen4Constants.cyndaquilIndex) { - // This isn't what we were expecting... - } else { - // Replace 155 (cynda) with 2nd starter - writeWord(file, secondBase + 4, - newStarters.get(1).number); - } - } - } - // Fix starter text - List<String> spStrings = getStrings(romEntry - .getInt("StarterScreenTextOffset")); - String[] intros = new String[] { "So, you like", "You’ll take", - "Do you want" }; - for (int i = 0; i < 3; i++) { - Pokemon newStarter = newStarters.get(i); - int color = (i == 0) ? 3 : i; - String newStarterDesc = "Professor Elm: " + intros[i] - + " \\vFF00\\z000" + color + newStarter.name - + "\\vFF00\\z0000,\\nthe " - + newStarter.primaryType.camelCase() - + "-type Pokémon?"; - spStrings.set(i + 1, newStarterDesc); - String altStarterDesc = "\\vFF00\\z000" + color - + newStarter.name + "\\vFF00\\z0000, the " - + newStarter.primaryType.camelCase() - + "-type Pokémon, is\\nin this Poké Ball!"; - spStrings.set(i + 4, altStarterDesc); - } - setStrings(romEntry.getInt("StarterScreenTextOffset"), - spStrings); - return true; - } else { - return false; - } - } else { - try { - byte[] starterData = readOverlay(romEntry - .getInt("StarterPokemonOvlNumber")); - writeWord(starterData, romEntry.getInt("StarterPokemonOffset"), - newStarters.get(0).number); - writeWord(starterData, - romEntry.getInt("StarterPokemonOffset") + 4, - newStarters.get(1).number); - writeWord(starterData, - romEntry.getInt("StarterPokemonOffset") + 8, - newStarters.get(2).number); - writeOverlay(romEntry.getInt("StarterPokemonOvlNumber"), - starterData); - // Patch DPPt-style rival scripts - // these have a series of IfJump commands - // following pokemon IDs - // the jumps either go to trainer battles, or a HoF times - // checker, or the StarterBattle command (Pt only) - // the HoF times checker case is for the Fight Area or Survival - // Area (depending on version). - // the StarterBattle case is for Route 201 in Pt. - int[] filesWithRivalScript = (romEntry.romType == Gen4Constants.Type_Plat) ? Gen4Constants.ptFilesWithRivalScript - : Gen4Constants.dpFilesWithRivalScript; - byte[] magic = Gen4Constants.dpptRivalScriptMagic; - NARCContents scriptNARC = scriptNarc; - for (int i = 0; i < filesWithRivalScript.length; i++) { - int fileCheck = filesWithRivalScript[i]; - byte[] file = scriptNARC.files.get(fileCheck); - List<Integer> rivalOffsets = RomFunctions.search(file, - magic); - if (rivalOffsets.size() > 0) { - for (int baseOffset : rivalOffsets) { - // found, check for trainer battle or HoF - // check at jump - int jumpLoc = baseOffset + magic.length; - int jumpTo = readLong(file, jumpLoc) + jumpLoc + 4; - // TODO find out what these constants are and remove - // them - if (readWord(file, jumpTo) != 0xE5 - && readWord(file, jumpTo) != 0x28F - && (readWord(file, jumpTo) != 0x125 || romEntry.romType != Gen4Constants.Type_Plat)) { - continue; // not a rival script - } - // Replace the two starter-words 387 and 390 - writeWord(file, baseOffset + 0x8, - newStarters.get(0).number); - writeWord(file, baseOffset + 0x15, - newStarters.get(1).number); - } - } - } - // Tag battles with rival or friend - // Have their own script magic - // 2 for Lucas/Dawn (=4 occurrences), 1 or 2 for Barry - byte[] tagBattleMagic = Gen4Constants.dpptTagBattleScriptMagic1; - byte[] tagBattleMagic2 = Gen4Constants.dpptTagBattleScriptMagic2; - int[] filesWithTagBattleScript = (romEntry.romType == Gen4Constants.Type_Plat) ? Gen4Constants.ptFilesWithTagScript - : Gen4Constants.dpFilesWithTagScript; - for (int i = 0; i < filesWithTagBattleScript.length; i++) { - int fileCheck = filesWithTagBattleScript[i]; - byte[] file = scriptNARC.files.get(fileCheck); - List<Integer> tbOffsets = RomFunctions.search(file, - tagBattleMagic); - if (tbOffsets.size() > 0) { - for (int baseOffset : tbOffsets) { - // found, check for second part - int secondPartStart = baseOffset - + tagBattleMagic.length + 2; - if (secondPartStart + tagBattleMagic2.length > file.length) { - continue; // match failed - } - boolean valid = true; - for (int spo = 0; spo < tagBattleMagic2.length; spo++) { - if (file[secondPartStart + spo] != tagBattleMagic2[spo]) { - valid = false; - break; - } - } - if (!valid) { - continue; - } - // Make sure the jump following the second - // part jumps to a <return> command - int jumpLoc = secondPartStart - + tagBattleMagic2.length; - int jumpTo = readLong(file, jumpLoc) + jumpLoc + 4; - // TODO find out what this constant is and remove it - if (readWord(file, jumpTo) != 0x1B) { - continue; // not a tag battle script - } - // Replace the two starter-words - if (readWord(file, baseOffset + 0x21) == Gen4Constants.turtwigIndex) { - // first starter - writeWord(file, baseOffset + 0x21, - newStarters.get(0).number); - } else { - // third starter - writeWord(file, baseOffset + 0x21, - newStarters.get(2).number); - } - // second starter - writeWord(file, baseOffset + 0xE, - newStarters.get(1).number); - } - } - } - // Fix starter script text - // The starter picking screen - List<String> spStrings = getStrings(romEntry - .getInt("StarterScreenTextOffset")); - // Get pokedex info - List<String> pokedexSpeciesStrings = getStrings(romEntry - .getInt("PokedexSpeciesTextOffset")); - for (int i = 0; i < 3; i++) { - Pokemon newStarter = newStarters.get(i); - int color = (i == 0) ? 3 : i; - String newStarterDesc = "\\vFF00\\z000" + color - + pokedexSpeciesStrings.get(newStarter.number) - + " " + newStarter.name - + "\\vFF00\\z0000!\\nWill you take this Pokémon?"; - spStrings.set(i + 1, newStarterDesc); - } - // rewrite starter picking screen - setStrings(romEntry.getInt("StarterScreenTextOffset"), - spStrings); - if (romEntry.romType == Gen4Constants.Type_DP) { - // what rival says after we get the Pokemon - List<String> lakeStrings = getStrings(romEntry - .getInt("StarterLocationTextOffset")); - lakeStrings - .set(Gen4Constants.dpStarterStringIndex, - "\\v0103\\z0000: Fwaaah!\\nYour Pokémon totally rocked!\\pBut mine was way tougher\\nthan yours!\\p...They were other people’s\\nPokémon, though...\\pBut we had to use them...\\nThey won’t mind, will they?\\p"); - setStrings(romEntry.getInt("StarterLocationTextOffset"), - lakeStrings); - } else { - // what rival says after we get the Pokemon - List<String> r201Strings = getStrings(romEntry - .getInt("StarterLocationTextOffset")); - r201Strings - .set(Gen4Constants.ptStarterStringIndex, - "\\v0103\\z0000\\z0000: Then, I choose you!\\nI’m picking this one!\\p"); - setStrings(romEntry.getInt("StarterLocationTextOffset"), - r201Strings); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return true; - } - } - - @Override - public List<Integer> getStarterHeldItems() { - // do nothing - return new ArrayList<Integer>(); - } - - @Override - public void setStarterHeldItems(List<Integer> items) { - // do nothing - } - - @Override - public void shufflePokemonStats() { - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - pokes[i].shuffleStats(this.random); - } - } - - @Override - public List<Move> getMoves() { - return Arrays.asList(moves); - } - - @Override - public List<EncounterSet> getEncounters(boolean useTimeOfDay) { - if (!loadedWildMapNames) { - loadWildMapNames(); - } - - try { - if (romEntry.romType == Gen4Constants.Type_HGSS) { - return getEncountersHGSS(useTimeOfDay); - } else { - return getEncountersDPPt(useTimeOfDay); - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - private List<EncounterSet> getEncountersDPPt(boolean useTimeOfDay) - throws IOException { - // Determine file to use - String encountersFile = romEntry.getString("WildPokemon"); - - NARCContents encounterData = readNARC(encountersFile); - List<EncounterSet> encounters = new ArrayList<EncounterSet>(); - // Credit for - // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-sinnoh.py - // for the structure for this. - int c = -1; - for (byte[] b : encounterData.files) { - c++; - if (!wildMapNames.containsKey(c)) { - wildMapNames.put(c, "? Unknown ?"); - } - String mapName = wildMapNames.get(c); - int grassRate = readLong(b, 0); - if (grassRate != 0) { - // up to 4 - List<Encounter> grassEncounters = readEncountersDPPt(b, 4, 12); - EncounterSet grass = new EncounterSet(); - grass.displayName = mapName + " Grass/Cave"; - grass.encounters = grassEncounters; - grass.rate = grassRate; - grass.offset = c; - encounters.add(grass); - - // Time of day replacements? - if (useTimeOfDay) { - for (int i = 0; i < 4; i++) { - int pknum = readLong(b, 108 + 4 * i); - if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { - Pokemon pk = pokes[pknum]; - Encounter enc = new Encounter(); - enc.level = grassEncounters - .get(Gen4Constants.dpptAlternateSlots[i + 2]).level; - enc.pokemon = pk; - grassEncounters.add(enc); - } - } - } - // (if useTimeOfDay is off, just override them later) - - // Other conditional replacements (swarm, radar, GBA) - EncounterSet conds = new EncounterSet(); - conds.displayName = mapName + " Swarm/Radar/GBA"; - conds.rate = grassRate; - conds.offset = c; - for (int i = 0; i < 20; i++) { - if (i >= 2 && i <= 5) { - // Time of day slot, handled already - continue; - } - int offs = 100 + i * 4 + (i >= 10 ? 24 : 0); - int pknum = readLong(b, offs); - if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { - Pokemon pk = pokes[pknum]; - Encounter enc = new Encounter(); - enc.level = grassEncounters - .get(Gen4Constants.dpptAlternateSlots[i]).level; - enc.pokemon = pk; - conds.encounters.add(enc); - } - } - if (conds.encounters.size() > 0) { - encounters.add(conds); - } - } - - // up to 204, 5 sets of "sea" encounters to go - int offset = 204; - for (int i = 0; i < 5; i++) { - int rate = readLong(b, offset); - offset += 4; - List<Encounter> encountersHere = readSeaEncountersDPPt(b, - offset, 5); - offset += 40; - if (rate == 0 || i == 1) { - continue; - } - EncounterSet other = new EncounterSet(); - other.displayName = mapName + " " - + Gen4Constants.dpptWaterSlotSetNames[i]; - other.offset = c; - other.encounters = encountersHere; - other.rate = rate; - encounters.add(other); - } - } - return encounters; - } - - private List<Encounter> readEncountersDPPt(byte[] data, int offset, - int amount) { - List<Encounter> encounters = new ArrayList<Encounter>(); - for (int i = 0; i < amount; i++) { - int level = readLong(data, offset + i * 8); - int pokemon = readLong(data, offset + 4 + i * 8); - Encounter enc = new Encounter(); - enc.level = level; - enc.pokemon = pokes[pokemon]; - encounters.add(enc); - } - return encounters; - } - - private List<Encounter> readSeaEncountersDPPt(byte[] data, int offset, - int amount) { - List<Encounter> encounters = new ArrayList<Encounter>(); - for (int i = 0; i < amount; i++) { - int level = readLong(data, offset + i * 8); - int pokemon = readLong(data, offset + 4 + i * 8); - Encounter enc = new Encounter(); - enc.level = level >> 8; - enc.maxLevel = level & 0xFF; - enc.pokemon = pokes[pokemon]; - encounters.add(enc); - } - return encounters; - } - - private List<EncounterSet> getEncountersHGSS(boolean useTimeOfDay) - throws IOException { - String encountersFile = romEntry.getString("WildPokemon"); - NARCContents encounterData = readNARC(encountersFile); - List<EncounterSet> encounters = new ArrayList<EncounterSet>(); - // Credit for - // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-johto.py - // for the structure for this. - int[] amounts = new int[] { 0, 5, 2, 5, 5, 5 }; - int c = -1; - for (byte[] b : encounterData.files) { - c++; - if (!wildMapNames.containsKey(c)) { - wildMapNames.put(c, "? Unknown ?"); - } - String mapName = wildMapNames.get(c); - int[] rates = new int[6]; - rates[0] = b[0] & 0xFF; - rates[1] = b[1] & 0xFF; - rates[2] = b[2] & 0xFF; - rates[3] = b[3] & 0xFF; - rates[4] = b[4] & 0xFF; - rates[5] = b[5] & 0xFF; - // Up to 8 after the rates - // Grass has to be handled on its own because the levels - // are reused for every time of day - int[] grassLevels = new int[12]; - for (int i = 0; i < 12; i++) { - grassLevels[i] = b[8 + i] & 0xFF; - } - // Up to 20 now (12 for levels) - Pokemon[][] grassPokes = new Pokemon[3][12]; - grassPokes[0] = readPokemonHGSS(b, 20, 12); - grassPokes[1] = readPokemonHGSS(b, 44, 12); - grassPokes[2] = readPokemonHGSS(b, 68, 12); - // Up to 92 now (12*2*3 for pokemon) - if (rates[0] != 0) { - if (!useTimeOfDay) { - // Just write "day" encounters - List<Encounter> grassEncounters = stitchEncsToLevels( - grassPokes[1], grassLevels); - EncounterSet grass = new EncounterSet(); - grass.encounters = grassEncounters; - grass.rate = rates[0]; - grass.displayName = mapName + " Grass/Cave"; - encounters.add(grass); - } else { - for (int i = 0; i < 3; i++) { - EncounterSet grass = new EncounterSet(); - grass.encounters = stitchEncsToLevels(grassPokes[i], - grassLevels); - grass.rate = rates[0]; - grass.displayName = mapName + " " - + Gen4Constants.hgssTimeOfDayNames[i] - + " Grass/Cave"; - encounters.add(grass); - } - } - } - - // Hoenn/Sinnoh Radio - EncounterSet radio = readOptionalEncountersHGSS(b, 92, 4); - radio.displayName = mapName + " Hoenn/Sinnoh Radio"; - if (radio.encounters.size() > 0) { - encounters.add(radio); - } - - // Up to 100 now... 2*2*2 for radio pokemon - int offset = 100; - for (int i = 1; i < 6; i++) { - List<Encounter> encountersHere = readSeaEncountersHGSS(b, - offset, amounts[i]); - offset += 4 * amounts[i]; - if (rates[i] != 0) { - // Valid area. - EncounterSet other = new EncounterSet(); - other.encounters = encountersHere; - other.displayName = mapName + " " - + Gen4Constants.hgssNonGrassSetNames[i]; - other.rate = rates[i]; - encounters.add(other); - } - } - - // Swarms - EncounterSet swarms = readOptionalEncountersHGSS(b, offset, 4); - swarms.displayName = mapName + " Swarms"; - if (swarms.encounters.size() > 0) { - encounters.add(swarms); - } - } - return encounters; - } - - private EncounterSet readOptionalEncountersHGSS(byte[] data, int offset, - int amount) { - EncounterSet es = new EncounterSet(); - es.rate = 1; - for (int i = 0; i < amount; i++) { - int pokemon = readWord(data, offset + i * 2); - if (pokemon != 0) { - Encounter e = new Encounter(); - e.level = 1; - e.pokemon = pokes[pokemon]; - es.encounters.add(e); - } - } - return es; - } - - private Pokemon[] readPokemonHGSS(byte[] data, int offset, int amount) { - Pokemon[] pokesHere = new Pokemon[amount]; - for (int i = 0; i < amount; i++) { - pokesHere[i] = pokes[readWord(data, offset + i * 2)]; - } - return pokesHere; - } - - private List<Encounter> readSeaEncountersHGSS(byte[] data, int offset, - int amount) { - List<Encounter> encounters = new ArrayList<Encounter>(); - for (int i = 0; i < amount; i++) { - int level = readWord(data, offset + i * 4); - int pokemon = readWord(data, offset + 2 + i * 4); - Encounter enc = new Encounter(); - enc.level = level & 0xFF; - enc.maxLevel = level >> 8; - enc.pokemon = pokes[pokemon]; - encounters.add(enc); - } - return encounters; - } - - @Override - public void setEncounters(boolean useTimeOfDay, - List<EncounterSet> encounters) { - try { - if (romEntry.romType == Gen4Constants.Type_HGSS) { - setEncountersHGSS(useTimeOfDay, encounters); - } else { - setEncountersDPPt(useTimeOfDay, encounters); - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - private void setEncountersDPPt(boolean useTimeOfDay, - List<EncounterSet> encounterList) throws IOException { - // Determine file to use - String encountersFile = romEntry.getString("WildPokemon"); - NARCContents encounterData = readNARC(encountersFile); - Iterator<EncounterSet> encounters = encounterList.iterator(); - // Credit for - // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-sinnoh.py - // for the structure for this. - for (byte[] b : encounterData.files) { - int grassRate = readLong(b, 0); - if (grassRate != 0) { - // grass encounters are a-go - EncounterSet grass = encounters.next(); - writeEncountersDPPt(b, 4, grass.encounters, 12); - - // Time of day encounters? - int todEncounterSlot = 12; - for (int i = 0; i < 4; i++) { - int pknum = readLong(b, 108 + 4 * i); - if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { - // Valid time of day slot - if (useTimeOfDay) { - // Get custom randomized encounter - Pokemon pk = grass.encounters - .get(todEncounterSlot++).pokemon; - writeLong(b, 108 + 4 * i, pk.number); - } else { - // Copy the original slot's randomized encounter - Pokemon pk = grass.encounters - .get(Gen4Constants.dpptAlternateSlots[i + 2]).pokemon; - writeLong(b, 108 + 4 * i, pk.number); - } - } - } - - // Other conditional encounters? - Iterator<Encounter> condEncounters = null; - for (int i = 0; i < 20; i++) { - if (i >= 2 && i <= 5) { - // Time of day slot, handled already - continue; - } - int offs = 100 + i * 4 + (i >= 10 ? 24 : 0); - int pknum = readLong(b, offs); - if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { - // This slot is used, grab a replacement. - if (condEncounters == null) { - // Fetch the set of conditional encounters for this - // area now that we know it's necessary and exists. - condEncounters = encounters.next().encounters - .iterator(); - } - Pokemon pk = condEncounters.next().pokemon; - writeLong(b, offs, pk.number); - } - } - } - // up to 204, 5 special ones to go - // This is for surf, filler, old rod, good rod, super rod - // so we skip index 1 (filler) - int offset = 204; - for (int i = 0; i < 5; i++) { - int rate = readLong(b, offset); - offset += 4; - if (rate == 0 || i == 1) { - offset += 40; - continue; - } - - EncounterSet other = encounters.next(); - writeSeaEncountersDPPt(b, offset, other.encounters); - offset += 40; - } - } - - // Save - writeNARC(encountersFile, encounterData); - - } - - private void writeEncountersDPPt(byte[] data, int offset, - List<Encounter> encounters, int enclength) { - for (int i = 0; i < enclength; i++) { - Encounter enc = encounters.get(i); - writeLong(data, offset + i * 8, enc.level); - writeLong(data, offset + i * 8 + 4, enc.pokemon.number); - } - } - - private void writeSeaEncountersDPPt(byte[] data, int offset, - List<Encounter> encounters) { - int enclength = encounters.size(); - for (int i = 0; i < enclength; i++) { - Encounter enc = encounters.get(i); - writeLong(data, offset + i * 8, (enc.level << 8) + enc.maxLevel); - writeLong(data, offset + i * 8 + 4, enc.pokemon.number); - } - } - - private void setEncountersHGSS(boolean useTimeOfDay, - List<EncounterSet> encounterList) throws IOException { - String encountersFile = romEntry.getString("WildPokemon"); - NARCContents encounterData = readNARC(encountersFile); - Iterator<EncounterSet> encounters = encounterList.iterator(); - // Credit for - // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-johto.py - // for the structure for this. - int[] amounts = new int[] { 0, 5, 2, 5, 5, 5 }; - for (byte[] b : encounterData.files) { - int[] rates = new int[6]; - rates[0] = b[0] & 0xFF; - rates[1] = b[1] & 0xFF; - rates[2] = b[2] & 0xFF; - rates[3] = b[3] & 0xFF; - rates[4] = b[4] & 0xFF; - rates[5] = b[5] & 0xFF; - // Up to 20 after the rates & levels - // Grass has to be handled on its own because the levels - // are reused for every time of day - if (rates[0] != 0) { - if (!useTimeOfDay) { - // Get a single set of encounters... - // Write the encounters we get 3x for morning, day, night - EncounterSet grass = encounters.next(); - writePokemonHGSS(b, 20, grass.encounters); - writePokemonHGSS(b, 44, grass.encounters); - writePokemonHGSS(b, 68, grass.encounters); - } else { - for (int i = 0; i < 3; i++) { - EncounterSet grass = encounters.next(); - writePokemonHGSS(b, 20 + i * 24, grass.encounters); - } - } - } - - // Write radio pokemon - writeOptionalEncountersHGSS(b, 92, 4, encounters); - - // Up to 100 now... 2*2*2 for radio pokemon - // Write rock smash, surf, et al - int offset = 100; - for (int i = 1; i < 6; i++) { - if (rates[i] != 0) { - // Valid area. - EncounterSet other = encounters.next(); - writeSeaEncountersHGSS(b, offset, other.encounters); - } - offset += 4 * amounts[i]; - } - - // Write swarm pokemon - writeOptionalEncountersHGSS(b, offset, 4, encounters); - } - - // Save - writeNARC(encountersFile, encounterData); - - } - - private void writeOptionalEncountersHGSS(byte[] data, int offset, - int amount, Iterator<EncounterSet> encounters) { - Iterator<Encounter> eIter = null; - for (int i = 0; i < amount; i++) { - int origPokemon = readWord(data, offset + i * 2); - if (origPokemon != 0) { - // Need an encounter set, yes. - if (eIter == null) { - eIter = encounters.next().encounters.iterator(); - } - Encounter here = eIter.next(); - writeWord(data, offset + i * 2, here.pokemon.number); - } - } - - } - - private void writePokemonHGSS(byte[] data, int offset, - List<Encounter> encounters) { - int enclength = encounters.size(); - for (int i = 0; i < enclength; i++) { - writeWord(data, offset + i * 2, encounters.get(i).pokemon.number); - } - - } - - private void writeSeaEncountersHGSS(byte[] data, int offset, - List<Encounter> encounters) { - int enclength = encounters.size(); - for (int i = 0; i < enclength; i++) { - Encounter enc = encounters.get(i); - data[offset + i * 4] = (byte) enc.level; - data[offset + i * 4 + 1] = (byte) enc.maxLevel; - writeWord(data, offset + i * 4 + 2, enc.pokemon.number); - } - - } - - private List<Encounter> stitchEncsToLevels(Pokemon[] pokemon, int[] levels) { - List<Encounter> encounters = new ArrayList<Encounter>(); - for (int i = 0; i < pokemon.length; i++) { - Encounter enc = new Encounter(); - enc.level = levels[i]; - enc.pokemon = pokemon[i]; - encounters.add(enc); - } - return encounters; - } - - private void loadWildMapNames() { - try { - wildMapNames = new HashMap<Integer, String>(); - byte[] internalNames = this.readFile(romEntry - .getString("MapTableFile")); - int numMapHeaders = internalNames.length / 16; - int baseMHOffset = romEntry.getInt("MapTableARM9Offset"); - List<String> allMapNames = getStrings(romEntry - .getInt("MapNamesTextOffset")); - int mapNameIndexSize = romEntry.getInt("MapTableNameIndexSize"); - for (int map = 0; map < numMapHeaders; map++) { - int baseOffset = baseMHOffset + map * 24; - int mapNameIndex = (mapNameIndexSize == 2) ? readWord(arm9, - baseOffset + 18) : (arm9[baseOffset + 18] & 0xFF); - String mapName = allMapNames.get(mapNameIndex); - if (romEntry.romType == Gen4Constants.Type_HGSS) { - int wildSet = arm9[baseOffset] & 0xFF; - if (wildSet != 255) { - wildMapNames.put(wildSet, mapName); - } - } else { - int wildSet = readWord(arm9, baseOffset + 14); - if (wildSet != 65535) { - wildMapNames.put(wildSet, mapName); - } - } - } - loadedWildMapNames = true; - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - @Override - public List<Trainer> getTrainers() { - List<Trainer> allTrainers = new ArrayList<Trainer>(); - try { - NARCContents trainers = this.readNARC(romEntry - .getString("TrainerData")); - NARCContents trpokes = this.readNARC(romEntry - .getString("TrainerPokemon")); - List<String> tclasses = this.getTrainerClassNames(); - List<String> tnames = this.getTrainerNames(); - int trainernum = trainers.files.size(); - for (int i = 1; i < trainernum; i++) { - byte[] trainer = trainers.files.get(i); - byte[] trpoke = trpokes.files.get(i); - Trainer tr = new Trainer(); - tr.poketype = trainer[0] & 0xFF; - tr.trainerclass = trainer[1] & 0xFF; - tr.offset = i; - int numPokes = trainer[3] & 0xFF; - int pokeOffs = 0; - tr.fullDisplayName = tclasses.get(tr.trainerclass) + " " - + tnames.get(i - 1); - // printBA(trpoke); - for (int poke = 0; poke < numPokes; poke++) { - int ailevel = trpoke[pokeOffs] & 0xFF; - int level = trpoke[pokeOffs + 2] & 0xFF; - int species = (trpoke[pokeOffs + 4] & 0xFF) - + ((trpoke[pokeOffs + 5] & 0x01) << 8); - // int formnum = (trpoke[pokeOffs + 5] >> 2); - TrainerPokemon tpk = new TrainerPokemon(); - tpk.level = level; - tpk.pokemon = pokes[species]; - tpk.AILevel = ailevel; - tpk.ability = trpoke[pokeOffs + 1] & 0xFF; - pokeOffs += 6; - if (tr.poketype >= 2) { - int heldItem = readWord(trpoke, pokeOffs); - tpk.heldItem = heldItem; - pokeOffs += 2; - } - if (tr.poketype % 2 == 1) { - int attack1 = readWord(trpoke, pokeOffs); - int attack2 = readWord(trpoke, pokeOffs + 2); - int attack3 = readWord(trpoke, pokeOffs + 4); - int attack4 = readWord(trpoke, pokeOffs + 6); - tpk.move1 = attack1; - tpk.move2 = attack2; - tpk.move3 = attack3; - tpk.move4 = attack4; - pokeOffs += 8; - } - // Plat/HGSS have another random pokeOffs +=2 here. - if (romEntry.romType != Gen4Constants.Type_DP) { - pokeOffs += 2; - } - tr.pokemon.add(tpk); - } - allTrainers.add(tr); - } - if (romEntry.romType == Gen4Constants.Type_DP) { - Gen4Constants.tagTrainersDP(allTrainers); - } else if (romEntry.romType == Gen4Constants.Type_Plat) { - Gen4Constants.tagTrainersPt(allTrainers); - } else { - Gen4Constants.tagTrainersHGSS(allTrainers); - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - return allTrainers; - } - - @Override - public void setTrainers(List<Trainer> trainerData) { - Iterator<Trainer> allTrainers = trainerData.iterator(); - try { - NARCContents trainers = this.readNARC(romEntry - .getString("TrainerData")); - NARCContents trpokes = new NARCContents(); - // empty entry - trpokes.files.add(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }); - int trainernum = trainers.files.size(); - for (int i = 1; i < trainernum; i++) { - byte[] trainer = trainers.files.get(i); - Trainer tr = allTrainers.next(); - tr.poketype = 0; // write as type 0 for no item/moves - trainer[0] = (byte) tr.poketype; - int numPokes = tr.pokemon.size(); - trainer[3] = (byte) numPokes; - - int bytesNeeded = 6 * numPokes; - if (romEntry.romType != Gen4Constants.Type_DP) { - bytesNeeded += 2 * numPokes; - } - if (tr.poketype % 2 == 1) { - bytesNeeded += 8 * numPokes; - } - if (tr.poketype >= 2) { - bytesNeeded += 2 * numPokes; - } - byte[] trpoke = new byte[bytesNeeded]; - int pokeOffs = 0; - Iterator<TrainerPokemon> tpokes = tr.pokemon.iterator(); - for (int poke = 0; poke < numPokes; poke++) { - TrainerPokemon tpk = tpokes.next(); - writeWord(trpoke, pokeOffs, tpk.AILevel); - writeWord(trpoke, pokeOffs + 2, tpk.level); - writeWord(trpoke, pokeOffs + 4, tpk.pokemon.number); - pokeOffs += 6; - if (tr.poketype >= 2) { - writeWord(trpoke, pokeOffs, tpk.heldItem); - pokeOffs += 2; - } - if (tr.poketype % 2 == 1) { - writeWord(trpoke, pokeOffs, tpk.move1); - writeWord(trpoke, pokeOffs + 2, tpk.move2); - writeWord(trpoke, pokeOffs + 4, tpk.move3); - writeWord(trpoke, pokeOffs + 6, tpk.move4); - pokeOffs += 8; - } - // Plat/HGSS have another random pokeOffs +=2 here. - if (romEntry.romType != Gen4Constants.Type_DP) { - pokeOffs += 2; - } - } - trpokes.files.add(trpoke); - } - this.writeNARC(romEntry.getString("TrainerData"), trainers); - this.writeNARC(romEntry.getString("TrainerPokemon"), trpokes); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - - } - - @Override - public Map<Pokemon, List<MoveLearnt>> getMovesLearnt() { - Map<Pokemon, List<MoveLearnt>> movesets = new TreeMap<Pokemon, List<MoveLearnt>>(); - try { - NARCContents movesLearnt = this.readNARC(romEntry - .getString("PokemonMovesets")); - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - Pokemon pkmn = pokes[i]; - byte[] rom = movesLearnt.files.get(i); - int moveDataLoc = 0; - List<MoveLearnt> learnt = new ArrayList<MoveLearnt>(); - while ((rom[moveDataLoc] & 0xFF) != 0xFF - || (rom[moveDataLoc + 1] & 0xFF) != 0xFF) { - int move = (rom[moveDataLoc] & 0xFF); - int level = (rom[moveDataLoc + 1] & 0xFE) >> 1; - if ((rom[moveDataLoc + 1] & 0x01) == 0x01) { - move += 256; - } - MoveLearnt ml = new MoveLearnt(); - ml.level = level; - ml.move = move; - learnt.add(ml); - moveDataLoc += 2; - } - movesets.put(pkmn, learnt); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return movesets; - } - - @Override - public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets) { - int[] extraLearnSets = new int[] { 7, 13, 13 }; - // Build up a new NARC - NARCContents movesLearnt = new NARCContents(); - // The blank moveset - byte[] blankSet = new byte[] { (byte) 0xFF, (byte) 0xFF, 0, 0 }; - movesLearnt.files.add(blankSet); - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - Pokemon pkmn = pokes[i]; - List<MoveLearnt> learnt = movesets.get(pkmn); - int sizeNeeded = learnt.size() * 2 + 2; - if ((sizeNeeded % 4) != 0) { - sizeNeeded += 2; - } - byte[] moveset = new byte[sizeNeeded]; - int j = 0; - for (; j < learnt.size(); j++) { - MoveLearnt ml = learnt.get(j); - moveset[j * 2] = (byte) (ml.move & 0xFF); - int levelPart = (ml.level << 1) & 0xFE; - if (ml.move > 255) { - levelPart++; - } - moveset[j * 2 + 1] = (byte) levelPart; - } - moveset[j * 2] = (byte) 0xFF; - moveset[j * 2 + 1] = (byte) 0xFF; - movesLearnt.files.add(moveset); - } - for (int j = 0; j < extraLearnSets[romEntry.romType]; j++) { - movesLearnt.files.add(blankSet); - } - // Save - try { - this.writeNARC(romEntry.getString("PokemonMovesets"), movesLearnt); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - private static class StaticPokemon { - private int[] files; - private int[] offsets; - - public Pokemon getPokemon(Gen4RomHandler parent, NARCContents scriptNARC) { - return parent.pokes[parent.readWord(scriptNARC.files.get(files[0]), - offsets[0])]; - } - - public void setPokemon(Gen4RomHandler parent, NARCContents scriptNARC, - Pokemon pkmn) { - int value = pkmn.number; - for (int i = 0; i < offsets.length; i++) { - byte[] file = scriptNARC.files.get(files[i]); - parent.writeWord(file, offsets[i], value); - } - } - } - - @Override - public List<Pokemon> getStaticPokemon() { - List<Pokemon> sp = new ArrayList<Pokemon>(); - if (!romEntry.staticPokemonSupport) { - return sp; - } - try { - NARCContents scriptNARC = scriptNarc; - for (StaticPokemon statP : romEntry.staticPokemon) { - sp.add(statP.getPokemon(this, scriptNARC)); - } - if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { - NARCContents tradeNARC = this.readNARC(romEntry - .getString("InGameTrades")); - int[] trades = romEntry.arrayEntries.get("StaticPokemonTrades"); - for (int tradeNum : trades) { - sp.add(pokes[readLong(tradeNARC.files.get(tradeNum), 0)]); - } - } - if (romEntry.getInt("MysteryEggOffset") > 0) { - byte[] ovOverlay = readOverlay(romEntry - .getInt("MoveTutorMovesOvlNumber")); - sp.add(pokes[ovOverlay[romEntry.getInt("MysteryEggOffset")] & 0xFF]); - } - if (romEntry.getInt("FossilTableOffset") > 0) { - byte[] ftData = arm9; - int baseOffset = romEntry.getInt("FossilTableOffset"); - if (romEntry.romType == Gen4Constants.Type_HGSS) { - ftData = readOverlay(romEntry - .getInt("FossilTableOvlNumber")); - } - // read the 7 Fossil Pokemon - for (int f = 0; f < Gen4Constants.fossilCount; f++) { - sp.add(pokes[readWord(ftData, baseOffset + 2 + f * 4)]); - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return sp; - } - - @Override - public boolean setStaticPokemon(List<Pokemon> staticPokemon) { - if (!romEntry.staticPokemonSupport) { - return false; - } - int sptsize = romEntry.arrayEntries.containsKey("StaticPokemonTrades") ? romEntry.arrayEntries - .get("StaticPokemonTrades").length : 0; - int meggsize = romEntry.getInt("MysteryEggOffset") > 0 ? 1 : 0; - int fossilsize = romEntry.getInt("FossilTableOffset") > 0 ? 7 : 0; - if (staticPokemon.size() != romEntry.staticPokemon.size() + sptsize - + meggsize + fossilsize) { - return false; - } - try { - Iterator<Pokemon> statics = staticPokemon.iterator(); - NARCContents scriptNARC = scriptNarc; - for (StaticPokemon statP : romEntry.staticPokemon) { - statP.setPokemon(this, scriptNARC, statics.next()); - } - if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { - NARCContents tradeNARC = this.readNARC(romEntry - .getString("InGameTrades")); - int[] trades = romEntry.arrayEntries.get("StaticPokemonTrades"); - for (int tradeNum : trades) { - Pokemon thisTrade = statics.next(); - List<Integer> possibleAbilities = new ArrayList<Integer>(); - possibleAbilities.add(thisTrade.ability1); - if (thisTrade.ability2 > 0) { - possibleAbilities.add(thisTrade.ability2); - } - if (thisTrade.ability3 > 0) { - possibleAbilities.add(thisTrade.ability3); - } - // Write species and ability - writeLong(tradeNARC.files.get(tradeNum), 0, - thisTrade.number); - writeLong(tradeNARC.files.get(tradeNum), 0x1C, - possibleAbilities.get(this.random - .nextInt(possibleAbilities.size()))); - } - writeNARC(romEntry.getString("InGameTrades"), tradeNARC); - } - if (romEntry.getInt("MysteryEggOffset") > 0) { - // Same overlay as MT moves - // Truncate the pokemon# to 1byte, unless it's 0 - int pokenum = statics.next().number; - if (pokenum > 255) { - pokenum = this.random.nextInt(255) + 1; - } - byte[] ovOverlay = readOverlay(romEntry - .getInt("MoveTutorMovesOvlNumber")); - ovOverlay[romEntry.getInt("MysteryEggOffset")] = (byte) pokenum; - writeOverlay(romEntry.getInt("MoveTutorMovesOvlNumber"), - ovOverlay); - } - if (romEntry.getInt("FossilTableOffset") > 0) { - int baseOffset = romEntry.getInt("FossilTableOffset"); - if (romEntry.romType == Gen4Constants.Type_HGSS) { - byte[] ftData = readOverlay(romEntry - .getInt("FossilTableOvlNumber")); - for (int f = 0; f < Gen4Constants.fossilCount; f++) { - int pokenum = statics.next().number; - writeWord(ftData, baseOffset + 2 + f * 4, pokenum); - } - writeOverlay(romEntry.getInt("FossilTableOvlNumber"), - ftData); - } else { - // write to arm9 - for (int f = 0; f < Gen4Constants.fossilCount; f++) { - int pokenum = statics.next().number; - writeWord(arm9, baseOffset + 2 + f * 4, pokenum); - } - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return true; - } - - @Override - public List<Integer> getTMMoves() { - String tmDataPrefix; - if (romEntry.romType == Gen4Constants.Type_DP - || romEntry.romType == Gen4Constants.Type_Plat) { - tmDataPrefix = Gen4Constants.dpptTMDataPrefix; - } else { - tmDataPrefix = Gen4Constants.hgssTMDataPrefix; - } - int offset = find(arm9, tmDataPrefix); - if (offset > 0) { - offset += tmDataPrefix.length() / 2; // because it was a prefix - List<Integer> tms = new ArrayList<Integer>(); - for (int i = 0; i < Gen4Constants.tmCount; i++) { - tms.add(readWord(arm9, offset + i * 2)); - } - return tms; - } else { - return null; - } - } - - @Override - public List<Integer> getHMMoves() { - String tmDataPrefix; - if (romEntry.romType == Gen4Constants.Type_DP - || romEntry.romType == Gen4Constants.Type_Plat) { - tmDataPrefix = Gen4Constants.dpptTMDataPrefix; - } else { - tmDataPrefix = Gen4Constants.hgssTMDataPrefix; - } - int offset = find(arm9, tmDataPrefix); - if (offset > 0) { - offset += tmDataPrefix.length() / 2; // because it was a prefix - offset += Gen4Constants.tmCount * 2; // TM data - List<Integer> hms = new ArrayList<Integer>(); - for (int i = 0; i < Gen4Constants.hmCount; i++) { - hms.add(readWord(arm9, offset + i * 2)); - } - return hms; - } else { - return null; - } - } - - @Override - public void setTMMoves(List<Integer> moveIndexes) { - String tmDataPrefix; - if (romEntry.romType == Gen4Constants.Type_DP - || romEntry.romType == Gen4Constants.Type_Plat) { - tmDataPrefix = Gen4Constants.dpptTMDataPrefix; - } else { - tmDataPrefix = Gen4Constants.hgssTMDataPrefix; - } - int offset = find(arm9, tmDataPrefix); - if (offset > 0) { - offset += tmDataPrefix.length() / 2; // because it was a prefix - for (int i = 0; i < Gen4Constants.tmCount; i++) { - writeWord(arm9, offset + i * 2, moveIndexes.get(i)); - } - - // Update TM item descriptions - List<String> itemDescriptions = getStrings(romEntry - .getInt("ItemDescriptionsTextOffset")); - List<String> moveDescriptions = getStrings(romEntry - .getInt("MoveDescriptionsTextOffset")); - // TM01 is item 328 and so on - for (int i = 0; i < Gen4Constants.tmCount; i++) { - // Rewrite 5-line move descs into 3-line item descs - itemDescriptions.set(i + Gen4Constants.tmItemOffset, - RomFunctions.rewriteDescriptionForNewLineSize( - moveDescriptions.get(moveIndexes.get(i)), - "\\n", Gen4Constants.textCharsPerLine, ssd)); - } - // Save the new item descriptions - setStrings(romEntry.getInt("ItemDescriptionsTextOffset"), - itemDescriptions); - // Palettes update - String baseOfPalettes = Gen4Constants.pthgssItemPalettesPrefix; - if (romEntry.romType == Gen4Constants.Type_DP) { - baseOfPalettes = Gen4Constants.dpItemPalettesPrefix; - } - int offsPals = find(arm9, baseOfPalettes); - if (offsPals > 0) { - // Write pals - for (int i = 0; i < Gen4Constants.tmCount; i++) { - Move m = this.moves[moveIndexes.get(i)]; - int pal = this.typeTMPaletteNumber(m.type); - writeWord(arm9, offsPals + i * 8 + 2, pal); - } - } - // if we can't update the palettes its not a big deal... - } else { - } - } - - private static RomFunctions.StringSizeDeterminer ssd = new RomFunctions.StringLengthSD(); - - @Override - public int getTMCount() { - return Gen4Constants.tmCount; - } - - @Override - public int getHMCount() { - return Gen4Constants.hmCount; - } - - @Override - public Map<Pokemon, boolean[]> getTMHMCompatibility() { - Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - byte[] data = pokeNarc.files.get(i); - Pokemon pkmn = pokes[i]; - boolean[] flags = new boolean[Gen4Constants.tmCount - + Gen4Constants.hmCount + 1]; - for (int j = 0; j < 13; j++) { - readByteIntoFlags(data, flags, j * 8 + 1, - Gen4Constants.bsTMHMCompatOffset + j); - } - compat.put(pkmn, flags); - } - return compat; - } - - @Override - public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData) { - for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { - Pokemon pkmn = compatEntry.getKey(); - boolean[] flags = compatEntry.getValue(); - byte[] data = pokeNarc.files.get(pkmn.number); - for (int j = 0; j < 13; j++) { - data[Gen4Constants.bsTMHMCompatOffset + j] = getByteFromFlags( - flags, j * 8 + 1); - } - } - } - - @Override - public boolean hasMoveTutors() { - return romEntry.romType != Gen4Constants.Type_DP; - } - - @Override - public List<Integer> getMoveTutorMoves() { - if (!hasMoveTutors()) { - return new ArrayList<Integer>(); - } - int baseOffset = romEntry.getInt("MoveTutorMovesOffset"); - int amount = romEntry.getInt("MoveTutorCount"); - int bytesPer = romEntry.getInt("MoveTutorBytesCount"); - List<Integer> mtMoves = new ArrayList<Integer>(); - try { - byte[] mtFile = readOverlay(romEntry - .getInt("MoveTutorMovesOvlNumber")); - for (int i = 0; i < amount; i++) { - mtMoves.add(readWord(mtFile, baseOffset + i * bytesPer)); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return mtMoves; - } - - @Override - public void setMoveTutorMoves(List<Integer> moves) { - if (!hasMoveTutors()) { - return; - } - int baseOffset = romEntry.getInt("MoveTutorMovesOffset"); - int amount = romEntry.getInt("MoveTutorCount"); - int bytesPer = romEntry.getInt("MoveTutorBytesCount"); - if (moves.size() != amount) { - return; - } - try { - byte[] mtFile = readOverlay(romEntry - .getInt("MoveTutorMovesOvlNumber")); - for (int i = 0; i < amount; i++) { - writeWord(mtFile, baseOffset + i * bytesPer, moves.get(i)); - } - writeOverlay(romEntry.getInt("MoveTutorMovesOvlNumber"), mtFile); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public Map<Pokemon, boolean[]> getMoveTutorCompatibility() { - if (!hasMoveTutors()) { - return new TreeMap<Pokemon, boolean[]>(); - } - Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); - int amount = romEntry.getInt("MoveTutorCount"); - int baseOffset = romEntry.getInt("MoveTutorCompatOffset"); - int bytesPer = romEntry.getInt("MoveTutorCompatBytesCount"); - try { - byte[] mtcFile; - if (romEntry.romType == Gen4Constants.Type_HGSS) { - mtcFile = readFile(romEntry.getString("MoveTutorCompat")); - } else { - mtcFile = readOverlay(romEntry - .getInt("MoveTutorCompatOvlNumber")); - } - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - Pokemon pkmn = pokes[i]; - boolean[] flags = new boolean[amount + 1]; - for (int j = 0; j < bytesPer; j++) { - readByteIntoFlags(mtcFile, flags, j * 8 + 1, baseOffset - + (i - 1) * bytesPer + j); - } - compat.put(pkmn, flags); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return compat; - } - - @Override - public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData) { - if (!hasMoveTutors()) { - return; - } - int amount = romEntry.getInt("MoveTutorCount"); - int baseOffset = romEntry.getInt("MoveTutorCompatOffset"); - int bytesPer = romEntry.getInt("MoveTutorCompatBytesCount"); - try { - byte[] mtcFile; - if (romEntry.romType == Gen4Constants.Type_HGSS) { - mtcFile = readFile(romEntry.getString("MoveTutorCompat")); - } else { - mtcFile = readOverlay(romEntry - .getInt("MoveTutorCompatOvlNumber")); - } - for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData - .entrySet()) { - Pokemon pkmn = compatEntry.getKey(); - boolean[] flags = compatEntry.getValue(); - for (int j = 0; j < bytesPer; j++) { - int offsHere = baseOffset + (pkmn.number - 1) * bytesPer - + j; - if (j * 8 + 8 <= amount) { - // entirely new byte - mtcFile[offsHere] = getByteFromFlags(flags, j * 8 + 1); - } else if (j * 8 < amount) { - // need some of the original byte - int newByte = getByteFromFlags(flags, j * 8 + 1) & 0xFF; - int oldByteParts = (mtcFile[offsHere] >>> (8 - amount + j * 8)) << (8 - amount + j * 8); - mtcFile[offsHere] = (byte) (newByte | oldByteParts); - } - // else do nothing to the byte - } - } - if (romEntry.romType == Gen4Constants.Type_HGSS) { - writeFile(romEntry.getString("MoveTutorCompat"), mtcFile); - } else { - writeOverlay(romEntry.getInt("MoveTutorCompatOvlNumber"), - mtcFile); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private int find(byte[] data, String hexString) { - if (hexString.length() % 2 != 0) { - return -3; // error - } - byte[] searchFor = new byte[hexString.length() / 2]; - for (int i = 0; i < searchFor.length; i++) { - searchFor[i] = (byte) Integer.parseInt( - hexString.substring(i * 2, i * 2 + 2), 16); - } - List<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); - } - } - - private boolean lastStringsCompressed = false; - - private List<String> getStrings(int index) { - PokeTextData pt = new PokeTextData(msgNarc.files.get(index)); - pt.decrypt(); - lastStringsCompressed = pt.compressFlag; - return new ArrayList<String>(pt.strlist); - } - - private void setStrings(int index, List<String> newStrings) { - setStrings(index, newStrings, false); - } - - private void setStrings(int index, List<String> newStrings, - boolean compressed) { - byte[] rawUnencrypted = TextToPoke.MakeFile(newStrings, compressed); - - // make new encrypted name set - PokeTextData encrypt = new PokeTextData(rawUnencrypted); - encrypt.SetKey(0xD00E); - encrypt.encrypt(); - - // rewrite - msgNarc.files.set(index, encrypt.get()); - } - - @Override - public String getROMName() { - return "Pokemon " + romEntry.name; - } - - @Override - public String getROMCode() { - return romEntry.romCode; - } - - @Override - public String getSupportLevel() { - return romEntry.staticPokemonSupport ? "Complete" : "No Static Pokemon"; - } - - @Override - public boolean hasTimeBasedEncounters() { - // dppt technically do but we ignore them completely - return romEntry.romType == Gen4Constants.Type_HGSS; - } - - @Override - public boolean canChangeStaticPokemon() { - return romEntry.staticPokemonSupport; - } - - @Override - public boolean canChangeStarters() { - return true; - } - - private void populateEvolutions() { - for (Pokemon pkmn : pokes) { - if (pkmn != null) { - pkmn.evolutionsFrom.clear(); - pkmn.evolutionsTo.clear(); - } - } - - // Read NARC - try { - NARCContents evoNARC = readNARC(romEntry - .getString("PokemonEvolutions")); - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - Pokemon pk = pokes[i]; - byte[] evoEntry = evoNARC.files.get(i); - for (int evo = 0; evo < 7; evo++) { - int method = readWord(evoEntry, evo * 6); - int species = readWord(evoEntry, evo * 6 + 4); - if (method >= 1 - && method <= Gen4Constants.evolutionMethodCount - && species >= 1) { - EvolutionType et = EvolutionType.fromIndex(4, method); - int extraInfo = readWord(evoEntry, evo * 6 + 2); - Evolution evol = new Evolution(pokes[i], - pokes[species], true, et, extraInfo); - if (!pk.evolutionsFrom.contains(evol)) { - pk.evolutionsFrom.add(evol); - pokes[species].evolutionsTo.add(evol); - } - } - } - // split evos don't carry stats - if (pk.evolutionsFrom.size() > 1) { - for (Evolution e : pk.evolutionsFrom) { - e.carryStats = false; - } - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void writeEvolutions() { - try { - NARCContents evoNARC = readNARC(romEntry - .getString("PokemonEvolutions")); - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - byte[] evoEntry = evoNARC.files.get(i); - int evosWritten = 0; - Pokemon pk = pokes[i]; - for (Evolution evo : pk.evolutionsFrom) { - writeWord(evoEntry, evosWritten * 6, evo.type.toIndex(4)); - writeWord(evoEntry, evosWritten * 6 + 2, evo.extraInfo); - writeWord(evoEntry, evosWritten * 6 + 4, evo.to.number); - evosWritten++; - if (evosWritten == 7) { - break; - } - } - while (evosWritten < 7) { - writeWord(evoEntry, evosWritten * 6, 0); - writeWord(evoEntry, evosWritten * 6 + 2, 0); - writeWord(evoEntry, evosWritten * 6 + 4, 0); - evosWritten++; - } - } - writeNARC(romEntry.getString("PokemonEvolutions"), evoNARC); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public void removeTradeEvolutions(boolean changeMoveEvos) { - Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); - log("--Removing Trade Evolutions--"); - Set<Evolution> extraEvolutions = new HashSet<Evolution>(); - for (Pokemon pkmn : pokes) { - if (pkmn != null) { - extraEvolutions.clear(); - for (Evolution evo : pkmn.evolutionsFrom) { - // new 160 other impossible evolutions: - if (romEntry.romType == Gen4Constants.Type_HGSS) { - // beauty milotic - if (evo.type == EvolutionType.LEVEL_HIGH_BEAUTY) { - // Replace w/ level 35 - evo.type = EvolutionType.LEVEL; - evo.extraInfo = 35; - logEvoChangeLevel(evo.from.name, evo.to.name, 35); - } - // mt.coronet (magnezone/probopass) - if (evo.type == EvolutionType.LEVEL_ELECTRIFIED_AREA) { - // Replace w/ level 40 - evo.type = EvolutionType.LEVEL; - evo.extraInfo = 40; - logEvoChangeLevel(evo.from.name, evo.to.name, 40); - } - // moss rock (leafeon) - if (evo.type == EvolutionType.LEVEL_MOSS_ROCK) { - // Replace w/ leaf stone - evo.type = EvolutionType.STONE; - evo.extraInfo = Gen4Constants.leafStoneIndex; // leaf - // stone - logEvoChangeStone(evo.from.name, evo.to.name, - itemNames.get(Gen4Constants.leafStoneIndex)); - } - // icy rock (glaceon) - if (evo.type == EvolutionType.LEVEL_ICY_ROCK) { - // Replace w/ dawn stone - evo.type = EvolutionType.STONE; - evo.extraInfo = Gen4Constants.dawnStoneIndex; // dawn - // stone - logEvoChangeStone(evo.from.name, evo.to.name, - itemNames.get(Gen4Constants.dawnStoneIndex)); - } - } - if (changeMoveEvos - && evo.type == EvolutionType.LEVEL_WITH_MOVE) { - // read move - int move = evo.extraInfo; - int levelLearntAt = 1; - for (MoveLearnt ml : movesets.get(evo.from)) { - 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; - logEvoChangeLevel(evo.from.name, evo.to.name, - levelLearntAt); - } - // Pure Trade - if (evo.type == EvolutionType.TRADE) { - // Replace w/ level 37 - evo.type = EvolutionType.LEVEL; - evo.extraInfo = 37; - logEvoChangeLevel(evo.from.name, evo.to.name, 37); - } - // Trade w/ Item - if (evo.type == EvolutionType.TRADE_ITEM) { - // Get the current item & evolution - int item = evo.extraInfo; - if (evo.from.number == Gen4Constants.slowpokeIndex) { - // 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 = Gen4Constants.waterStoneIndex; // water - // stone - logEvoChangeStone(evo.from.name, evo.to.name, - itemNames - .get(Gen4Constants.waterStoneIndex)); - } else { - logEvoChangeLevelWithItem(evo.from.name, - evo.to.name, itemNames.get(item)); - // Replace, for this entry, w/ - // Level up w/ Held Item at Day - evo.type = EvolutionType.LEVEL_ITEM_DAY; - // now add an extra evo for - // Level up w/ Held Item at Night - Evolution extraEntry = new Evolution(evo.from, - evo.to, true, - EvolutionType.LEVEL_ITEM_NIGHT, item); - extraEvolutions.add(extraEntry); - } - } - } - pkmn.evolutionsFrom.addAll(extraEvolutions); - for (Evolution ev : extraEvolutions) { - ev.to.evolutionsTo.add(ev); - } - } - } - logBlankLine(); - - } - - @Override - public List<String> getTrainerNames() { - List<String> tnames = new ArrayList<String>( - getStrings(romEntry.getInt("TrainerNamesTextOffset"))); - tnames.remove(0); // blank one - for (int i = 0; i < tnames.size(); i++) { - if (tnames.get(i).contains("\\and")) { - tnames.set(i, tnames.get(i).replace("\\and", "&")); - } - } - return tnames; - } - - @Override - public int maxTrainerNameLength() { - return 10;// based off the english ROMs fixed - } - - @Override - public void setTrainerNames(List<String> trainerNames) { - List<String> oldTNames = getStrings(romEntry - .getInt("TrainerNamesTextOffset")); - List<String> newTNames = new ArrayList<String>(trainerNames); - for (int i = 0; i < newTNames.size(); i++) { - if (newTNames.get(i).contains("&")) { - newTNames.set(i, newTNames.get(i).replace("&", "\\and")); - } - } - newTNames.add(0, oldTNames.get(0)); // the 0-entry, preserve it - - // rewrite, only compressed if they were compressed before - setStrings(romEntry.getInt("TrainerNamesTextOffset"), newTNames, - lastStringsCompressed); - - } - - @Override - public TrainerNameMode trainerNameMode() { - return TrainerNameMode.MAX_LENGTH; - } - - @Override - public List<Integer> getTCNameLengthsByTrainer() { - // not needed - return new ArrayList<Integer>(); - } - - @Override - public List<String> getTrainerClassNames() { - return getStrings(romEntry.getInt("TrainerClassesTextOffset")); - } - - @Override - public void setTrainerClassNames(List<String> trainerClassNames) { - setStrings(romEntry.getInt("TrainerClassesTextOffset"), - trainerClassNames); - } - - @Override - public int maxTrainerClassNameLength() { - return 12;// based off the english ROMs - } - - @Override - public boolean fixedTrainerClassNamesLength() { - return false; - } - - @Override - public String getDefaultExtension() { - return "nds"; - } - - @Override - public int abilitiesPerPokemon() { - return 2; - } - - @Override - public int highestAbilityIndex() { - return Gen4Constants.highestAbilityIndex; - } - - @Override - public int internalStringLength(String string) { - return string.length(); - } - - @Override - public void applySignature() { - // For now, do nothing. - - } - - @Override - public ItemList getAllowedItems() { - return Gen4Constants.allowedItems; - } - - @Override - public ItemList getNonBadItems() { - return Gen4Constants.nonBadItems; - } - - @Override - public String[] getItemNames() { - return itemNames.toArray(new String[0]); - } - - @Override - public String abilityName(int number) { - return abilityNames.get(number); - } - - private List<Integer> getFieldItems() { - List<Integer> fieldItems = new ArrayList<Integer>(); - // normal items - int scriptFile = romEntry.getInt("ItemBallsScriptOffset"); - byte[] itemScripts = scriptNarc.files.get(scriptFile); - int offset = 0; - int skipTableOffset = 0; - int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); - int setVar = romEntry.romType == Gen4Constants.Type_HGSS ? Gen4Constants.hgssSetVarScript - : Gen4Constants.dpptSetVarScript; - while (true) { - int part1 = readWord(itemScripts, offset); - if (part1 == Gen4Constants.scriptListTerminator) { - // done - break; - } - int offsetInFile = readRelativePointer(itemScripts, offset); - offset += 4; - if (skipTableOffset < skipTable.length - && (skipTable[skipTableOffset] == (offset / 4) - 1)) { - skipTableOffset++; - continue; - } - int command = readWord(itemScripts, offsetInFile); - int variable = readWord(itemScripts, offsetInFile + 2); - if (command == setVar - && variable == Gen4Constants.itemScriptVariable) { - int item = readWord(itemScripts, offsetInFile + 4); - fieldItems.add(item); - } - - } - - // hidden items - int hiTableOffset = romEntry.getInt("HiddenItemTableOffset"); - int hiTableLimit = romEntry.getInt("HiddenItemCount"); - for (int i = 0; i < hiTableLimit; i++) { - int item = readWord(arm9, hiTableOffset + i * 8); - fieldItems.add(item); - } - - return fieldItems; - } - - private void setFieldItems(List<Integer> fieldItems) { - Iterator<Integer> iterItems = fieldItems.iterator(); - - // normal items - int scriptFile = romEntry.getInt("ItemBallsScriptOffset"); - byte[] itemScripts = scriptNarc.files.get(scriptFile); - int offset = 0; - int skipTableOffset = 0; - int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); - int setVar = romEntry.romType == Gen4Constants.Type_HGSS ? Gen4Constants.hgssSetVarScript - : Gen4Constants.dpptSetVarScript; - while (true) { - int part1 = readWord(itemScripts, offset); - if (part1 == Gen4Constants.scriptListTerminator) { - // done - break; - } - int offsetInFile = readRelativePointer(itemScripts, offset); - offset += 4; - if (skipTableOffset < skipTable.length - && (skipTable[skipTableOffset] == (offset / 4) - 1)) { - skipTableOffset++; - continue; - } - int command = readWord(itemScripts, offsetInFile); - int variable = readWord(itemScripts, offsetInFile + 2); - if (command == setVar - && variable == Gen4Constants.itemScriptVariable) { - int item = iterItems.next(); - writeWord(itemScripts, offsetInFile + 4, item); - } - } - - // hidden items - int hiTableOffset = romEntry.getInt("HiddenItemTableOffset"); - int hiTableLimit = romEntry.getInt("HiddenItemCount"); - for (int i = 0; i < hiTableLimit; i++) { - int item = iterItems.next(); - writeWord(arm9, hiTableOffset + i * 8, item); - } - } - - @Override - public List<Integer> getRequiredFieldTMs() { - if (romEntry.romType == Gen4Constants.Type_DP) { - return Gen4Constants.dpRequiredFieldTMs; - } else if (romEntry.romType == Gen4Constants.Type_Plat) { - // same as DP just we have to keep the weather TMs - return Gen4Constants.ptRequiredFieldTMs; - } - return new ArrayList<Integer>(); - } - - @Override - public List<Integer> getCurrentFieldTMs() { - List<Integer> fieldItems = this.getFieldItems(); - List<Integer> fieldTMs = new ArrayList<Integer>(); - - for (int item : fieldItems) { - if (Gen4Constants.allowedItems.isTM(item)) { - fieldTMs.add(item - Gen4Constants.tmItemOffset + 1); - } - } - - return fieldTMs; - } - - @Override - public void setFieldTMs(List<Integer> fieldTMs) { - List<Integer> fieldItems = this.getFieldItems(); - int fiLength = fieldItems.size(); - Iterator<Integer> iterTMs = fieldTMs.iterator(); - - for (int i = 0; i < fiLength; i++) { - int oldItem = fieldItems.get(i); - if (Gen4Constants.allowedItems.isTM(oldItem)) { - int newItem = iterTMs.next() + Gen4Constants.tmItemOffset - 1; - fieldItems.set(i, newItem); - } - } - - this.setFieldItems(fieldItems); - } - - @Override - public List<Integer> getRegularFieldItems() { - List<Integer> fieldItems = this.getFieldItems(); - List<Integer> fieldRegItems = new ArrayList<Integer>(); - - for (int item : fieldItems) { - if (Gen4Constants.allowedItems.isAllowed(item) - && !(Gen4Constants.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(); - - for (int i = 0; i < fiLength; i++) { - int oldItem = fieldItems.get(i); - if (!(Gen4Constants.allowedItems.isTM(oldItem)) - && Gen4Constants.allowedItems.isAllowed(oldItem)) { - int newItem = iterNewItems.next(); - fieldItems.set(i, newItem); - } - } - - this.setFieldItems(fieldItems); - } - - @Override - public List<IngameTrade> getIngameTrades() { - List<IngameTrade> trades = new ArrayList<IngameTrade>(); - try { - NARCContents tradeNARC = this.readNARC(romEntry - .getString("InGameTrades")); - int[] spTrades = new int[0]; - if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { - spTrades = romEntry.arrayEntries.get("StaticPokemonTrades"); - } - List<String> tradeStrings = getStrings(romEntry - .getInt("IngameTradesTextOffset")); - int tradeCount = tradeNARC.files.size(); - for (int i = 0; i < tradeCount; i++) { - boolean isSP = false; - for (int j = 0; j < spTrades.length; j++) { - if (spTrades[j] == i) { - isSP = true; - break; - } - } - if (isSP) { - continue; - } - byte[] tfile = tradeNARC.files.get(i); - IngameTrade trade = new IngameTrade(); - trade.nickname = tradeStrings.get(i); - trade.givenPokemon = pokes[readLong(tfile, 0)]; - trade.ivs = new int[6]; - for (int iv = 0; iv < 6; iv++) { - trade.ivs[iv] = readLong(tfile, 4 + iv * 4); - } - trade.otId = readWord(tfile, 0x20); - trade.otName = tradeStrings.get(i + tradeCount); - trade.item = readLong(tfile, 0x3C); - trade.requestedPokemon = pokes[readLong(tfile, 0x4C)]; - trades.add(trade); - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - return trades; - } - - @Override - public void setIngameTrades(List<IngameTrade> trades) { - int tradeOffset = 0; - List<IngameTrade> oldTrades = this.getIngameTrades(); - try { - NARCContents tradeNARC = this.readNARC(romEntry - .getString("InGameTrades")); - int[] spTrades = new int[0]; - if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { - spTrades = romEntry.arrayEntries.get("StaticPokemonTrades"); - } - List<String> tradeStrings = getStrings(romEntry - .getInt("IngameTradesTextOffset")); - int tradeCount = tradeNARC.files.size(); - for (int i = 0; i < tradeCount; i++) { - boolean isSP = false; - for (int j = 0; j < spTrades.length; j++) { - if (spTrades[j] == i) { - isSP = true; - break; - } - } - if (isSP) { - continue; - } - byte[] tfile = tradeNARC.files.get(i); - IngameTrade trade = trades.get(tradeOffset++); - tradeStrings.set(i, trade.nickname); - tradeStrings.set(i + tradeCount, trade.otName); - writeLong(tfile, 0, trade.givenPokemon.number); - for (int iv = 0; iv < 6; iv++) { - writeLong(tfile, 4 + iv * 4, trade.ivs[iv]); - } - writeWord(tfile, 0x20, trade.otId); - writeLong(tfile, 0x3C, trade.item); - writeLong(tfile, 0x4C, trade.requestedPokemon.number); - if (tfile.length > 0x50) { - writeLong(tfile, 0x50, 0); // disable gender - } - } - this.writeNARC(romEntry.getString("InGameTrades"), tradeNARC); - this.setStrings(romEntry.getInt("IngameTradesTextOffset"), - tradeStrings); - // update what the people say when they talk to you - if (romEntry.arrayEntries - .containsKey("IngameTradePersonTextOffsets")) { - int[] textOffsets = romEntry.arrayEntries - .get("IngameTradePersonTextOffsets"); - for (int trade = 0; trade < textOffsets.length; trade++) { - if (textOffsets[trade] > 0) { - if (trade >= oldTrades.size() || trade >= trades.size()) { - break; - } - IngameTrade oldTrade = oldTrades.get(trade); - IngameTrade newTrade = trades.get(trade); - Map<String, String> replacements = new TreeMap<String, String>(); - replacements.put(oldTrade.givenPokemon.name, - newTrade.givenPokemon.name); - if (oldTrade.requestedPokemon != newTrade.requestedPokemon) { - replacements.put(oldTrade.requestedPokemon.name, - newTrade.requestedPokemon.name); - } - replaceAllStringsInEntry(textOffsets[trade], - replacements, Gen4Constants.textCharsPerLine); - // hgss override for one set of strings that appears 2x - if (romEntry.romType == Gen4Constants.Type_HGSS - && trade == 6) { - replaceAllStringsInEntry(textOffsets[trade] + 1, - replacements, - Gen4Constants.textCharsPerLine); - } - } - } - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - private void replaceAllStringsInEntry(int entry, - Map<String, String> replacements, int lineLength) { - List<String> thisTradeStrings = this.getStrings(entry); - int ttsCount = thisTradeStrings.size(); - for (int strNum = 0; strNum < ttsCount; strNum++) { - String oldString = thisTradeStrings.get(strNum); - String newString = RomFunctions.formatTextWithReplacements( - oldString, replacements, "\\n", "\\l", "\\p", lineLength, - ssd); - thisTradeStrings.set(strNum, newString); - } - this.setStrings(entry, thisTradeStrings); - } - - @Override - public boolean hasDVs() { - return false; - } - - @Override - public int generationOfPokemon() { - return 4; - } - - @Override - public void removeEvosForPokemonPool() { - // slightly more complicated than gen2/3 - // we have to update a "baby table" too - List<Pokemon> pokemonIncluded = this.mainPokemonList; - Set<Evolution> keepEvos = new HashSet<Evolution>(); - for (Pokemon pk : pokes) { - if (pk != null) { - keepEvos.clear(); - for (Evolution evol : pk.evolutionsFrom) { - if (pokemonIncluded.contains(evol.from) - && pokemonIncluded.contains(evol.to)) { - keepEvos.add(evol); - } else { - evol.to.evolutionsTo.remove(evol); - } - } - pk.evolutionsFrom.retainAll(keepEvos); - } - } - - try { - byte[] babyPokes = readFile(romEntry.getString("BabyPokemon")); - // baby pokemon - for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { - Pokemon baby = pokes[i]; - while (baby.evolutionsTo.size() > 0) { - // Grab the first "to evolution" even if there are multiple - baby = baby.evolutionsTo.get(0).from; - } - writeWord(babyPokes, i * 2, baby.number); - } - // finish up - writeFile(romEntry.getString("BabyPokemon"), babyPokes); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean supportsFourStartingMoves() { - return true; - } - - @Override - public List<Integer> getFieldMoves() { - if (romEntry.romType == Gen4Constants.Type_HGSS) { - return Gen4Constants.hgssFieldMoves; - } else { - return Gen4Constants.dpptFieldMoves; - } - } - - @Override - public List<Integer> getEarlyRequiredHMMoves() { - if (romEntry.romType == Gen4Constants.Type_HGSS) { - return Gen4Constants.hgssEarlyRequiredHMMoves; - } else { - return Gen4Constants.dpptEarlyRequiredHMMoves; - } - } - - @Override - public int miscTweaksAvailable() { - int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); - available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); - if (romEntry.tweakFiles.get("FastestTextTweak") != null) { - available |= MiscTweak.FASTEST_TEXT.getValue(); - } - return available; - } - - @Override - public void applyMiscTweak(MiscTweak tweak) { - if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { - applyCamelCaseNames(); - } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { - randomizeCatchingTutorial(); - } else if (tweak == MiscTweak.FASTEST_TEXT) { - applyFastestText(); - } - } - - private void randomizeCatchingTutorial() { - int opponentOffset = romEntry - .getInt("CatchingTutorialOpponentMonOffset"); - - if (romEntry.romType == Gen4Constants.Type_HGSS) { - // Can randomize player mon too, but both limited to 1-255 - int playerOffset = romEntry - .getInt("CatchingTutorialPlayerMonOffset"); - - Pokemon opponent = randomPokemonLimited(255, false); - Pokemon player = randomPokemonLimited(255, false); - - if (opponent != null && player != null) { - arm9[opponentOffset] = (byte) opponent.number; - arm9[playerOffset] = (byte) player.number; - } - } else { - // Only opponent, but enough space for any mon - Pokemon opponent = randomPokemonLimited(Integer.MAX_VALUE, false); - - if (opponent != null) { - writeLong(arm9, opponentOffset, opponent.number); - } - } - - } - - private void applyFastestText() { - genericIPSPatch(arm9, "FastestTextTweak"); - } - - private boolean genericIPSPatch(byte[] data, String ctName) { - String patchName = romEntry.tweakFiles.get(ctName); - if (patchName == null) { - return false; - } - - try { - FileFunctions.applyPatch(data, patchName); - return true; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private Pokemon randomPokemonLimited(int maxValue, boolean blockNonMales) { - checkPokemonRestrictions(); - List<Pokemon> validPokemon = new ArrayList<Pokemon>(); - for (Pokemon pk : this.mainPokemonList) { - if (pk.number <= maxValue - && (!blockNonMales || pk.genderRatio <= 0xFD)) { - validPokemon.add(pk); - } - } - if (validPokemon.size() == 0) { - return null; - } else { - return validPokemon.get(random.nextInt(validPokemon.size())); - } - } - - @Override - public BufferedImage getMascotImage() { - try { - Pokemon pk = randomPokemon(); - NARCContents pokespritesNARC = this.readNARC(romEntry - .getString("PokemonGraphics")); - int spriteIndex = pk.number * 6 + 2 + random.nextInt(2); - int palIndex = pk.number * 6 + 4; - if (random.nextInt(10) == 0) { - // shiny - palIndex++; - } - - // read sprite - byte[] rawSprite = pokespritesNARC.files.get(spriteIndex); - if (rawSprite.length == 0) { - // Must use other gender form - rawSprite = pokespritesNARC.files.get(spriteIndex ^ 1); - } - int[] spriteData = new int[3200]; - for (int i = 0; i < 3200; i++) { - spriteData[i] = readWord(rawSprite, i * 2 + 48); - } - - // Decrypt sprite (why does EVERYTHING use the RNG formula geez) - if (romEntry.romType != Gen4Constants.Type_DP) { - int key = spriteData[0]; - for (int i = 0; i < 3200; i++) { - spriteData[i] ^= (key & 0xFFFF); - key = key * 0x41C64E6D + 0x6073; - } - } else { - // D/P sprites are encrypted *backwards*. Wut. - int key = spriteData[3199]; - for (int i = 3199; i >= 0; i--) { - spriteData[i] ^= (key & 0xFFFF); - key = key * 0x41C64E6D + 0x6073; - } - } - - byte[] rawPalette = pokespritesNARC.files.get(palIndex); - - int[] palette = new int[16]; - for (int i = 1; i < 16; i++) { - palette[i] = GFXFunctions.conv16BitColorToARGB(readWord( - rawPalette, 40 + i * 2)); - } - - // Deliberately chop off the right half of the image while still - // correctly indexing the array. - BufferedImage bim = new BufferedImage(80, 80, - BufferedImage.TYPE_INT_ARGB); - for (int y = 0; y < 80; y++) { - for (int x = 0; x < 80; x++) { - int value = ((spriteData[y * 40 + x / 4]) >> (x % 4) * 4) & 0x0F; - bim.setRGB(x, y, palette[value]); - } - } - return bim; - } catch (IOException e) { - throw new RuntimeException(e); - } - } + public static class Factory extends RomHandler.Factory { + + @Override + public Gen4RomHandler create(Random random, PrintStream logStream) { + return new Gen4RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + return detectNDSRomInner(getROMCodeFromFile(filename)); + } + } + + public Gen4RomHandler(Random random) { + super(random, null); + } + + public Gen4RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + private static class RomEntry { + private String name; + private String romCode; + private int romType; + private boolean staticPokemonSupport = false, copyStaticPokemon = false; + private Map<String, String> strings = new HashMap<String, String>(); + private Map<String, String> tweakFiles = new HashMap<String, String>(); + private Map<String, Integer> numbers = new HashMap<String, Integer>(); + private Map<String, int[]> arrayEntries = new HashMap<String, int[]>(); + private List<StaticPokemon> staticPokemon = new ArrayList<StaticPokemon>(); + + 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 static List<RomEntry> roms; + + static { + loadROMInfo(); + + } + + private static void loadROMInfo() { + roms = new ArrayList<RomEntry>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen4_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + if (r[0].equals("Game")) { + current.romCode = r[1]; + } else if (r[0].equals("Type")) { + if (r[1].equalsIgnoreCase("DP")) { + current.romType = Gen4Constants.Type_DP; + } else if (r[1].equalsIgnoreCase("Plat")) { + current.romType = Gen4Constants.Type_Plat; + } else if (r[1].equalsIgnoreCase("HGSS")) { + current.romType = Gen4Constants.Type_HGSS; + } else { + System.err.println("unrecognised rom type: " + r[1]); + } + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.romCode)) { + // copy from here + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.numbers.putAll(otherEntry.numbers); + current.strings.putAll(otherEntry.strings); + if (current.copyStaticPokemon) { + current.staticPokemon.addAll(otherEntry.staticPokemon); + current.staticPokemonSupport = true; + } else { + current.staticPokemonSupport = false; + } + } + } + } else if (r[0].equals("StaticPokemon[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + int[] offs = new int[offsets.length]; + int[] files = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + String[] parts = off.split("\\:"); + files[c] = parseRIInt(parts[0]); + offs[c++] = parseRIInt(parts[1]); + } + StaticPokemon sp = new StaticPokemon(); + sp.files = files; + sp.offsets = offs; + current.staticPokemon.add(sp); + } else { + String[] parts = r[1].split("\\:"); + int files = parseRIInt(parts[0]); + int offs = parseRIInt(parts[1]); + StaticPokemon sp = new StaticPokemon(); + sp.files = new int[] { files }; + sp.offsets = new int[] { offs }; + } + } else if (r[0].equals("StaticPokemonSupport")) { + int spsupport = parseRIInt(r[1]); + current.staticPokemonSupport = (spsupport > 0); + } else if (r[0].equals("CopyStaticPokemon")) { + int csp = parseRIInt(r[1]); + current.copyStaticPokemon = (csp > 0); + } else if (r[0].endsWith("Tweak")) { + current.tweakFiles.put(r[0], r[1]); + } else { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.arrayEntries.put(r[0], offs); + } + } else if (r[0].endsWith("Offset") || r[0].endsWith("Count") || r[0].endsWith("Number") + || r[0].endsWith("Size")) { + int offs = parseRIInt(r[1]); + current.numbers.put(r[0], offs); + } else { + current.strings.put(r[0], r[1]); + } + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + } + + } + + 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; + } + } + + // This rom + private Pokemon[] pokes; + private List<Pokemon> pokemonList; + private Move[] moves; + private NARCContents pokeNarc, moveNarc; + private NARCContents msgNarc; + private NARCContents scriptNarc; + private NARCContents eventNarc; + private byte[] arm9; + private List<String> abilityNames; + private List<String> itemNames; + private boolean loadedWildMapNames; + private Map<Integer, String> wildMapNames; + + private RomEntry romEntry; + + @Override + protected boolean detectNDSRom(String ndsCode) { + return detectNDSRomInner(ndsCode); + } + + private static boolean detectNDSRomInner(String ndsCode) { + return entryFor(ndsCode) != null; + } + + private static RomEntry entryFor(String ndsCode) { + for (RomEntry re : roms) { + if (ndsCode.equals(re.romCode)) { + return re; + } + } + return null; + } + + @Override + protected void loadedROM(String romCode) { + this.romEntry = entryFor(romCode); + try { + arm9 = readARM9(); + } catch (IOException e) { + throw new RuntimeException(e); + } + try { + msgNarc = readNARC(romEntry.getString("Text")); + } catch (IOException e) { + throw new RuntimeException(e); + } + try { + scriptNarc = readNARC(romEntry.getString("Scripts")); + } catch (IOException e) { + throw new RuntimeException(e); + } + try { + eventNarc = readNARC(romEntry.getString("Events")); + } catch (IOException e) { + throw new RuntimeException(e); + } + loadPokemonStats(); + pokemonList = Arrays.asList(pokes); + loadMoves(); + abilityNames = getStrings(romEntry.getInt("AbilityNamesTextOffset")); + itemNames = getStrings(romEntry.getInt("ItemNamesTextOffset")); + loadedWildMapNames = false; + } + + private void loadMoves() { + try { + moveNarc = this.readNARC(romEntry.getString("MoveData")); + moves = new Move[Gen4Constants.moveCount + 1]; + List<String> moveNames = getStrings(romEntry.getInt("MoveNamesTextOffset")); + for (int i = 1; i <= Gen4Constants.moveCount; i++) { + byte[] moveData = moveNarc.files.get(i); + moves[i] = new Move(); + moves[i].name = moveNames.get(i); + moves[i].number = i; + moves[i].internalId = i; + moves[i].effectIndex = readWord(moveData, 0); + moves[i].hitratio = (moveData[5] & 0xFF); + moves[i].power = moveData[3] & 0xFF; + moves[i].pp = moveData[6] & 0xFF; + moves[i].type = Gen4Constants.typeTable[moveData[4] & 0xFF]; + moves[i].category = Gen4Constants.moveCategoryIndices[moveData[2] & 0xFF]; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + private void loadPokemonStats() { + try { + String pstatsnarc = romEntry.getString("PokemonStats"); + pokeNarc = this.readNARC(pstatsnarc); + String[] pokeNames = readPokemonNames(); + pokes = new Pokemon[Gen4Constants.pokemonCount + 1]; + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i], pokeNarc.files.get(i)); + // Name? + pokes[i].name = pokeNames[i]; + } + populateEvolutions(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + private void loadBasicPokeStats(Pokemon pkmn, byte[] stats) { + pkmn.hp = stats[Gen4Constants.bsHPOffset] & 0xFF; + pkmn.attack = stats[Gen4Constants.bsAttackOffset] & 0xFF; + pkmn.defense = stats[Gen4Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = stats[Gen4Constants.bsSpeedOffset] & 0xFF; + pkmn.spatk = stats[Gen4Constants.bsSpAtkOffset] & 0xFF; + pkmn.spdef = stats[Gen4Constants.bsSpDefOffset] & 0xFF; + // Type + pkmn.primaryType = Gen4Constants.typeTable[stats[Gen4Constants.bsPrimaryTypeOffset] & 0xFF]; + pkmn.secondaryType = Gen4Constants.typeTable[stats[Gen4Constants.bsSecondaryTypeOffset] & 0xFF]; + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + pkmn.catchRate = stats[Gen4Constants.bsCatchRateOffset] & 0xFF; + pkmn.growthCurve = ExpCurve.fromByte(stats[Gen4Constants.bsGrowthCurveOffset]); + + // Abilities + pkmn.ability1 = stats[Gen4Constants.bsAbility1Offset] & 0xFF; + pkmn.ability2 = stats[Gen4Constants.bsAbility2Offset] & 0xFF; + + // Held Items? + int item1 = readWord(stats, Gen4Constants.bsCommonHeldItemOffset); + int item2 = readWord(stats, Gen4Constants.bsRareHeldItemOffset); + + if (item1 == item2) { + // guaranteed + pkmn.guaranteedHeldItem = item1; + pkmn.commonHeldItem = 0; + pkmn.rareHeldItem = 0; + } else { + pkmn.guaranteedHeldItem = 0; + pkmn.commonHeldItem = item1; + pkmn.rareHeldItem = item2; + } + pkmn.darkGrassHeldItem = -1; + } + + private String[] readPokemonNames() { + String[] pokeNames = new String[Gen4Constants.pokemonCount + 1]; + List<String> nameList = getStrings(romEntry.getInt("PokemonNamesTextOffset")); + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + pokeNames[i] = nameList.get(i); + } + return pokeNames; + } + + @Override + protected void savingROM() { + savePokemonStats(); + saveMoves(); + try { + writeARM9(arm9); + } catch (IOException e) { + throw new RuntimeException(e); + } + try { + writeNARC(romEntry.getString("Text"), msgNarc); + } catch (IOException e) { + throw new RuntimeException(e); + } + try { + writeNARC(romEntry.getString("Scripts"), scriptNarc); + } catch (IOException e) { + throw new RuntimeException(e); + } + try { + writeNARC(romEntry.getString("Events"), eventNarc); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void saveMoves() { + for (int i = 1; i <= Gen4Constants.moveCount; i++) { + byte[] data = moveNarc.files.get(i); + writeWord(data, 0, moves[i].effectIndex); + data[3] = (byte) moves[i].power; + data[4] = Gen4Constants.typeToByte(moves[i].type); + int hitratio = (int) Math.round(moves[i].hitratio); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 100) { + hitratio = 100; + } + data[5] = (byte) hitratio; + data[6] = (byte) moves[i].pp; + } + + try { + this.writeNARC(romEntry.getString("MoveData"), moveNarc); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + private void savePokemonStats() { + // Update the "a/an X" list too, if it exists + List<String> namesList = getStrings(romEntry.getInt("PokemonNamesTextOffset")); + if (romEntry.getString("HasExtraPokemonNames").equalsIgnoreCase("Yes")) { + List<String> namesList2 = getStrings(romEntry.getInt("PokemonNamesTextOffset") + 1); + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + saveBasicPokeStats(pokes[i], pokeNarc.files.get(i)); + String oldName = namesList.get(i); + namesList.set(i, pokes[i].name); + namesList2.set(i, namesList2.get(i).replace(oldName, pokes[i].name)); + } + setStrings(romEntry.getInt("PokemonNamesTextOffset") + 1, namesList2, false); + } else { + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + saveBasicPokeStats(pokes[i], pokeNarc.files.get(i)); + namesList.set(i, pokes[i].name); + } + } + setStrings(romEntry.getInt("PokemonNamesTextOffset"), namesList, false); + + try { + String pstatsnarc = romEntry.getString("PokemonStats"); + this.writeNARC(pstatsnarc, pokeNarc); + } catch (IOException e) { + throw new RuntimeException(e); + } + + writeEvolutions(); + + } + + private void saveBasicPokeStats(Pokemon pkmn, byte[] stats) { + stats[Gen4Constants.bsHPOffset] = (byte) pkmn.hp; + stats[Gen4Constants.bsAttackOffset] = (byte) pkmn.attack; + stats[Gen4Constants.bsDefenseOffset] = (byte) pkmn.defense; + stats[Gen4Constants.bsSpeedOffset] = (byte) pkmn.speed; + stats[Gen4Constants.bsSpAtkOffset] = (byte) pkmn.spatk; + stats[Gen4Constants.bsSpDefOffset] = (byte) pkmn.spdef; + stats[Gen4Constants.bsPrimaryTypeOffset] = Gen4Constants.typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + stats[Gen4Constants.bsSecondaryTypeOffset] = stats[Gen4Constants.bsPrimaryTypeOffset]; + } else { + stats[Gen4Constants.bsSecondaryTypeOffset] = Gen4Constants.typeToByte(pkmn.secondaryType); + } + stats[Gen4Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + stats[Gen4Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + + stats[Gen4Constants.bsAbility1Offset] = (byte) pkmn.ability1; + stats[Gen4Constants.bsAbility2Offset] = (byte) pkmn.ability2; + + // Held items + if (pkmn.guaranteedHeldItem > 0) { + writeWord(stats, Gen4Constants.bsCommonHeldItemOffset, pkmn.guaranteedHeldItem); + writeWord(stats, Gen4Constants.bsRareHeldItemOffset, pkmn.guaranteedHeldItem); + } else { + writeWord(stats, Gen4Constants.bsCommonHeldItemOffset, pkmn.commonHeldItem); + writeWord(stats, Gen4Constants.bsRareHeldItemOffset, pkmn.rareHeldItem); + } + } + + @Override + public List<Pokemon> getPokemon() { + return pokemonList; + } + + @Override + public List<Pokemon> getStarters() { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + List<Integer> tailOffsets = RomFunctions.search(arm9, Gen4Constants.hgssStarterCodeSuffix); + if (tailOffsets.size() == 1) { + // Found starters + int starterOffset = tailOffsets.get(0) - 13; + int poke1 = readWord(arm9, starterOffset); + int poke2 = readWord(arm9, starterOffset + 4); + int poke3 = readWord(arm9, starterOffset + 8); + return Arrays.asList(pokes[poke1], pokes[poke2], pokes[poke3]); + } else { + return Arrays.asList(pokes[Gen4Constants.chikoritaIndex], pokes[Gen4Constants.cyndaquilIndex], + pokes[Gen4Constants.totodileIndex]); + } + } else { + try { + byte[] starterData = readOverlay(romEntry.getInt("StarterPokemonOvlNumber")); + int poke1 = readWord(starterData, romEntry.getInt("StarterPokemonOffset")); + int poke2 = readWord(starterData, romEntry.getInt("StarterPokemonOffset") + 4); + int poke3 = readWord(starterData, romEntry.getInt("StarterPokemonOffset") + 8); + return Arrays.asList(pokes[poke1], pokes[poke2], pokes[poke3]); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public boolean setStarters(List<Pokemon> newStarters) { + if (newStarters.size() != 3) { + return false; + } + + if (romEntry.romType == Gen4Constants.Type_HGSS) { + List<Integer> tailOffsets = RomFunctions.search(arm9, Gen4Constants.hgssStarterCodeSuffix); + if (tailOffsets.size() == 1) { + // Found starters + int starterOffset = tailOffsets.get(0) - 13; + writeWord(arm9, starterOffset, newStarters.get(0).number); + writeWord(arm9, starterOffset + 4, newStarters.get(1).number); + writeWord(arm9, starterOffset + 8, newStarters.get(2).number); + // Go fix the rival scripts, which rely on fixed pokemon numbers + // The logic to be changed each time is roughly: + // Set 0x800C = player starter + // If(0x800C==152) { trainerbattle rival w/ cynda } + // ElseIf(0x800C==155) { trainerbattle rival w/ totodile } + // Else { trainerbattle rival w/ chiko } + // So we basically have to adjust the 152 and the 155. + int[] filesWithRivalScript = Gen4Constants.hgssFilesWithRivalScript; + // below code represents a rival script for sure + // it means: StoreStarter2 0x800C; If 0x800C 152; CheckLR B_!= + // <offset to follow> + byte[] magic = Gen4Constants.hgssRivalScriptMagic; + NARCContents scriptNARC = scriptNarc; + for (int i = 0; i < filesWithRivalScript.length; i++) { + int fileCheck = filesWithRivalScript[i]; + byte[] file = scriptNARC.files.get(fileCheck); + List<Integer> rivalOffsets = RomFunctions.search(file, magic); + if (rivalOffsets.size() == 1) { + // found, adjust + int baseOffset = rivalOffsets.get(0); + // Replace 152 (chiko) with first starter + writeWord(file, baseOffset + 8, newStarters.get(0).number); + int jumpAmount = readLong(file, baseOffset + 13); + int secondBase = jumpAmount + baseOffset + 17; + // TODO find out what this constant 0x11 is and remove + // it + if (file[secondBase] != 0x11 || (file[secondBase + 4] & 0xFF) != Gen4Constants.cyndaquilIndex) { + // This isn't what we were expecting... + } else { + // Replace 155 (cynda) with 2nd starter + writeWord(file, secondBase + 4, newStarters.get(1).number); + } + } + } + // Fix starter text + List<String> spStrings = getStrings(romEntry.getInt("StarterScreenTextOffset")); + String[] intros = new String[] { "So, you like", "You’ll take", "Do you want" }; + for (int i = 0; i < 3; i++) { + Pokemon newStarter = newStarters.get(i); + int color = (i == 0) ? 3 : i; + String newStarterDesc = "Professor Elm: " + intros[i] + " \\vFF00\\z000" + color + newStarter.name + + "\\vFF00\\z0000,\\nthe " + newStarter.primaryType.camelCase() + "-type Pokémon?"; + spStrings.set(i + 1, newStarterDesc); + String altStarterDesc = "\\vFF00\\z000" + color + newStarter.name + "\\vFF00\\z0000, the " + + newStarter.primaryType.camelCase() + "-type Pokémon, is\\nin this Poké Ball!"; + spStrings.set(i + 4, altStarterDesc); + } + setStrings(romEntry.getInt("StarterScreenTextOffset"), spStrings); + return true; + } else { + return false; + } + } else { + try { + byte[] starterData = readOverlay(romEntry.getInt("StarterPokemonOvlNumber")); + writeWord(starterData, romEntry.getInt("StarterPokemonOffset"), newStarters.get(0).number); + writeWord(starterData, romEntry.getInt("StarterPokemonOffset") + 4, newStarters.get(1).number); + writeWord(starterData, romEntry.getInt("StarterPokemonOffset") + 8, newStarters.get(2).number); + writeOverlay(romEntry.getInt("StarterPokemonOvlNumber"), starterData); + // Patch DPPt-style rival scripts + // these have a series of IfJump commands + // following pokemon IDs + // the jumps either go to trainer battles, or a HoF times + // checker, or the StarterBattle command (Pt only) + // the HoF times checker case is for the Fight Area or Survival + // Area (depending on version). + // the StarterBattle case is for Route 201 in Pt. + int[] filesWithRivalScript = (romEntry.romType == Gen4Constants.Type_Plat) ? Gen4Constants.ptFilesWithRivalScript + : Gen4Constants.dpFilesWithRivalScript; + byte[] magic = Gen4Constants.dpptRivalScriptMagic; + NARCContents scriptNARC = scriptNarc; + for (int i = 0; i < filesWithRivalScript.length; i++) { + int fileCheck = filesWithRivalScript[i]; + byte[] file = scriptNARC.files.get(fileCheck); + List<Integer> rivalOffsets = RomFunctions.search(file, magic); + if (rivalOffsets.size() > 0) { + for (int baseOffset : rivalOffsets) { + // found, check for trainer battle or HoF + // check at jump + int jumpLoc = baseOffset + magic.length; + int jumpTo = readLong(file, jumpLoc) + jumpLoc + 4; + // TODO find out what these constants are and remove + // them + if (readWord(file, jumpTo) != 0xE5 && readWord(file, jumpTo) != 0x28F + && (readWord(file, jumpTo) != 0x125 || romEntry.romType != Gen4Constants.Type_Plat)) { + continue; // not a rival script + } + // Replace the two starter-words 387 and 390 + writeWord(file, baseOffset + 0x8, newStarters.get(0).number); + writeWord(file, baseOffset + 0x15, newStarters.get(1).number); + } + } + } + // Tag battles with rival or friend + // Have their own script magic + // 2 for Lucas/Dawn (=4 occurrences), 1 or 2 for Barry + byte[] tagBattleMagic = Gen4Constants.dpptTagBattleScriptMagic1; + byte[] tagBattleMagic2 = Gen4Constants.dpptTagBattleScriptMagic2; + int[] filesWithTagBattleScript = (romEntry.romType == Gen4Constants.Type_Plat) ? Gen4Constants.ptFilesWithTagScript + : Gen4Constants.dpFilesWithTagScript; + for (int i = 0; i < filesWithTagBattleScript.length; i++) { + int fileCheck = filesWithTagBattleScript[i]; + byte[] file = scriptNARC.files.get(fileCheck); + List<Integer> tbOffsets = RomFunctions.search(file, tagBattleMagic); + if (tbOffsets.size() > 0) { + for (int baseOffset : tbOffsets) { + // found, check for second part + int secondPartStart = baseOffset + tagBattleMagic.length + 2; + if (secondPartStart + tagBattleMagic2.length > file.length) { + continue; // match failed + } + boolean valid = true; + for (int spo = 0; spo < tagBattleMagic2.length; spo++) { + if (file[secondPartStart + spo] != tagBattleMagic2[spo]) { + valid = false; + break; + } + } + if (!valid) { + continue; + } + // Make sure the jump following the second + // part jumps to a <return> command + int jumpLoc = secondPartStart + tagBattleMagic2.length; + int jumpTo = readLong(file, jumpLoc) + jumpLoc + 4; + // TODO find out what this constant is and remove it + if (readWord(file, jumpTo) != 0x1B) { + continue; // not a tag battle script + } + // Replace the two starter-words + if (readWord(file, baseOffset + 0x21) == Gen4Constants.turtwigIndex) { + // first starter + writeWord(file, baseOffset + 0x21, newStarters.get(0).number); + } else { + // third starter + writeWord(file, baseOffset + 0x21, newStarters.get(2).number); + } + // second starter + writeWord(file, baseOffset + 0xE, newStarters.get(1).number); + } + } + } + // Fix starter script text + // The starter picking screen + List<String> spStrings = getStrings(romEntry.getInt("StarterScreenTextOffset")); + // Get pokedex info + List<String> pokedexSpeciesStrings = getStrings(romEntry.getInt("PokedexSpeciesTextOffset")); + for (int i = 0; i < 3; i++) { + Pokemon newStarter = newStarters.get(i); + int color = (i == 0) ? 3 : i; + String newStarterDesc = "\\vFF00\\z000" + color + pokedexSpeciesStrings.get(newStarter.number) + + " " + newStarter.name + "\\vFF00\\z0000!\\nWill you take this Pokémon?"; + spStrings.set(i + 1, newStarterDesc); + } + // rewrite starter picking screen + setStrings(romEntry.getInt("StarterScreenTextOffset"), spStrings); + if (romEntry.romType == Gen4Constants.Type_DP) { + // what rival says after we get the Pokemon + List<String> lakeStrings = getStrings(romEntry.getInt("StarterLocationTextOffset")); + lakeStrings + .set(Gen4Constants.dpStarterStringIndex, + "\\v0103\\z0000: Fwaaah!\\nYour Pokémon totally rocked!\\pBut mine was way tougher\\nthan yours!\\p...They were other people’s\\nPokémon, though...\\pBut we had to use them...\\nThey won’t mind, will they?\\p"); + setStrings(romEntry.getInt("StarterLocationTextOffset"), lakeStrings); + } else { + // what rival says after we get the Pokemon + List<String> r201Strings = getStrings(romEntry.getInt("StarterLocationTextOffset")); + r201Strings.set(Gen4Constants.ptStarterStringIndex, + "\\v0103\\z0000\\z0000: Then, I choose you!\\nI’m picking this one!\\p"); + setStrings(romEntry.getInt("StarterLocationTextOffset"), r201Strings); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return true; + } + } + + @Override + public List<Integer> getStarterHeldItems() { + // do nothing + return new ArrayList<Integer>(); + } + + @Override + public void setStarterHeldItems(List<Integer> items) { + // do nothing + } + + @Override + public void shufflePokemonStats() { + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + pokes[i].shuffleStats(this.random); + } + } + + @Override + public List<Move> getMoves() { + return Arrays.asList(moves); + } + + @Override + public List<EncounterSet> getEncounters(boolean useTimeOfDay) { + if (!loadedWildMapNames) { + loadWildMapNames(); + } + + try { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + return getEncountersHGSS(useTimeOfDay); + } else { + return getEncountersDPPt(useTimeOfDay); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private List<EncounterSet> getEncountersDPPt(boolean useTimeOfDay) throws IOException { + // Determine file to use + String encountersFile = romEntry.getString("WildPokemon"); + + NARCContents encounterData = readNARC(encountersFile); + List<EncounterSet> encounters = new ArrayList<EncounterSet>(); + // Credit for + // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-sinnoh.py + // for the structure for this. + int c = -1; + for (byte[] b : encounterData.files) { + c++; + if (!wildMapNames.containsKey(c)) { + wildMapNames.put(c, "? Unknown ?"); + } + String mapName = wildMapNames.get(c); + int grassRate = readLong(b, 0); + if (grassRate != 0) { + // up to 4 + List<Encounter> grassEncounters = readEncountersDPPt(b, 4, 12); + EncounterSet grass = new EncounterSet(); + grass.displayName = mapName + " Grass/Cave"; + grass.encounters = grassEncounters; + grass.rate = grassRate; + grass.offset = c; + encounters.add(grass); + + // Time of day replacements? + if (useTimeOfDay) { + for (int i = 0; i < 4; i++) { + int pknum = readLong(b, 108 + 4 * i); + if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { + Pokemon pk = pokes[pknum]; + Encounter enc = new Encounter(); + enc.level = grassEncounters.get(Gen4Constants.dpptAlternateSlots[i + 2]).level; + enc.pokemon = pk; + grassEncounters.add(enc); + } + } + } + // (if useTimeOfDay is off, just override them later) + + // Other conditional replacements (swarm, radar, GBA) + EncounterSet conds = new EncounterSet(); + conds.displayName = mapName + " Swarm/Radar/GBA"; + conds.rate = grassRate; + conds.offset = c; + for (int i = 0; i < 20; i++) { + if (i >= 2 && i <= 5) { + // Time of day slot, handled already + continue; + } + int offs = 100 + i * 4 + (i >= 10 ? 24 : 0); + int pknum = readLong(b, offs); + if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { + Pokemon pk = pokes[pknum]; + Encounter enc = new Encounter(); + enc.level = grassEncounters.get(Gen4Constants.dpptAlternateSlots[i]).level; + enc.pokemon = pk; + conds.encounters.add(enc); + } + } + if (conds.encounters.size() > 0) { + encounters.add(conds); + } + } + + // up to 204, 5 sets of "sea" encounters to go + int offset = 204; + for (int i = 0; i < 5; i++) { + int rate = readLong(b, offset); + offset += 4; + List<Encounter> encountersHere = readSeaEncountersDPPt(b, offset, 5); + offset += 40; + if (rate == 0 || i == 1) { + continue; + } + EncounterSet other = new EncounterSet(); + other.displayName = mapName + " " + Gen4Constants.dpptWaterSlotSetNames[i]; + other.offset = c; + other.encounters = encountersHere; + other.rate = rate; + encounters.add(other); + } + } + return encounters; + } + + private List<Encounter> readEncountersDPPt(byte[] data, int offset, int amount) { + List<Encounter> encounters = new ArrayList<Encounter>(); + for (int i = 0; i < amount; i++) { + int level = readLong(data, offset + i * 8); + int pokemon = readLong(data, offset + 4 + i * 8); + Encounter enc = new Encounter(); + enc.level = level; + enc.pokemon = pokes[pokemon]; + encounters.add(enc); + } + return encounters; + } + + private List<Encounter> readSeaEncountersDPPt(byte[] data, int offset, int amount) { + List<Encounter> encounters = new ArrayList<Encounter>(); + for (int i = 0; i < amount; i++) { + int level = readLong(data, offset + i * 8); + int pokemon = readLong(data, offset + 4 + i * 8); + Encounter enc = new Encounter(); + enc.level = level >> 8; + enc.maxLevel = level & 0xFF; + enc.pokemon = pokes[pokemon]; + encounters.add(enc); + } + return encounters; + } + + private List<EncounterSet> getEncountersHGSS(boolean useTimeOfDay) throws IOException { + String encountersFile = romEntry.getString("WildPokemon"); + NARCContents encounterData = readNARC(encountersFile); + List<EncounterSet> encounters = new ArrayList<EncounterSet>(); + // Credit for + // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-johto.py + // for the structure for this. + int[] amounts = new int[] { 0, 5, 2, 5, 5, 5 }; + int c = -1; + for (byte[] b : encounterData.files) { + c++; + if (!wildMapNames.containsKey(c)) { + wildMapNames.put(c, "? Unknown ?"); + } + String mapName = wildMapNames.get(c); + int[] rates = new int[6]; + rates[0] = b[0] & 0xFF; + rates[1] = b[1] & 0xFF; + rates[2] = b[2] & 0xFF; + rates[3] = b[3] & 0xFF; + rates[4] = b[4] & 0xFF; + rates[5] = b[5] & 0xFF; + // Up to 8 after the rates + // Grass has to be handled on its own because the levels + // are reused for every time of day + int[] grassLevels = new int[12]; + for (int i = 0; i < 12; i++) { + grassLevels[i] = b[8 + i] & 0xFF; + } + // Up to 20 now (12 for levels) + Pokemon[][] grassPokes = new Pokemon[3][12]; + grassPokes[0] = readPokemonHGSS(b, 20, 12); + grassPokes[1] = readPokemonHGSS(b, 44, 12); + grassPokes[2] = readPokemonHGSS(b, 68, 12); + // Up to 92 now (12*2*3 for pokemon) + if (rates[0] != 0) { + if (!useTimeOfDay) { + // Just write "day" encounters + List<Encounter> grassEncounters = stitchEncsToLevels(grassPokes[1], grassLevels); + EncounterSet grass = new EncounterSet(); + grass.encounters = grassEncounters; + grass.rate = rates[0]; + grass.displayName = mapName + " Grass/Cave"; + encounters.add(grass); + } else { + for (int i = 0; i < 3; i++) { + EncounterSet grass = new EncounterSet(); + grass.encounters = stitchEncsToLevels(grassPokes[i], grassLevels); + grass.rate = rates[0]; + grass.displayName = mapName + " " + Gen4Constants.hgssTimeOfDayNames[i] + " Grass/Cave"; + encounters.add(grass); + } + } + } + + // Hoenn/Sinnoh Radio + EncounterSet radio = readOptionalEncountersHGSS(b, 92, 4); + radio.displayName = mapName + " Hoenn/Sinnoh Radio"; + if (radio.encounters.size() > 0) { + encounters.add(radio); + } + + // Up to 100 now... 2*2*2 for radio pokemon + int offset = 100; + for (int i = 1; i < 6; i++) { + List<Encounter> encountersHere = readSeaEncountersHGSS(b, offset, amounts[i]); + offset += 4 * amounts[i]; + if (rates[i] != 0) { + // Valid area. + EncounterSet other = new EncounterSet(); + other.encounters = encountersHere; + other.displayName = mapName + " " + Gen4Constants.hgssNonGrassSetNames[i]; + other.rate = rates[i]; + encounters.add(other); + } + } + + // Swarms + EncounterSet swarms = readOptionalEncountersHGSS(b, offset, 4); + swarms.displayName = mapName + " Swarms"; + if (swarms.encounters.size() > 0) { + encounters.add(swarms); + } + } + return encounters; + } + + private EncounterSet readOptionalEncountersHGSS(byte[] data, int offset, int amount) { + EncounterSet es = new EncounterSet(); + es.rate = 1; + for (int i = 0; i < amount; i++) { + int pokemon = readWord(data, offset + i * 2); + if (pokemon != 0) { + Encounter e = new Encounter(); + e.level = 1; + e.pokemon = pokes[pokemon]; + es.encounters.add(e); + } + } + return es; + } + + private Pokemon[] readPokemonHGSS(byte[] data, int offset, int amount) { + Pokemon[] pokesHere = new Pokemon[amount]; + for (int i = 0; i < amount; i++) { + pokesHere[i] = pokes[readWord(data, offset + i * 2)]; + } + return pokesHere; + } + + private List<Encounter> readSeaEncountersHGSS(byte[] data, int offset, int amount) { + List<Encounter> encounters = new ArrayList<Encounter>(); + for (int i = 0; i < amount; i++) { + int level = readWord(data, offset + i * 4); + int pokemon = readWord(data, offset + 2 + i * 4); + Encounter enc = new Encounter(); + enc.level = level & 0xFF; + enc.maxLevel = level >> 8; + enc.pokemon = pokes[pokemon]; + encounters.add(enc); + } + return encounters; + } + + @Override + public void setEncounters(boolean useTimeOfDay, List<EncounterSet> encounters) { + try { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + setEncountersHGSS(useTimeOfDay, encounters); + } else { + setEncountersDPPt(useTimeOfDay, encounters); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private void setEncountersDPPt(boolean useTimeOfDay, List<EncounterSet> encounterList) throws IOException { + // Determine file to use + String encountersFile = romEntry.getString("WildPokemon"); + NARCContents encounterData = readNARC(encountersFile); + Iterator<EncounterSet> encounters = encounterList.iterator(); + // Credit for + // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-sinnoh.py + // for the structure for this. + for (byte[] b : encounterData.files) { + int grassRate = readLong(b, 0); + if (grassRate != 0) { + // grass encounters are a-go + EncounterSet grass = encounters.next(); + writeEncountersDPPt(b, 4, grass.encounters, 12); + + // Time of day encounters? + int todEncounterSlot = 12; + for (int i = 0; i < 4; i++) { + int pknum = readLong(b, 108 + 4 * i); + if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { + // Valid time of day slot + if (useTimeOfDay) { + // Get custom randomized encounter + Pokemon pk = grass.encounters.get(todEncounterSlot++).pokemon; + writeLong(b, 108 + 4 * i, pk.number); + } else { + // Copy the original slot's randomized encounter + Pokemon pk = grass.encounters.get(Gen4Constants.dpptAlternateSlots[i + 2]).pokemon; + writeLong(b, 108 + 4 * i, pk.number); + } + } + } + + // Other conditional encounters? + Iterator<Encounter> condEncounters = null; + for (int i = 0; i < 20; i++) { + if (i >= 2 && i <= 5) { + // Time of day slot, handled already + continue; + } + int offs = 100 + i * 4 + (i >= 10 ? 24 : 0); + int pknum = readLong(b, offs); + if (pknum >= 1 && pknum <= Gen4Constants.pokemonCount) { + // This slot is used, grab a replacement. + if (condEncounters == null) { + // Fetch the set of conditional encounters for this + // area now that we know it's necessary and exists. + condEncounters = encounters.next().encounters.iterator(); + } + Pokemon pk = condEncounters.next().pokemon; + writeLong(b, offs, pk.number); + } + } + } + // up to 204, 5 special ones to go + // This is for surf, filler, old rod, good rod, super rod + // so we skip index 1 (filler) + int offset = 204; + for (int i = 0; i < 5; i++) { + int rate = readLong(b, offset); + offset += 4; + if (rate == 0 || i == 1) { + offset += 40; + continue; + } + + EncounterSet other = encounters.next(); + writeSeaEncountersDPPt(b, offset, other.encounters); + offset += 40; + } + } + + // Save + writeNARC(encountersFile, encounterData); + + } + + private void writeEncountersDPPt(byte[] data, int offset, List<Encounter> encounters, int enclength) { + for (int i = 0; i < enclength; i++) { + Encounter enc = encounters.get(i); + writeLong(data, offset + i * 8, enc.level); + writeLong(data, offset + i * 8 + 4, enc.pokemon.number); + } + } + + private void writeSeaEncountersDPPt(byte[] data, int offset, List<Encounter> encounters) { + int enclength = encounters.size(); + for (int i = 0; i < enclength; i++) { + Encounter enc = encounters.get(i); + writeLong(data, offset + i * 8, (enc.level << 8) + enc.maxLevel); + writeLong(data, offset + i * 8 + 4, enc.pokemon.number); + } + } + + private void setEncountersHGSS(boolean useTimeOfDay, List<EncounterSet> encounterList) throws IOException { + String encountersFile = romEntry.getString("WildPokemon"); + NARCContents encounterData = readNARC(encountersFile); + Iterator<EncounterSet> encounters = encounterList.iterator(); + // Credit for + // https://github.com/magical/pokemon-encounters/blob/master/nds/encounters-gen4-johto.py + // for the structure for this. + int[] amounts = new int[] { 0, 5, 2, 5, 5, 5 }; + for (byte[] b : encounterData.files) { + int[] rates = new int[6]; + rates[0] = b[0] & 0xFF; + rates[1] = b[1] & 0xFF; + rates[2] = b[2] & 0xFF; + rates[3] = b[3] & 0xFF; + rates[4] = b[4] & 0xFF; + rates[5] = b[5] & 0xFF; + // Up to 20 after the rates & levels + // Grass has to be handled on its own because the levels + // are reused for every time of day + if (rates[0] != 0) { + if (!useTimeOfDay) { + // Get a single set of encounters... + // Write the encounters we get 3x for morning, day, night + EncounterSet grass = encounters.next(); + writePokemonHGSS(b, 20, grass.encounters); + writePokemonHGSS(b, 44, grass.encounters); + writePokemonHGSS(b, 68, grass.encounters); + } else { + for (int i = 0; i < 3; i++) { + EncounterSet grass = encounters.next(); + writePokemonHGSS(b, 20 + i * 24, grass.encounters); + } + } + } + + // Write radio pokemon + writeOptionalEncountersHGSS(b, 92, 4, encounters); + + // Up to 100 now... 2*2*2 for radio pokemon + // Write rock smash, surf, et al + int offset = 100; + for (int i = 1; i < 6; i++) { + if (rates[i] != 0) { + // Valid area. + EncounterSet other = encounters.next(); + writeSeaEncountersHGSS(b, offset, other.encounters); + } + offset += 4 * amounts[i]; + } + + // Write swarm pokemon + writeOptionalEncountersHGSS(b, offset, 4, encounters); + } + + // Save + writeNARC(encountersFile, encounterData); + + } + + private void writeOptionalEncountersHGSS(byte[] data, int offset, int amount, Iterator<EncounterSet> encounters) { + Iterator<Encounter> eIter = null; + for (int i = 0; i < amount; i++) { + int origPokemon = readWord(data, offset + i * 2); + if (origPokemon != 0) { + // Need an encounter set, yes. + if (eIter == null) { + eIter = encounters.next().encounters.iterator(); + } + Encounter here = eIter.next(); + writeWord(data, offset + i * 2, here.pokemon.number); + } + } + + } + + private void writePokemonHGSS(byte[] data, int offset, List<Encounter> encounters) { + int enclength = encounters.size(); + for (int i = 0; i < enclength; i++) { + writeWord(data, offset + i * 2, encounters.get(i).pokemon.number); + } + + } + + private void writeSeaEncountersHGSS(byte[] data, int offset, List<Encounter> encounters) { + int enclength = encounters.size(); + for (int i = 0; i < enclength; i++) { + Encounter enc = encounters.get(i); + data[offset + i * 4] = (byte) enc.level; + data[offset + i * 4 + 1] = (byte) enc.maxLevel; + writeWord(data, offset + i * 4 + 2, enc.pokemon.number); + } + + } + + private List<Encounter> stitchEncsToLevels(Pokemon[] pokemon, int[] levels) { + List<Encounter> encounters = new ArrayList<Encounter>(); + for (int i = 0; i < pokemon.length; i++) { + Encounter enc = new Encounter(); + enc.level = levels[i]; + enc.pokemon = pokemon[i]; + encounters.add(enc); + } + return encounters; + } + + private void loadWildMapNames() { + try { + wildMapNames = new HashMap<Integer, String>(); + byte[] internalNames = this.readFile(romEntry.getString("MapTableFile")); + int numMapHeaders = internalNames.length / 16; + int baseMHOffset = romEntry.getInt("MapTableARM9Offset"); + List<String> allMapNames = getStrings(romEntry.getInt("MapNamesTextOffset")); + int mapNameIndexSize = romEntry.getInt("MapTableNameIndexSize"); + for (int map = 0; map < numMapHeaders; map++) { + int baseOffset = baseMHOffset + map * 24; + int mapNameIndex = (mapNameIndexSize == 2) ? readWord(arm9, baseOffset + 18) + : (arm9[baseOffset + 18] & 0xFF); + String mapName = allMapNames.get(mapNameIndex); + if (romEntry.romType == Gen4Constants.Type_HGSS) { + int wildSet = arm9[baseOffset] & 0xFF; + if (wildSet != 255) { + wildMapNames.put(wildSet, mapName); + } + } else { + int wildSet = readWord(arm9, baseOffset + 14); + if (wildSet != 65535) { + wildMapNames.put(wildSet, mapName); + } + } + } + loadedWildMapNames = true; + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + @Override + public List<Trainer> getTrainers() { + List<Trainer> allTrainers = new ArrayList<Trainer>(); + try { + NARCContents trainers = this.readNARC(romEntry.getString("TrainerData")); + NARCContents trpokes = this.readNARC(romEntry.getString("TrainerPokemon")); + List<String> tclasses = this.getTrainerClassNames(); + List<String> tnames = this.getTrainerNames(); + int trainernum = trainers.files.size(); + for (int i = 1; i < trainernum; i++) { + byte[] trainer = trainers.files.get(i); + byte[] trpoke = trpokes.files.get(i); + Trainer tr = new Trainer(); + tr.poketype = trainer[0] & 0xFF; + tr.trainerclass = trainer[1] & 0xFF; + tr.offset = i; + int numPokes = trainer[3] & 0xFF; + int pokeOffs = 0; + tr.fullDisplayName = tclasses.get(tr.trainerclass) + " " + tnames.get(i - 1); + // printBA(trpoke); + for (int poke = 0; poke < numPokes; poke++) { + int ailevel = trpoke[pokeOffs] & 0xFF; + int level = trpoke[pokeOffs + 2] & 0xFF; + int species = (trpoke[pokeOffs + 4] & 0xFF) + ((trpoke[pokeOffs + 5] & 0x01) << 8); + // int formnum = (trpoke[pokeOffs + 5] >> 2); + TrainerPokemon tpk = new TrainerPokemon(); + tpk.level = level; + tpk.pokemon = pokes[species]; + tpk.AILevel = ailevel; + tpk.ability = trpoke[pokeOffs + 1] & 0xFF; + pokeOffs += 6; + if (tr.poketype >= 2) { + int heldItem = readWord(trpoke, pokeOffs); + tpk.heldItem = heldItem; + pokeOffs += 2; + } + if (tr.poketype % 2 == 1) { + int attack1 = readWord(trpoke, pokeOffs); + int attack2 = readWord(trpoke, pokeOffs + 2); + int attack3 = readWord(trpoke, pokeOffs + 4); + int attack4 = readWord(trpoke, pokeOffs + 6); + tpk.move1 = attack1; + tpk.move2 = attack2; + tpk.move3 = attack3; + tpk.move4 = attack4; + pokeOffs += 8; + } + // Plat/HGSS have another random pokeOffs +=2 here. + if (romEntry.romType != Gen4Constants.Type_DP) { + pokeOffs += 2; + } + tr.pokemon.add(tpk); + } + allTrainers.add(tr); + } + if (romEntry.romType == Gen4Constants.Type_DP) { + Gen4Constants.tagTrainersDP(allTrainers); + } else if (romEntry.romType == Gen4Constants.Type_Plat) { + Gen4Constants.tagTrainersPt(allTrainers); + } else { + Gen4Constants.tagTrainersHGSS(allTrainers); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + return allTrainers; + } + + @Override + public void setTrainers(List<Trainer> trainerData) { + Iterator<Trainer> allTrainers = trainerData.iterator(); + try { + NARCContents trainers = this.readNARC(romEntry.getString("TrainerData")); + NARCContents trpokes = new NARCContents(); + // empty entry + trpokes.files.add(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }); + int trainernum = trainers.files.size(); + for (int i = 1; i < trainernum; i++) { + byte[] trainer = trainers.files.get(i); + Trainer tr = allTrainers.next(); + tr.poketype = 0; // write as type 0 for no item/moves + trainer[0] = (byte) tr.poketype; + int numPokes = tr.pokemon.size(); + trainer[3] = (byte) numPokes; + + int bytesNeeded = 6 * numPokes; + if (romEntry.romType != Gen4Constants.Type_DP) { + bytesNeeded += 2 * numPokes; + } + if (tr.poketype % 2 == 1) { + bytesNeeded += 8 * numPokes; + } + if (tr.poketype >= 2) { + bytesNeeded += 2 * numPokes; + } + byte[] trpoke = new byte[bytesNeeded]; + int pokeOffs = 0; + Iterator<TrainerPokemon> tpokes = tr.pokemon.iterator(); + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon tpk = tpokes.next(); + writeWord(trpoke, pokeOffs, tpk.AILevel); + writeWord(trpoke, pokeOffs + 2, tpk.level); + writeWord(trpoke, pokeOffs + 4, tpk.pokemon.number); + pokeOffs += 6; + if (tr.poketype >= 2) { + writeWord(trpoke, pokeOffs, tpk.heldItem); + pokeOffs += 2; + } + if (tr.poketype % 2 == 1) { + writeWord(trpoke, pokeOffs, tpk.move1); + writeWord(trpoke, pokeOffs + 2, tpk.move2); + writeWord(trpoke, pokeOffs + 4, tpk.move3); + writeWord(trpoke, pokeOffs + 6, tpk.move4); + pokeOffs += 8; + } + // Plat/HGSS have another random pokeOffs +=2 here. + if (romEntry.romType != Gen4Constants.Type_DP) { + pokeOffs += 2; + } + } + trpokes.files.add(trpoke); + } + this.writeNARC(romEntry.getString("TrainerData"), trainers); + this.writeNARC(romEntry.getString("TrainerPokemon"), trpokes); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + } + + @Override + public Map<Pokemon, List<MoveLearnt>> getMovesLearnt() { + Map<Pokemon, List<MoveLearnt>> movesets = new TreeMap<Pokemon, List<MoveLearnt>>(); + try { + NARCContents movesLearnt = this.readNARC(romEntry.getString("PokemonMovesets")); + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + Pokemon pkmn = pokes[i]; + byte[] rom = movesLearnt.files.get(i); + int moveDataLoc = 0; + List<MoveLearnt> learnt = new ArrayList<MoveLearnt>(); + while ((rom[moveDataLoc] & 0xFF) != 0xFF || (rom[moveDataLoc + 1] & 0xFF) != 0xFF) { + int move = (rom[moveDataLoc] & 0xFF); + int level = (rom[moveDataLoc + 1] & 0xFE) >> 1; + if ((rom[moveDataLoc + 1] & 0x01) == 0x01) { + move += 256; + } + MoveLearnt ml = new MoveLearnt(); + ml.level = level; + ml.move = move; + learnt.add(ml); + moveDataLoc += 2; + } + movesets.put(pkmn, learnt); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return movesets; + } + + @Override + public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets) { + int[] extraLearnSets = new int[] { 7, 13, 13 }; + // Build up a new NARC + NARCContents movesLearnt = new NARCContents(); + // The blank moveset + byte[] blankSet = new byte[] { (byte) 0xFF, (byte) 0xFF, 0, 0 }; + movesLearnt.files.add(blankSet); + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + Pokemon pkmn = pokes[i]; + List<MoveLearnt> learnt = movesets.get(pkmn); + int sizeNeeded = learnt.size() * 2 + 2; + if ((sizeNeeded % 4) != 0) { + sizeNeeded += 2; + } + byte[] moveset = new byte[sizeNeeded]; + int j = 0; + for (; j < learnt.size(); j++) { + MoveLearnt ml = learnt.get(j); + moveset[j * 2] = (byte) (ml.move & 0xFF); + int levelPart = (ml.level << 1) & 0xFE; + if (ml.move > 255) { + levelPart++; + } + moveset[j * 2 + 1] = (byte) levelPart; + } + moveset[j * 2] = (byte) 0xFF; + moveset[j * 2 + 1] = (byte) 0xFF; + movesLearnt.files.add(moveset); + } + for (int j = 0; j < extraLearnSets[romEntry.romType]; j++) { + movesLearnt.files.add(blankSet); + } + // Save + try { + this.writeNARC(romEntry.getString("PokemonMovesets"), movesLearnt); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + private static class StaticPokemon { + private int[] files; + private int[] offsets; + + public Pokemon getPokemon(Gen4RomHandler parent, NARCContents scriptNARC) { + return parent.pokes[parent.readWord(scriptNARC.files.get(files[0]), offsets[0])]; + } + + public void setPokemon(Gen4RomHandler parent, NARCContents scriptNARC, Pokemon pkmn) { + int value = pkmn.number; + for (int i = 0; i < offsets.length; i++) { + byte[] file = scriptNARC.files.get(files[i]); + parent.writeWord(file, offsets[i], value); + } + } + } + + @Override + public List<Pokemon> getStaticPokemon() { + List<Pokemon> sp = new ArrayList<Pokemon>(); + if (!romEntry.staticPokemonSupport) { + return sp; + } + try { + NARCContents scriptNARC = scriptNarc; + for (StaticPokemon statP : romEntry.staticPokemon) { + sp.add(statP.getPokemon(this, scriptNARC)); + } + if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { + NARCContents tradeNARC = this.readNARC(romEntry.getString("InGameTrades")); + int[] trades = romEntry.arrayEntries.get("StaticPokemonTrades"); + for (int tradeNum : trades) { + sp.add(pokes[readLong(tradeNARC.files.get(tradeNum), 0)]); + } + } + if (romEntry.getInt("MysteryEggOffset") > 0) { + byte[] ovOverlay = readOverlay(romEntry.getInt("MoveTutorMovesOvlNumber")); + sp.add(pokes[ovOverlay[romEntry.getInt("MysteryEggOffset")] & 0xFF]); + } + if (romEntry.getInt("FossilTableOffset") > 0) { + byte[] ftData = arm9; + int baseOffset = romEntry.getInt("FossilTableOffset"); + if (romEntry.romType == Gen4Constants.Type_HGSS) { + ftData = readOverlay(romEntry.getInt("FossilTableOvlNumber")); + } + // read the 7 Fossil Pokemon + for (int f = 0; f < Gen4Constants.fossilCount; f++) { + sp.add(pokes[readWord(ftData, baseOffset + 2 + f * 4)]); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return sp; + } + + @Override + public boolean setStaticPokemon(List<Pokemon> staticPokemon) { + if (!romEntry.staticPokemonSupport) { + return false; + } + int sptsize = romEntry.arrayEntries.containsKey("StaticPokemonTrades") ? romEntry.arrayEntries + .get("StaticPokemonTrades").length : 0; + int meggsize = romEntry.getInt("MysteryEggOffset") > 0 ? 1 : 0; + int fossilsize = romEntry.getInt("FossilTableOffset") > 0 ? 7 : 0; + if (staticPokemon.size() != romEntry.staticPokemon.size() + sptsize + meggsize + fossilsize) { + return false; + } + try { + Iterator<Pokemon> statics = staticPokemon.iterator(); + NARCContents scriptNARC = scriptNarc; + for (StaticPokemon statP : romEntry.staticPokemon) { + statP.setPokemon(this, scriptNARC, statics.next()); + } + if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { + NARCContents tradeNARC = this.readNARC(romEntry.getString("InGameTrades")); + int[] trades = romEntry.arrayEntries.get("StaticPokemonTrades"); + for (int tradeNum : trades) { + Pokemon thisTrade = statics.next(); + List<Integer> possibleAbilities = new ArrayList<Integer>(); + possibleAbilities.add(thisTrade.ability1); + if (thisTrade.ability2 > 0) { + possibleAbilities.add(thisTrade.ability2); + } + if (thisTrade.ability3 > 0) { + possibleAbilities.add(thisTrade.ability3); + } + // Write species and ability + writeLong(tradeNARC.files.get(tradeNum), 0, thisTrade.number); + writeLong(tradeNARC.files.get(tradeNum), 0x1C, + possibleAbilities.get(this.random.nextInt(possibleAbilities.size()))); + } + writeNARC(romEntry.getString("InGameTrades"), tradeNARC); + } + if (romEntry.getInt("MysteryEggOffset") > 0) { + // Same overlay as MT moves + // Truncate the pokemon# to 1byte, unless it's 0 + int pokenum = statics.next().number; + if (pokenum > 255) { + pokenum = this.random.nextInt(255) + 1; + } + byte[] ovOverlay = readOverlay(romEntry.getInt("MoveTutorMovesOvlNumber")); + ovOverlay[romEntry.getInt("MysteryEggOffset")] = (byte) pokenum; + writeOverlay(romEntry.getInt("MoveTutorMovesOvlNumber"), ovOverlay); + } + if (romEntry.getInt("FossilTableOffset") > 0) { + int baseOffset = romEntry.getInt("FossilTableOffset"); + if (romEntry.romType == Gen4Constants.Type_HGSS) { + byte[] ftData = readOverlay(romEntry.getInt("FossilTableOvlNumber")); + for (int f = 0; f < Gen4Constants.fossilCount; f++) { + int pokenum = statics.next().number; + writeWord(ftData, baseOffset + 2 + f * 4, pokenum); + } + writeOverlay(romEntry.getInt("FossilTableOvlNumber"), ftData); + } else { + // write to arm9 + for (int f = 0; f < Gen4Constants.fossilCount; f++) { + int pokenum = statics.next().number; + writeWord(arm9, baseOffset + 2 + f * 4, pokenum); + } + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return true; + } + + @Override + public List<Integer> getTMMoves() { + String tmDataPrefix; + if (romEntry.romType == Gen4Constants.Type_DP || romEntry.romType == Gen4Constants.Type_Plat) { + tmDataPrefix = Gen4Constants.dpptTMDataPrefix; + } else { + tmDataPrefix = Gen4Constants.hgssTMDataPrefix; + } + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += tmDataPrefix.length() / 2; // because it was a prefix + List<Integer> tms = new ArrayList<Integer>(); + for (int i = 0; i < Gen4Constants.tmCount; i++) { + tms.add(readWord(arm9, offset + i * 2)); + } + return tms; + } else { + return null; + } + } + + @Override + public List<Integer> getHMMoves() { + String tmDataPrefix; + if (romEntry.romType == Gen4Constants.Type_DP || romEntry.romType == Gen4Constants.Type_Plat) { + tmDataPrefix = Gen4Constants.dpptTMDataPrefix; + } else { + tmDataPrefix = Gen4Constants.hgssTMDataPrefix; + } + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += tmDataPrefix.length() / 2; // because it was a prefix + offset += Gen4Constants.tmCount * 2; // TM data + List<Integer> hms = new ArrayList<Integer>(); + for (int i = 0; i < Gen4Constants.hmCount; i++) { + hms.add(readWord(arm9, offset + i * 2)); + } + return hms; + } else { + return null; + } + } + + @Override + public void setTMMoves(List<Integer> moveIndexes) { + String tmDataPrefix; + if (romEntry.romType == Gen4Constants.Type_DP || romEntry.romType == Gen4Constants.Type_Plat) { + tmDataPrefix = Gen4Constants.dpptTMDataPrefix; + } else { + tmDataPrefix = Gen4Constants.hgssTMDataPrefix; + } + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += tmDataPrefix.length() / 2; // because it was a prefix + for (int i = 0; i < Gen4Constants.tmCount; i++) { + writeWord(arm9, offset + i * 2, moveIndexes.get(i)); + } + + // Update TM item descriptions + List<String> itemDescriptions = getStrings(romEntry.getInt("ItemDescriptionsTextOffset")); + List<String> moveDescriptions = getStrings(romEntry.getInt("MoveDescriptionsTextOffset")); + // TM01 is item 328 and so on + for (int i = 0; i < Gen4Constants.tmCount; i++) { + // Rewrite 5-line move descs into 3-line item descs + itemDescriptions.set(i + Gen4Constants.tmItemOffset, RomFunctions.rewriteDescriptionForNewLineSize( + moveDescriptions.get(moveIndexes.get(i)), "\\n", Gen4Constants.textCharsPerLine, ssd)); + } + // Save the new item descriptions + setStrings(romEntry.getInt("ItemDescriptionsTextOffset"), itemDescriptions); + // Palettes update + String baseOfPalettes = Gen4Constants.pthgssItemPalettesPrefix; + if (romEntry.romType == Gen4Constants.Type_DP) { + baseOfPalettes = Gen4Constants.dpItemPalettesPrefix; + } + int offsPals = find(arm9, baseOfPalettes); + if (offsPals > 0) { + // Write pals + for (int i = 0; i < Gen4Constants.tmCount; i++) { + Move m = this.moves[moveIndexes.get(i)]; + int pal = this.typeTMPaletteNumber(m.type); + writeWord(arm9, offsPals + i * 8 + 2, pal); + } + } + // if we can't update the palettes its not a big deal... + } else { + } + } + + private static RomFunctions.StringSizeDeterminer ssd = new RomFunctions.StringLengthSD(); + + @Override + public int getTMCount() { + return Gen4Constants.tmCount; + } + + @Override + public int getHMCount() { + return Gen4Constants.hmCount; + } + + @Override + public Map<Pokemon, boolean[]> getTMHMCompatibility() { + Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + byte[] data = pokeNarc.files.get(i); + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen4Constants.tmCount + Gen4Constants.hmCount + 1]; + for (int j = 0; j < 13; j++) { + readByteIntoFlags(data, flags, j * 8 + 1, Gen4Constants.bsTMHMCompatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData) { + for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + byte[] data = pokeNarc.files.get(pkmn.number); + for (int j = 0; j < 13; j++) { + data[Gen4Constants.bsTMHMCompatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public boolean hasMoveTutors() { + return romEntry.romType != Gen4Constants.Type_DP; + } + + @Override + public List<Integer> getMoveTutorMoves() { + if (!hasMoveTutors()) { + return new ArrayList<Integer>(); + } + int baseOffset = romEntry.getInt("MoveTutorMovesOffset"); + int amount = romEntry.getInt("MoveTutorCount"); + int bytesPer = romEntry.getInt("MoveTutorBytesCount"); + List<Integer> mtMoves = new ArrayList<Integer>(); + try { + byte[] mtFile = readOverlay(romEntry.getInt("MoveTutorMovesOvlNumber")); + for (int i = 0; i < amount; i++) { + mtMoves.add(readWord(mtFile, baseOffset + i * bytesPer)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return mtMoves; + } + + @Override + public void setMoveTutorMoves(List<Integer> moves) { + if (!hasMoveTutors()) { + return; + } + int baseOffset = romEntry.getInt("MoveTutorMovesOffset"); + int amount = romEntry.getInt("MoveTutorCount"); + int bytesPer = romEntry.getInt("MoveTutorBytesCount"); + if (moves.size() != amount) { + return; + } + try { + byte[] mtFile = readOverlay(romEntry.getInt("MoveTutorMovesOvlNumber")); + for (int i = 0; i < amount; i++) { + writeWord(mtFile, baseOffset + i * bytesPer, moves.get(i)); + } + writeOverlay(romEntry.getInt("MoveTutorMovesOvlNumber"), mtFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public Map<Pokemon, boolean[]> getMoveTutorCompatibility() { + if (!hasMoveTutors()) { + return new TreeMap<Pokemon, boolean[]>(); + } + Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); + int amount = romEntry.getInt("MoveTutorCount"); + int baseOffset = romEntry.getInt("MoveTutorCompatOffset"); + int bytesPer = romEntry.getInt("MoveTutorCompatBytesCount"); + try { + byte[] mtcFile; + if (romEntry.romType == Gen4Constants.Type_HGSS) { + mtcFile = readFile(romEntry.getString("MoveTutorCompat")); + } else { + mtcFile = readOverlay(romEntry.getInt("MoveTutorCompatOvlNumber")); + } + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[amount + 1]; + for (int j = 0; j < bytesPer; j++) { + readByteIntoFlags(mtcFile, flags, j * 8 + 1, baseOffset + (i - 1) * bytesPer + j); + } + compat.put(pkmn, flags); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return compat; + } + + @Override + public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData) { + if (!hasMoveTutors()) { + return; + } + int amount = romEntry.getInt("MoveTutorCount"); + int baseOffset = romEntry.getInt("MoveTutorCompatOffset"); + int bytesPer = romEntry.getInt("MoveTutorCompatBytesCount"); + try { + byte[] mtcFile; + if (romEntry.romType == Gen4Constants.Type_HGSS) { + mtcFile = readFile(romEntry.getString("MoveTutorCompat")); + } else { + mtcFile = readOverlay(romEntry.getInt("MoveTutorCompatOvlNumber")); + } + for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + for (int j = 0; j < bytesPer; j++) { + int offsHere = baseOffset + (pkmn.number - 1) * bytesPer + j; + if (j * 8 + 8 <= amount) { + // entirely new byte + mtcFile[offsHere] = getByteFromFlags(flags, j * 8 + 1); + } else if (j * 8 < amount) { + // need some of the original byte + int newByte = getByteFromFlags(flags, j * 8 + 1) & 0xFF; + int oldByteParts = (mtcFile[offsHere] >>> (8 - amount + j * 8)) << (8 - amount + j * 8); + mtcFile[offsHere] = (byte) (newByte | oldByteParts); + } + // else do nothing to the byte + } + } + if (romEntry.romType == Gen4Constants.Type_HGSS) { + writeFile(romEntry.getString("MoveTutorCompat"), mtcFile); + } else { + writeOverlay(romEntry.getInt("MoveTutorCompatOvlNumber"), mtcFile); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private int find(byte[] data, String hexString) { + if (hexString.length() % 2 != 0) { + return -3; // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + List<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); + } + } + + private boolean lastStringsCompressed = false; + + private List<String> getStrings(int index) { + PokeTextData pt = new PokeTextData(msgNarc.files.get(index)); + pt.decrypt(); + lastStringsCompressed = pt.compressFlag; + return new ArrayList<String>(pt.strlist); + } + + private void setStrings(int index, List<String> newStrings) { + setStrings(index, newStrings, false); + } + + private void setStrings(int index, List<String> newStrings, boolean compressed) { + byte[] rawUnencrypted = TextToPoke.MakeFile(newStrings, compressed); + + // make new encrypted name set + PokeTextData encrypt = new PokeTextData(rawUnencrypted); + encrypt.SetKey(0xD00E); + encrypt.encrypt(); + + // rewrite + msgNarc.files.set(index, encrypt.get()); + } + + @Override + public String getROMName() { + return "Pokemon " + romEntry.name; + } + + @Override + public String getROMCode() { + return romEntry.romCode; + } + + @Override + public String getSupportLevel() { + return romEntry.staticPokemonSupport ? "Complete" : "No Static Pokemon"; + } + + @Override + public boolean hasTimeBasedEncounters() { + // dppt technically do but we ignore them completely + return romEntry.romType == Gen4Constants.Type_HGSS; + } + + @Override + public boolean canChangeStaticPokemon() { + return romEntry.staticPokemonSupport; + } + + @Override + public boolean canChangeStarters() { + return true; + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + // Read NARC + try { + NARCContents evoNARC = readNARC(romEntry.getString("PokemonEvolutions")); + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + Pokemon pk = pokes[i]; + byte[] evoEntry = evoNARC.files.get(i); + for (int evo = 0; evo < 7; evo++) { + int method = readWord(evoEntry, evo * 6); + int species = readWord(evoEntry, evo * 6 + 4); + if (method >= 1 && method <= Gen4Constants.evolutionMethodCount && species >= 1) { + EvolutionType et = EvolutionType.fromIndex(4, method); + int extraInfo = readWord(evoEntry, evo * 6 + 2); + Evolution evol = new Evolution(pokes[i], pokes[species], true, et, extraInfo); + if (!pk.evolutionsFrom.contains(evol)) { + pk.evolutionsFrom.add(evol); + pokes[species].evolutionsTo.add(evol); + } + } + } + // split evos don't carry stats + if (pk.evolutionsFrom.size() > 1) { + for (Evolution e : pk.evolutionsFrom) { + e.carryStats = false; + } + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void writeEvolutions() { + try { + NARCContents evoNARC = readNARC(romEntry.getString("PokemonEvolutions")); + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + byte[] evoEntry = evoNARC.files.get(i); + int evosWritten = 0; + Pokemon pk = pokes[i]; + for (Evolution evo : pk.evolutionsFrom) { + writeWord(evoEntry, evosWritten * 6, evo.type.toIndex(4)); + writeWord(evoEntry, evosWritten * 6 + 2, evo.extraInfo); + writeWord(evoEntry, evosWritten * 6 + 4, evo.to.number); + evosWritten++; + if (evosWritten == 7) { + break; + } + } + while (evosWritten < 7) { + writeWord(evoEntry, evosWritten * 6, 0); + writeWord(evoEntry, evosWritten * 6 + 2, 0); + writeWord(evoEntry, evosWritten * 6 + 4, 0); + evosWritten++; + } + } + writeNARC(romEntry.getString("PokemonEvolutions"), evoNARC); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void removeTradeEvolutions(boolean changeMoveEvos) { + Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); + log("--Removing Trade Evolutions--"); + Set<Evolution> extraEvolutions = new HashSet<Evolution>(); + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + extraEvolutions.clear(); + for (Evolution evo : pkmn.evolutionsFrom) { + // new 160 other impossible evolutions: + if (romEntry.romType == Gen4Constants.Type_HGSS) { + // beauty milotic + if (evo.type == EvolutionType.LEVEL_HIGH_BEAUTY) { + // Replace w/ level 35 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 35; + logEvoChangeLevel(evo.from.name, evo.to.name, 35); + } + // mt.coronet (magnezone/probopass) + if (evo.type == EvolutionType.LEVEL_ELECTRIFIED_AREA) { + // Replace w/ level 40 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 40; + logEvoChangeLevel(evo.from.name, evo.to.name, 40); + } + // moss rock (leafeon) + if (evo.type == EvolutionType.LEVEL_MOSS_ROCK) { + // Replace w/ leaf stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Gen4Constants.leafStoneIndex; // leaf + // stone + logEvoChangeStone(evo.from.name, evo.to.name, itemNames.get(Gen4Constants.leafStoneIndex)); + } + // icy rock (glaceon) + if (evo.type == EvolutionType.LEVEL_ICY_ROCK) { + // Replace w/ dawn stone + evo.type = EvolutionType.STONE; + evo.extraInfo = Gen4Constants.dawnStoneIndex; // dawn + // stone + logEvoChangeStone(evo.from.name, evo.to.name, itemNames.get(Gen4Constants.dawnStoneIndex)); + } + } + if (changeMoveEvos && evo.type == EvolutionType.LEVEL_WITH_MOVE) { + // read move + int move = evo.extraInfo; + int levelLearntAt = 1; + for (MoveLearnt ml : movesets.get(evo.from)) { + 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; + logEvoChangeLevel(evo.from.name, evo.to.name, levelLearntAt); + } + // Pure Trade + if (evo.type == EvolutionType.TRADE) { + // Replace w/ level 37 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + logEvoChangeLevel(evo.from.name, evo.to.name, 37); + } + // Trade w/ Item + if (evo.type == EvolutionType.TRADE_ITEM) { + // Get the current item & evolution + int item = evo.extraInfo; + if (evo.from.number == Gen4Constants.slowpokeIndex) { + // 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 = Gen4Constants.waterStoneIndex; // water + // stone + logEvoChangeStone(evo.from.name, evo.to.name, itemNames.get(Gen4Constants.waterStoneIndex)); + } else { + logEvoChangeLevelWithItem(evo.from.name, evo.to.name, itemNames.get(item)); + // Replace, for this entry, w/ + // Level up w/ Held Item at Day + evo.type = EvolutionType.LEVEL_ITEM_DAY; + // now add an extra evo for + // Level up w/ Held Item at Night + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_NIGHT, item); + extraEvolutions.add(extraEntry); + } + } + } + pkmn.evolutionsFrom.addAll(extraEvolutions); + for (Evolution ev : extraEvolutions) { + ev.to.evolutionsTo.add(ev); + } + } + } + logBlankLine(); + + } + + @Override + public List<String> getTrainerNames() { + List<String> tnames = new ArrayList<String>(getStrings(romEntry.getInt("TrainerNamesTextOffset"))); + tnames.remove(0); // blank one + for (int i = 0; i < tnames.size(); i++) { + if (tnames.get(i).contains("\\and")) { + tnames.set(i, tnames.get(i).replace("\\and", "&")); + } + } + return tnames; + } + + @Override + public int maxTrainerNameLength() { + return 10;// based off the english ROMs fixed + } + + @Override + public void setTrainerNames(List<String> trainerNames) { + List<String> oldTNames = getStrings(romEntry.getInt("TrainerNamesTextOffset")); + List<String> newTNames = new ArrayList<String>(trainerNames); + for (int i = 0; i < newTNames.size(); i++) { + if (newTNames.get(i).contains("&")) { + newTNames.set(i, newTNames.get(i).replace("&", "\\and")); + } + } + newTNames.add(0, oldTNames.get(0)); // the 0-entry, preserve it + + // rewrite, only compressed if they were compressed before + setStrings(romEntry.getInt("TrainerNamesTextOffset"), newTNames, lastStringsCompressed); + + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.MAX_LENGTH; + } + + @Override + public List<Integer> getTCNameLengthsByTrainer() { + // not needed + return new ArrayList<Integer>(); + } + + @Override + public List<String> getTrainerClassNames() { + return getStrings(romEntry.getInt("TrainerClassesTextOffset")); + } + + @Override + public void setTrainerClassNames(List<String> trainerClassNames) { + setStrings(romEntry.getInt("TrainerClassesTextOffset"), trainerClassNames); + } + + @Override + public int maxTrainerClassNameLength() { + return 12;// based off the english ROMs + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return false; + } + + @Override + public String getDefaultExtension() { + return "nds"; + } + + @Override + public int abilitiesPerPokemon() { + return 2; + } + + @Override + public int highestAbilityIndex() { + return Gen4Constants.highestAbilityIndex; + } + + @Override + public int internalStringLength(String string) { + return string.length(); + } + + @Override + public void applySignature() { + // For now, do nothing. + + } + + @Override + public ItemList getAllowedItems() { + return Gen4Constants.allowedItems; + } + + @Override + public ItemList getNonBadItems() { + return Gen4Constants.nonBadItems; + } + + @Override + public String[] getItemNames() { + return itemNames.toArray(new String[0]); + } + + @Override + public String abilityName(int number) { + return abilityNames.get(number); + } + + private List<Integer> getFieldItems() { + List<Integer> fieldItems = new ArrayList<Integer>(); + // normal items + int scriptFile = romEntry.getInt("ItemBallsScriptOffset"); + byte[] itemScripts = scriptNarc.files.get(scriptFile); + int offset = 0; + int skipTableOffset = 0; + int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); + int setVar = romEntry.romType == Gen4Constants.Type_HGSS ? Gen4Constants.hgssSetVarScript + : Gen4Constants.dpptSetVarScript; + while (true) { + int part1 = readWord(itemScripts, offset); + if (part1 == Gen4Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(itemScripts, offset); + offset += 4; + if (skipTableOffset < skipTable.length && (skipTable[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(itemScripts, offsetInFile); + int variable = readWord(itemScripts, offsetInFile + 2); + if (command == setVar && variable == Gen4Constants.itemScriptVariable) { + int item = readWord(itemScripts, offsetInFile + 4); + fieldItems.add(item); + } + + } + + // hidden items + int hiTableOffset = romEntry.getInt("HiddenItemTableOffset"); + int hiTableLimit = romEntry.getInt("HiddenItemCount"); + for (int i = 0; i < hiTableLimit; i++) { + int item = readWord(arm9, hiTableOffset + i * 8); + fieldItems.add(item); + } + + return fieldItems; + } + + private void setFieldItems(List<Integer> fieldItems) { + Iterator<Integer> iterItems = fieldItems.iterator(); + + // normal items + int scriptFile = romEntry.getInt("ItemBallsScriptOffset"); + byte[] itemScripts = scriptNarc.files.get(scriptFile); + int offset = 0; + int skipTableOffset = 0; + int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); + int setVar = romEntry.romType == Gen4Constants.Type_HGSS ? Gen4Constants.hgssSetVarScript + : Gen4Constants.dpptSetVarScript; + while (true) { + int part1 = readWord(itemScripts, offset); + if (part1 == Gen4Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(itemScripts, offset); + offset += 4; + if (skipTableOffset < skipTable.length && (skipTable[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(itemScripts, offsetInFile); + int variable = readWord(itemScripts, offsetInFile + 2); + if (command == setVar && variable == Gen4Constants.itemScriptVariable) { + int item = iterItems.next(); + writeWord(itemScripts, offsetInFile + 4, item); + } + } + + // hidden items + int hiTableOffset = romEntry.getInt("HiddenItemTableOffset"); + int hiTableLimit = romEntry.getInt("HiddenItemCount"); + for (int i = 0; i < hiTableLimit; i++) { + int item = iterItems.next(); + writeWord(arm9, hiTableOffset + i * 8, item); + } + } + + @Override + public List<Integer> getRequiredFieldTMs() { + if (romEntry.romType == Gen4Constants.Type_DP) { + return Gen4Constants.dpRequiredFieldTMs; + } else if (romEntry.romType == Gen4Constants.Type_Plat) { + // same as DP just we have to keep the weather TMs + return Gen4Constants.ptRequiredFieldTMs; + } + return new ArrayList<Integer>(); + } + + @Override + public List<Integer> getCurrentFieldTMs() { + List<Integer> fieldItems = this.getFieldItems(); + List<Integer> fieldTMs = new ArrayList<Integer>(); + + for (int item : fieldItems) { + if (Gen4Constants.allowedItems.isTM(item)) { + fieldTMs.add(item - Gen4Constants.tmItemOffset + 1); + } + } + + return fieldTMs; + } + + @Override + public void setFieldTMs(List<Integer> fieldTMs) { + List<Integer> fieldItems = this.getFieldItems(); + int fiLength = fieldItems.size(); + Iterator<Integer> iterTMs = fieldTMs.iterator(); + + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (Gen4Constants.allowedItems.isTM(oldItem)) { + int newItem = iterTMs.next() + Gen4Constants.tmItemOffset - 1; + fieldItems.set(i, newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List<Integer> getRegularFieldItems() { + List<Integer> fieldItems = this.getFieldItems(); + List<Integer> fieldRegItems = new ArrayList<Integer>(); + + for (int item : fieldItems) { + if (Gen4Constants.allowedItems.isAllowed(item) && !(Gen4Constants.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(); + + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (!(Gen4Constants.allowedItems.isTM(oldItem)) && Gen4Constants.allowedItems.isAllowed(oldItem)) { + int newItem = iterNewItems.next(); + fieldItems.set(i, newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List<IngameTrade> getIngameTrades() { + List<IngameTrade> trades = new ArrayList<IngameTrade>(); + try { + NARCContents tradeNARC = this.readNARC(romEntry.getString("InGameTrades")); + int[] spTrades = new int[0]; + if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { + spTrades = romEntry.arrayEntries.get("StaticPokemonTrades"); + } + List<String> tradeStrings = getStrings(romEntry.getInt("IngameTradesTextOffset")); + int tradeCount = tradeNARC.files.size(); + for (int i = 0; i < tradeCount; i++) { + boolean isSP = false; + for (int j = 0; j < spTrades.length; j++) { + if (spTrades[j] == i) { + isSP = true; + break; + } + } + if (isSP) { + continue; + } + byte[] tfile = tradeNARC.files.get(i); + IngameTrade trade = new IngameTrade(); + trade.nickname = tradeStrings.get(i); + trade.givenPokemon = pokes[readLong(tfile, 0)]; + trade.ivs = new int[6]; + for (int iv = 0; iv < 6; iv++) { + trade.ivs[iv] = readLong(tfile, 4 + iv * 4); + } + trade.otId = readWord(tfile, 0x20); + trade.otName = tradeStrings.get(i + tradeCount); + trade.item = readLong(tfile, 0x3C); + trade.requestedPokemon = pokes[readLong(tfile, 0x4C)]; + trades.add(trade); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + return trades; + } + + @Override + public void setIngameTrades(List<IngameTrade> trades) { + int tradeOffset = 0; + List<IngameTrade> oldTrades = this.getIngameTrades(); + try { + NARCContents tradeNARC = this.readNARC(romEntry.getString("InGameTrades")); + int[] spTrades = new int[0]; + if (romEntry.arrayEntries.containsKey("StaticPokemonTrades")) { + spTrades = romEntry.arrayEntries.get("StaticPokemonTrades"); + } + List<String> tradeStrings = getStrings(romEntry.getInt("IngameTradesTextOffset")); + int tradeCount = tradeNARC.files.size(); + for (int i = 0; i < tradeCount; i++) { + boolean isSP = false; + for (int j = 0; j < spTrades.length; j++) { + if (spTrades[j] == i) { + isSP = true; + break; + } + } + if (isSP) { + continue; + } + byte[] tfile = tradeNARC.files.get(i); + IngameTrade trade = trades.get(tradeOffset++); + tradeStrings.set(i, trade.nickname); + tradeStrings.set(i + tradeCount, trade.otName); + writeLong(tfile, 0, trade.givenPokemon.number); + for (int iv = 0; iv < 6; iv++) { + writeLong(tfile, 4 + iv * 4, trade.ivs[iv]); + } + writeWord(tfile, 0x20, trade.otId); + writeLong(tfile, 0x3C, trade.item); + writeLong(tfile, 0x4C, trade.requestedPokemon.number); + if (tfile.length > 0x50) { + writeLong(tfile, 0x50, 0); // disable gender + } + } + this.writeNARC(romEntry.getString("InGameTrades"), tradeNARC); + this.setStrings(romEntry.getInt("IngameTradesTextOffset"), tradeStrings); + // update what the people say when they talk to you + if (romEntry.arrayEntries.containsKey("IngameTradePersonTextOffsets")) { + int[] textOffsets = romEntry.arrayEntries.get("IngameTradePersonTextOffsets"); + for (int trade = 0; trade < textOffsets.length; trade++) { + if (textOffsets[trade] > 0) { + if (trade >= oldTrades.size() || trade >= trades.size()) { + break; + } + IngameTrade oldTrade = oldTrades.get(trade); + IngameTrade newTrade = trades.get(trade); + Map<String, String> replacements = new TreeMap<String, String>(); + replacements.put(oldTrade.givenPokemon.name, newTrade.givenPokemon.name); + if (oldTrade.requestedPokemon != newTrade.requestedPokemon) { + replacements.put(oldTrade.requestedPokemon.name, newTrade.requestedPokemon.name); + } + replaceAllStringsInEntry(textOffsets[trade], replacements, Gen4Constants.textCharsPerLine); + // hgss override for one set of strings that appears 2x + if (romEntry.romType == Gen4Constants.Type_HGSS && trade == 6) { + replaceAllStringsInEntry(textOffsets[trade] + 1, replacements, + Gen4Constants.textCharsPerLine); + } + } + } + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private void replaceAllStringsInEntry(int entry, Map<String, String> replacements, int lineLength) { + List<String> thisTradeStrings = this.getStrings(entry); + int ttsCount = thisTradeStrings.size(); + for (int strNum = 0; strNum < ttsCount; strNum++) { + String oldString = thisTradeStrings.get(strNum); + String newString = RomFunctions.formatTextWithReplacements(oldString, replacements, "\\n", "\\l", "\\p", + lineLength, ssd); + thisTradeStrings.set(strNum, newString); + } + this.setStrings(entry, thisTradeStrings); + } + + @Override + public boolean hasDVs() { + return false; + } + + @Override + public int generationOfPokemon() { + return 4; + } + + @Override + public void removeEvosForPokemonPool() { + // slightly more complicated than gen2/3 + // we have to update a "baby table" too + List<Pokemon> pokemonIncluded = this.mainPokemonList; + Set<Evolution> keepEvos = new HashSet<Evolution>(); + for (Pokemon pk : pokes) { + if (pk != null) { + keepEvos.clear(); + for (Evolution evol : pk.evolutionsFrom) { + if (pokemonIncluded.contains(evol.from) && pokemonIncluded.contains(evol.to)) { + keepEvos.add(evol); + } else { + evol.to.evolutionsTo.remove(evol); + } + } + pk.evolutionsFrom.retainAll(keepEvos); + } + } + + try { + byte[] babyPokes = readFile(romEntry.getString("BabyPokemon")); + // baby pokemon + for (int i = 1; i <= Gen4Constants.pokemonCount; i++) { + Pokemon baby = pokes[i]; + while (baby.evolutionsTo.size() > 0) { + // Grab the first "to evolution" even if there are multiple + baby = baby.evolutionsTo.get(0).from; + } + writeWord(babyPokes, i * 2, baby.number); + } + // finish up + writeFile(romEntry.getString("BabyPokemon"), babyPokes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean supportsFourStartingMoves() { + return true; + } + + @Override + public List<Integer> getFieldMoves() { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + return Gen4Constants.hgssFieldMoves; + } else { + return Gen4Constants.dpptFieldMoves; + } + } + + @Override + public List<Integer> getEarlyRequiredHMMoves() { + if (romEntry.romType == Gen4Constants.Type_HGSS) { + return Gen4Constants.hgssEarlyRequiredHMMoves; + } else { + return Gen4Constants.dpptEarlyRequiredHMMoves; + } + } + + @Override + public int miscTweaksAvailable() { + int available = MiscTweak.LOWER_CASE_POKEMON_NAMES.getValue(); + available |= MiscTweak.RANDOMIZE_CATCHING_TUTORIAL.getValue(); + if (romEntry.tweakFiles.get("FastestTextTweak") != null) { + available |= MiscTweak.FASTEST_TEXT.getValue(); + } + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.LOWER_CASE_POKEMON_NAMES) { + applyCamelCaseNames(); + } else if (tweak == MiscTweak.RANDOMIZE_CATCHING_TUTORIAL) { + randomizeCatchingTutorial(); + } else if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestText(); + } + } + + private void randomizeCatchingTutorial() { + int opponentOffset = romEntry.getInt("CatchingTutorialOpponentMonOffset"); + + if (romEntry.romType == Gen4Constants.Type_HGSS) { + // Can randomize player mon too, but both limited to 1-255 + int playerOffset = romEntry.getInt("CatchingTutorialPlayerMonOffset"); + + Pokemon opponent = randomPokemonLimited(255, false); + Pokemon player = randomPokemonLimited(255, false); + + if (opponent != null && player != null) { + arm9[opponentOffset] = (byte) opponent.number; + arm9[playerOffset] = (byte) player.number; + } + } else { + // Only opponent, but enough space for any mon + Pokemon opponent = randomPokemonLimited(Integer.MAX_VALUE, false); + + if (opponent != null) { + writeLong(arm9, opponentOffset, opponent.number); + } + } + + } + + private void applyFastestText() { + genericIPSPatch(arm9, "FastestTextTweak"); + } + + private boolean genericIPSPatch(byte[] data, String ctName) { + String patchName = romEntry.tweakFiles.get(ctName); + if (patchName == null) { + return false; + } + + try { + FileFunctions.applyPatch(data, patchName); + return true; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private Pokemon randomPokemonLimited(int maxValue, boolean blockNonMales) { + checkPokemonRestrictions(); + List<Pokemon> validPokemon = new ArrayList<Pokemon>(); + for (Pokemon pk : this.mainPokemonList) { + if (pk.number <= maxValue && (!blockNonMales || pk.genderRatio <= 0xFD)) { + validPokemon.add(pk); + } + } + if (validPokemon.size() == 0) { + return null; + } else { + return validPokemon.get(random.nextInt(validPokemon.size())); + } + } + + @Override + public BufferedImage getMascotImage() { + try { + Pokemon pk = randomPokemon(); + NARCContents pokespritesNARC = this.readNARC(romEntry.getString("PokemonGraphics")); + int spriteIndex = pk.number * 6 + 2 + random.nextInt(2); + int palIndex = pk.number * 6 + 4; + if (random.nextInt(10) == 0) { + // shiny + palIndex++; + } + + // read sprite + byte[] rawSprite = pokespritesNARC.files.get(spriteIndex); + if (rawSprite.length == 0) { + // Must use other gender form + rawSprite = pokespritesNARC.files.get(spriteIndex ^ 1); + } + int[] spriteData = new int[3200]; + for (int i = 0; i < 3200; i++) { + spriteData[i] = readWord(rawSprite, i * 2 + 48); + } + + // Decrypt sprite (why does EVERYTHING use the RNG formula geez) + if (romEntry.romType != Gen4Constants.Type_DP) { + int key = spriteData[0]; + for (int i = 0; i < 3200; i++) { + spriteData[i] ^= (key & 0xFFFF); + key = key * 0x41C64E6D + 0x6073; + } + } else { + // D/P sprites are encrypted *backwards*. Wut. + int key = spriteData[3199]; + for (int i = 3199; i >= 0; i--) { + spriteData[i] ^= (key & 0xFFFF); + key = key * 0x41C64E6D + 0x6073; + } + } + + byte[] rawPalette = pokespritesNARC.files.get(palIndex); + + int[] palette = new int[16]; + for (int i = 1; i < 16; i++) { + palette[i] = GFXFunctions.conv16BitColorToARGB(readWord(rawPalette, 40 + i * 2)); + } + + // Deliberately chop off the right half of the image while still + // correctly indexing the array. + BufferedImage bim = new BufferedImage(80, 80, BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < 80; y++) { + for (int x = 0; x < 80; x++) { + int value = ((spriteData[y * 40 + x / 4]) >> (x % 4) * 4) & 0x0F; + bim.setRGB(x, y, palette[value]); + } + } + return bim; + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/com/dabomstew/pkrandom/romhandlers/Gen5RomHandler.java b/src/com/dabomstew/pkrandom/romhandlers/Gen5RomHandler.java index 2e713b9..d341ba6 100755 --- a/src/com/dabomstew/pkrandom/romhandlers/Gen5RomHandler.java +++ b/src/com/dabomstew/pkrandom/romhandlers/Gen5RomHandler.java @@ -63,2453 +63,2289 @@ import compressors.DSDecmp; public class Gen5RomHandler extends AbstractDSRomHandler { - public static class Factory extends RomHandler.Factory { - - @Override - public Gen5RomHandler create(Random random, PrintStream logStream) { - return new Gen5RomHandler(random, logStream); - } - - public boolean isLoadable(String filename) { - return detectNDSRomInner(getROMCodeFromFile(filename)); - } - } - - public Gen5RomHandler(Random random) { - super(random, null); - } - - public Gen5RomHandler(Random random, PrintStream logStream) { - super(random, logStream); - } - - private static class OffsetWithinEntry { - private int entry; - private int offset; - } - - private static class RomEntry { - private String name; - private String romCode; - private int romType; - private boolean staticPokemonSupport = false, - copyStaticPokemon = false; - private Map<String, String> strings = new HashMap<String, String>(); - private Map<String, Integer> numbers = new HashMap<String, Integer>(); - private Map<String, String> tweakFiles = new HashMap<String, String>(); - private Map<String, int[]> arrayEntries = new HashMap<String, int[]>(); - private Map<String, OffsetWithinEntry[]> offsetArrayEntries = new HashMap<String, OffsetWithinEntry[]>(); - private List<StaticPokemon> staticPokemon = new ArrayList<StaticPokemon>(); - - 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 static List<RomEntry> roms; - - static { - loadROMInfo(); - } - - private static void loadROMInfo() { - roms = new ArrayList<RomEntry>(); - RomEntry current = null; - try { - Scanner sc = new Scanner( - FileFunctions.openConfig("gen5_offsets.ini"), "UTF-8"); - while (sc.hasNextLine()) { - String q = sc.nextLine().trim(); - if (q.contains("//")) { - q = q.substring(0, q.indexOf("//")).trim(); - } - if (!q.isEmpty()) { - if (q.startsWith("[") && q.endsWith("]")) { - // New rom - current = new RomEntry(); - current.name = q.substring(1, q.length() - 1); - roms.add(current); - } else { - String[] r = q.split("=", 2); - if (r.length == 1) { - System.err.println("invalid entry " + q); - continue; - } - if (r[1].endsWith("\r\n")) { - r[1] = r[1].substring(0, r[1].length() - 2); - } - r[1] = r[1].trim(); - if (r[0].equals("Game")) { - current.romCode = r[1]; - } else if (r[0].equals("Type")) { - if (r[1].equalsIgnoreCase("BW2")) { - current.romType = Gen5Constants.Type_BW2; - } else { - current.romType = Gen5Constants.Type_BW; - } - } else if (r[0].equals("CopyFrom")) { - for (RomEntry otherEntry : roms) { - if (r[1].equalsIgnoreCase(otherEntry.romCode)) { - // copy from here - current.arrayEntries - .putAll(otherEntry.arrayEntries); - current.numbers.putAll(otherEntry.numbers); - current.strings.putAll(otherEntry.strings); - current.offsetArrayEntries - .putAll(otherEntry.offsetArrayEntries); - if (current.copyStaticPokemon) { - current.staticPokemon - .addAll(otherEntry.staticPokemon); - current.staticPokemonSupport = true; - } else { - current.staticPokemonSupport = false; - } - } - } - } else if (r[0].equals("StaticPokemon[]")) { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - int[] offs = new int[offsets.length]; - int[] files = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - String[] parts = off.split("\\:"); - files[c] = parseRIInt(parts[0]); - offs[c++] = parseRIInt(parts[1]); - } - StaticPokemon sp = new StaticPokemon(); - sp.files = files; - sp.offsets = offs; - current.staticPokemon.add(sp); - } else { - String[] parts = r[1].split("\\:"); - int files = parseRIInt(parts[0]); - int offs = parseRIInt(parts[1]); - StaticPokemon sp = new StaticPokemon(); - sp.files = new int[] { files }; - sp.offsets = new int[] { offs }; - } - } else if (r[0].equals("StaticPokemonSupport")) { - int spsupport = parseRIInt(r[1]); - current.staticPokemonSupport = (spsupport > 0); - } else if (r[0].equals("CopyStaticPokemon")) { - int csp = parseRIInt(r[1]); - current.copyStaticPokemon = (csp > 0); - } else if (r[0].startsWith("StarterOffsets") - || r[0].equals("StaticPokemonFormValues")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - OffsetWithinEntry[] offs = new OffsetWithinEntry[offsets.length]; - int c = 0; - for (String off : offsets) { - String[] parts = off.split("\\:"); - OffsetWithinEntry owe = new OffsetWithinEntry(); - owe.entry = parseRIInt(parts[0]); - owe.offset = parseRIInt(parts[1]); - offs[c++] = owe; - } - current.offsetArrayEntries.put(r[0], offs); - } else if (r[0].endsWith("Tweak")) { - current.tweakFiles.put(r[0], r[1]); - } else { - if (r[1].startsWith("[") && r[1].endsWith("]")) { - String[] offsets = r[1].substring(1, - r[1].length() - 1).split(","); - if (offsets.length == 1 - && offsets[0].trim().isEmpty()) { - current.arrayEntries.put(r[0], new int[0]); - } else { - int[] offs = new int[offsets.length]; - int c = 0; - for (String off : offsets) { - offs[c++] = parseRIInt(off); - } - current.arrayEntries.put(r[0], offs); - } - } else if (r[0].endsWith("Offset") - || r[0].endsWith("Count") - || r[0].endsWith("Number")) { - int offs = parseRIInt(r[1]); - current.numbers.put(r[0], offs); - } else { - current.strings.put(r[0], r[1]); - } - } - } - } - } - sc.close(); - } catch (FileNotFoundException e) { - } - } - - 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; - } - } - - // This ROM - private Pokemon[] pokes; - private List<Pokemon> pokemonList; - private Move[] moves; - private RomEntry romEntry; - private byte[] arm9; - private List<String> abilityNames; - private List<String> itemNames; - private boolean loadedWildMapNames; - private Map<Integer, String> wildMapNames; - - private NARCContents pokeNarc, moveNarc, stringsNarc, storyTextNarc, - scriptNarc; - - @Override - protected boolean detectNDSRom(String ndsCode) { - return detectNDSRomInner(ndsCode); - } - - private static boolean detectNDSRomInner(String ndsCode) { - return entryFor(ndsCode) != null; - } - - private static RomEntry entryFor(String ndsCode) { - if (ndsCode == null) { - return null; - } - - for (RomEntry re : roms) { - if (ndsCode.equals(re.romCode)) { - return re; - } - } - return null; - } - - @Override - protected void loadedROM(String romCode) { - this.romEntry = entryFor(romCode); - try { - arm9 = readARM9(); - } catch (IOException e) { - throw new RuntimeException(e); - } - try { - stringsNarc = readNARC(romEntry.getString("TextStrings")); - storyTextNarc = readNARC(romEntry.getString("TextStory")); - } catch (IOException e) { - throw new RuntimeException(e); - } - - try { - scriptNarc = readNARC(romEntry.getString("Scripts")); - } catch (IOException e) { - throw new RuntimeException(e); - } - loadPokemonStats(); - pokemonList = Arrays.asList(pokes); - loadMoves(); - - abilityNames = getStrings(false, - romEntry.getInt("AbilityNamesTextOffset")); - itemNames = getStrings(false, romEntry.getInt("ItemNamesTextOffset")); - loadedWildMapNames = false; - } - - private void loadPokemonStats() { - try { - pokeNarc = this.readNARC(romEntry.getString("PokemonStats")); - String[] pokeNames = readPokemonNames(); - pokes = new Pokemon[Gen5Constants.pokemonCount + 1]; - for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { - pokes[i] = new Pokemon(); - pokes[i].number = i; - loadBasicPokeStats(pokes[i], pokeNarc.files.get(i)); - // Name? - pokes[i].name = pokeNames[i]; - } - populateEvolutions(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - private void loadMoves() { - try { - moveNarc = this.readNARC(romEntry.getString("MoveData")); - moves = new Move[Gen5Constants.moveCount + 1]; - List<String> moveNames = getStrings(false, - romEntry.getInt("MoveNamesTextOffset")); - for (int i = 1; i <= Gen5Constants.moveCount; i++) { - byte[] moveData = moveNarc.files.get(i); - moves[i] = new Move(); - moves[i].name = moveNames.get(i); - moves[i].number = i; - moves[i].internalId = i; - moves[i].hitratio = (moveData[4] & 0xFF); - moves[i].power = moveData[3] & 0xFF; - moves[i].pp = moveData[5] & 0xFF; - moves[i].type = Gen5Constants.typeTable[moveData[0] & 0xFF]; - moves[i].category = Gen5Constants.moveCategoryIndices[moveData[2] & 0xFF]; - } - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - private void loadBasicPokeStats(Pokemon pkmn, byte[] stats) { - pkmn.hp = stats[Gen5Constants.bsHPOffset] & 0xFF; - pkmn.attack = stats[Gen5Constants.bsAttackOffset] & 0xFF; - pkmn.defense = stats[Gen5Constants.bsDefenseOffset] & 0xFF; - pkmn.speed = stats[Gen5Constants.bsSpeedOffset] & 0xFF; - pkmn.spatk = stats[Gen5Constants.bsSpAtkOffset] & 0xFF; - pkmn.spdef = stats[Gen5Constants.bsSpDefOffset] & 0xFF; - // Type - pkmn.primaryType = Gen5Constants.typeTable[stats[Gen5Constants.bsPrimaryTypeOffset] & 0xFF]; - pkmn.secondaryType = Gen5Constants.typeTable[stats[Gen5Constants.bsSecondaryTypeOffset] & 0xFF]; - // Only one type? - if (pkmn.secondaryType == pkmn.primaryType) { - pkmn.secondaryType = null; - } - pkmn.catchRate = stats[Gen5Constants.bsCatchRateOffset] & 0xFF; - pkmn.growthCurve = ExpCurve - .fromByte(stats[Gen5Constants.bsGrowthCurveOffset]); - - pkmn.ability1 = stats[Gen5Constants.bsAbility1Offset] & 0xFF; - pkmn.ability2 = stats[Gen5Constants.bsAbility2Offset] & 0xFF; - pkmn.ability3 = stats[Gen5Constants.bsAbility3Offset] & 0xFF; - - // Held Items? - int item1 = readWord(stats, Gen5Constants.bsCommonHeldItemOffset); - int item2 = readWord(stats, Gen5Constants.bsRareHeldItemOffset); - - if (item1 == item2) { - // guaranteed - pkmn.guaranteedHeldItem = item1; - pkmn.commonHeldItem = 0; - pkmn.rareHeldItem = 0; - pkmn.darkGrassHeldItem = 0; - } else { - pkmn.guaranteedHeldItem = 0; - pkmn.commonHeldItem = item1; - pkmn.rareHeldItem = item2; - pkmn.darkGrassHeldItem = readWord(stats, - Gen5Constants.bsDarkGrassHeldItemOffset); - } - } - - private String[] readPokemonNames() { - String[] pokeNames = new String[Gen5Constants.pokemonCount + 1]; - List<String> nameList = getStrings(false, - romEntry.getInt("PokemonNamesTextOffset")); - for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { - pokeNames[i] = nameList.get(i); - } - return pokeNames; - } - - @Override - protected void savingROM() { - savePokemonStats(); - saveMoves(); - try { - writeARM9(arm9); - } catch (IOException e) { - throw new RuntimeException(e); - } - try { - writeNARC(romEntry.getString("TextStrings"), stringsNarc); - writeNARC(romEntry.getString("TextStory"), storyTextNarc); - } catch (IOException e) { - throw new RuntimeException(e); - } - - try { - writeNARC(romEntry.getString("Scripts"), scriptNarc); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void saveMoves() { - for (int i = 1; i <= Gen5Constants.moveCount; i++) { - byte[] data = moveNarc.files.get(i); - data[2] = Gen5Constants.moveCategoryToByte(moves[i].category); - data[3] = (byte) moves[i].power; - data[0] = Gen5Constants.typeToByte(moves[i].type); - int hitratio = (int) Math.round(moves[i].hitratio); - if (hitratio < 0) { - hitratio = 0; - } - if (hitratio > 101) { - hitratio = 100; - } - data[4] = (byte) hitratio; - data[5] = (byte) moves[i].pp; - } - - try { - this.writeNARC(romEntry.getString("MoveData"), moveNarc); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - private void savePokemonStats() { - List<String> nameList = getStrings(false, - romEntry.getInt("PokemonNamesTextOffset")); - - for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { - saveBasicPokeStats(pokes[i], pokeNarc.files.get(i)); - nameList.set(i, pokes[i].name); - } - - setStrings(false, romEntry.getInt("PokemonNamesTextOffset"), nameList); - - try { - this.writeNARC(romEntry.getString("PokemonStats"), pokeNarc); - } catch (IOException e) { - throw new RuntimeException(e); - } - - writeEvolutions(); - } - - private void saveBasicPokeStats(Pokemon pkmn, byte[] stats) { - stats[Gen5Constants.bsHPOffset] = (byte) pkmn.hp; - stats[Gen5Constants.bsAttackOffset] = (byte) pkmn.attack; - stats[Gen5Constants.bsDefenseOffset] = (byte) pkmn.defense; - stats[Gen5Constants.bsSpeedOffset] = (byte) pkmn.speed; - stats[Gen5Constants.bsSpAtkOffset] = (byte) pkmn.spatk; - stats[Gen5Constants.bsSpDefOffset] = (byte) pkmn.spdef; - stats[Gen5Constants.bsPrimaryTypeOffset] = Gen5Constants - .typeToByte(pkmn.primaryType); - if (pkmn.secondaryType == null) { - stats[Gen5Constants.bsSecondaryTypeOffset] = stats[Gen5Constants.bsPrimaryTypeOffset]; - } else { - stats[Gen5Constants.bsSecondaryTypeOffset] = Gen5Constants - .typeToByte(pkmn.secondaryType); - } - stats[Gen5Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; - stats[Gen5Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); - - stats[Gen5Constants.bsAbility1Offset] = (byte) pkmn.ability1; - stats[Gen5Constants.bsAbility2Offset] = (byte) pkmn.ability2; - stats[Gen5Constants.bsAbility3Offset] = (byte) pkmn.ability3; - - // Held items - if (pkmn.guaranteedHeldItem > 0) { - writeWord(stats, Gen5Constants.bsCommonHeldItemOffset, - pkmn.guaranteedHeldItem); - writeWord(stats, Gen5Constants.bsRareHeldItemOffset, - pkmn.guaranteedHeldItem); - writeWord(stats, Gen5Constants.bsDarkGrassHeldItemOffset, 0); - } else { - writeWord(stats, Gen5Constants.bsCommonHeldItemOffset, - pkmn.commonHeldItem); - writeWord(stats, Gen5Constants.bsRareHeldItemOffset, - pkmn.rareHeldItem); - writeWord(stats, Gen5Constants.bsDarkGrassHeldItemOffset, - pkmn.darkGrassHeldItem); - } - } - - @Override - public List<Pokemon> getPokemon() { - return pokemonList; - } - - @Override - public List<Pokemon> getStarters() { - NARCContents scriptNARC = scriptNarc; - List<Pokemon> starters = new ArrayList<Pokemon>(); - for (int i = 0; i < 3; i++) { - OffsetWithinEntry[] thisStarter = romEntry.offsetArrayEntries - .get("StarterOffsets" + (i + 1)); - starters.add(pokes[readWord( - scriptNARC.files.get(thisStarter[0].entry), - thisStarter[0].offset)]); - } - return starters; - } - - @Override - public boolean setStarters(List<Pokemon> newStarters) { - if (newStarters.size() != 3) { - return false; - } - - // Fix up starter offsets - try { - NARCContents scriptNARC = scriptNarc; - for (int i = 0; i < 3; i++) { - int starter = newStarters.get(i).number; - OffsetWithinEntry[] thisStarter = romEntry.offsetArrayEntries - .get("StarterOffsets" + (i + 1)); - for (OffsetWithinEntry entry : thisStarter) { - writeWord(scriptNARC.files.get(entry.entry), entry.offset, - starter); - } - } - // GIVE ME BACK MY PURRLOIN - if (romEntry.romType == Gen5Constants.Type_BW2) { - byte[] newScript = Gen5Constants.bw2NewStarterScript; - byte[] oldFile = scriptNARC.files.get(romEntry - .getInt("PokedexGivenFileOffset")); - byte[] newFile = new byte[oldFile.length + newScript.length]; - int offset = find(oldFile, Gen5Constants.bw2StarterScriptMagic); - if (offset > 0) { - System.arraycopy(oldFile, 0, newFile, 0, oldFile.length); - System.arraycopy(newScript, 0, newFile, oldFile.length, - newScript.length); - if (romEntry.romCode.charAt(3) == 'J') { - newFile[oldFile.length + 0x6] -= 4; - } - newFile[offset++] = 0x1E; - newFile[offset++] = 0x0; - writeRelativePointer(newFile, offset, oldFile.length); - scriptNARC.files.set( - romEntry.getInt("PokedexGivenFileOffset"), newFile); - } - } else { - byte[] newScript = Gen5Constants.bw1NewStarterScript; - - byte[] oldFile = scriptNARC.files.get(romEntry - .getInt("PokedexGivenFileOffset")); - byte[] newFile = new byte[oldFile.length + newScript.length]; - int offset = find(oldFile, Gen5Constants.bw1StarterScriptMagic); - if (offset > 0) { - System.arraycopy(oldFile, 0, newFile, 0, oldFile.length); - System.arraycopy(newScript, 0, newFile, oldFile.length, - newScript.length); - if (romEntry.romCode.charAt(3) == 'J') { - newFile[oldFile.length + 0x4] -= 4; - newFile[oldFile.length + 0x8] -= 4; - } - newFile[offset++] = 0x04; - newFile[offset++] = 0x0; - writeRelativePointer(newFile, offset, oldFile.length); - scriptNARC.files.set( - romEntry.getInt("PokedexGivenFileOffset"), newFile); - } - } - - // Starter sprites - NARCContents starterNARC = this.readNARC(romEntry - .getString("StarterGraphics")); - NARCContents pokespritesNARC = this.readNARC(romEntry - .getString("PokemonGraphics")); - replaceStarterFiles(starterNARC, pokespritesNARC, 0, - newStarters.get(0).number); - replaceStarterFiles(starterNARC, pokespritesNARC, 1, - newStarters.get(1).number); - replaceStarterFiles(starterNARC, pokespritesNARC, 2, - newStarters.get(2).number); - writeNARC(romEntry.getString("StarterGraphics"), starterNARC); - } catch (IOException ex) { - throw new RuntimeException(ex); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - // Fix text depending on version - if (romEntry.romType == Gen5Constants.Type_BW) { - List<String> yourHouseStrings = getStrings(true, - romEntry.getInt("StarterLocationTextOffset")); - for (int i = 0; i < 3; i++) { - yourHouseStrings - .set(Gen5Constants.bw1StarterTextOffset - i, - "\\xF000\\xBD02\\x0000The " - + newStarters.get(i).primaryType - .camelCase() - + "-type Pok\\x00E9mon\\xFFFE\\xF000\\xBD02\\x0000" - + newStarters.get(i).name); - } - // Update what the friends say - yourHouseStrings - .set(Gen5Constants.bw1CherenText1Offset, - "Cheren: Hey, how come you get to pick\\xFFFEout my Pok\\x00E9mon?" - + "\\xF000\\xBE01\\x0000\\xFFFEOh, never mind. I wanted this one\\xFFFEfrom the start, anyway." - + "\\xF000\\xBE01\\x0000"); - yourHouseStrings - .set(Gen5Constants.bw1CherenText2Offset, - "It's decided. You'll be my opponent...\\xFFFEin our first Pok\\x00E9mon battle!" - + "\\xF000\\xBE01\\x0000\\xFFFELet's see what you can do, \\xFFFEmy Pok\\x00E9mon!" - + "\\xF000\\xBE01\\x0000"); - - // rewrite - setStrings(true, romEntry.getInt("StarterLocationTextOffset"), - yourHouseStrings); - } else { - List<String> starterTownStrings = getStrings(true, - romEntry.getInt("StarterLocationTextOffset")); - for (int i = 0; i < 3; i++) { - starterTownStrings - .set(Gen5Constants.bw2StarterTextOffset - i, - "\\xF000\\xBD02\\x0000The " - + newStarters.get(i).primaryType - .camelCase() - + "-type Pok\\x00E9mon\\xFFFE\\xF000\\xBD02\\x0000" - + newStarters.get(i).name); - } - // Update what the rival says - starterTownStrings - .set(Gen5Constants.bw2RivalTextOffset, - "\\xF000\\x0100\\x0001\\x0001: Let's see how good\\xFFFEa Trainer you are!" - + "\\xF000\\xBE01\\x0000\\xFFFEI'll use my Pok\\x00E9mon" - + "\\xFFFEthat I raised from an Egg!\\xF000\\xBE01\\x0000"); - - // rewrite - setStrings(true, romEntry.getInt("StarterLocationTextOffset"), - starterTownStrings); - } - return true; - } - - @Override - public List<Integer> getStarterHeldItems() { - // do nothing - return new ArrayList<Integer>(); - } - - @Override - public void setStarterHeldItems(List<Integer> items) { - // do nothing - } - - private void replaceStarterFiles(NARCContents starterNARC, - NARCContents pokespritesNARC, int starterIndex, int pokeNumber) - throws IOException, InterruptedException { - starterNARC.files.set(starterIndex * 2, - pokespritesNARC.files.get(pokeNumber * 20 + 18)); - // Get the picture... - byte[] compressedPic = pokespritesNARC.files.get(pokeNumber * 20); - // Decompress it with JavaDSDecmp - byte[] uncompressedPic = DSDecmp.Decompress(compressedPic); - starterNARC.files.set(12 + starterIndex, uncompressedPic); - } - - @Override - public void shufflePokemonStats() { - for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { - pokes[i].shuffleStats(this.random); - } - - } - - @Override - public List<Move> getMoves() { - return Arrays.asList(moves); - } - - @Override - public List<EncounterSet> getEncounters(boolean useTimeOfDay) { - if (!loadedWildMapNames) { - loadWildMapNames(); - } - try { - NARCContents encounterNARC = readNARC(romEntry - .getString("WildPokemon")); - List<EncounterSet> encounters = new ArrayList<EncounterSet>(); - int idx = -1; - for (byte[] entry : encounterNARC.files) { - idx++; - if (entry.length > Gen5Constants.perSeasonEncounterDataLength - && useTimeOfDay) { - for (int i = 0; i < 4; i++) { - processEncounterEntry(encounters, entry, i - * Gen5Constants.perSeasonEncounterDataLength, - idx); - } - } else { - processEncounterEntry(encounters, entry, 0, idx); - } - } - return encounters; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void processEncounterEntry(List<EncounterSet> encounters, - byte[] entry, int startOffset, int idx) { - - if (!wildMapNames.containsKey(idx)) { - wildMapNames.put(idx, "? Unknown ?"); - } - String mapName = wildMapNames.get(idx); - - int[] amounts = Gen5Constants.encountersOfEachType; - - int offset = 8; - for (int i = 0; i < 7; i++) { - int rate = entry[startOffset + i] & 0xFF; - if (rate != 0) { - List<Encounter> encs = readEncounters(entry, startOffset - + offset, amounts[i]); - EncounterSet area = new EncounterSet(); - area.rate = rate; - area.encounters = encs; - area.offset = idx; - area.displayName = mapName + " " - + Gen5Constants.encounterTypeNames[i]; - encounters.add(area); - } - offset += amounts[i] * 4; - } - - } - - private List<Encounter> readEncounters(byte[] data, int offset, int number) { - List<Encounter> encs = new ArrayList<Encounter>(); - for (int i = 0; i < number; i++) { - Encounter enc1 = new Encounter(); - enc1.pokemon = pokes[((data[offset + i * 4] & 0xFF) + ((data[offset - + 1 + i * 4] & 0x03) << 8))]; - enc1.level = data[offset + 2 + i * 4] & 0xFF; - enc1.maxLevel = data[offset + 3 + i * 4] & 0xFF; - encs.add(enc1); - } - return encs; - } - - @Override - public void setEncounters(boolean useTimeOfDay, - List<EncounterSet> encountersList) { - try { - NARCContents encounterNARC = readNARC(romEntry - .getString("WildPokemon")); - Iterator<EncounterSet> encounters = encountersList.iterator(); - for (byte[] entry : encounterNARC.files) { - writeEncounterEntry(encounters, entry, 0); - if (entry.length > 232) { - if (useTimeOfDay) { - for (int i = 1; i < 4; i++) { - writeEncounterEntry(encounters, entry, i * 232); - } - } else { - // copy for other 3 seasons - System.arraycopy(entry, 0, entry, 232, 232); - System.arraycopy(entry, 0, entry, 464, 232); - System.arraycopy(entry, 0, entry, 696, 232); - } - } - } - - // Save - writeNARC(romEntry.getString("WildPokemon"), encounterNARC); - - // Habitat List / Area Data? - if (romEntry.romType == Gen5Constants.Type_BW2) { - // disabled: habitat list changes cause a crash if too many - // entries for now. - - // NARCContents habitatNARC = readNARC(romEntry - // .getString("HabitatList")); - // for (int i = 0; i < habitatNARC.files.size(); i++) { - // byte[] oldEntry = habitatNARC.files.get(i); - // int[] encounterFiles = habitatListEntries[i]; - // Map<Pokemon, byte[]> pokemonHere = new TreeMap<Pokemon, - // byte[]>(); - // for (int encFile : encounterFiles) { - // byte[] encEntry = encounterNARC.files.get(encFile); - // if (encEntry.length > 232) { - // for (int s = 0; s < 4; s++) { - // addHabitats(encEntry, s * 232, pokemonHere, s); - // } - // } else { - // for (int s = 0; s < 4; s++) { - // addHabitats(encEntry, 0, pokemonHere, s); - // } - // } - // } - // // Make the new file - // byte[] habitatEntry = new byte[10 + pokemonHere.size() * 28]; - // System.arraycopy(oldEntry, 0, habitatEntry, 0, 10); - // habitatEntry[8] = (byte) pokemonHere.size(); - // // 28-byte entries for each pokemon - // int num = -1; - // for (Pokemon pkmn : pokemonHere.keySet()) { - // num++; - // writeWord(habitatEntry, 10 + num * 28, pkmn.number); - // byte[] slots = pokemonHere.get(pkmn); - // System.arraycopy(slots, 0, habitatEntry, 12 + num * 28, - // 12); - // } - // // Save - // habitatNARC.files.set(i, habitatEntry); - // } - // // Save habitat - // this.writeNARC(romEntry.getString("HabitatList"), - // habitatNARC); - - // Area Data - NARCContents areaNARC = this.readNARC(romEntry - .getString("PokemonAreaData")); - List<byte[]> newFiles = new ArrayList<byte[]>(); - for (int i = 0; i < Gen5Constants.pokemonCount; i++) { - byte[] nf = new byte[Gen5Constants.bw2AreaDataEntryLength]; - nf[0] = 1; - newFiles.add(nf); - } - // Get data now - for (int i = 0; i < encounterNARC.files.size(); i++) { - byte[] encEntry = encounterNARC.files.get(i); - if (encEntry.length > Gen5Constants.perSeasonEncounterDataLength) { - for (int s = 0; s < 4; s++) { - parseAreaData( - encEntry, - s - * Gen5Constants.perSeasonEncounterDataLength, - newFiles, s, i); - } - } else { - for (int s = 0; s < 4; s++) { - parseAreaData(encEntry, 0, newFiles, s, i); - } - } - } - // Now update unobtainables & save - for (int i = 0; i < Gen5Constants.pokemonCount; i++) { - byte[] file = newFiles.get(i); - for (int s = 0; s < 4; s++) { - boolean unobtainable = true; - for (int e = 0; e < Gen5Constants.bw2EncounterAreaCount; e++) { - if (file[s - * (Gen5Constants.bw2EncounterAreaCount + 1) - + e + 2] != 0) { - unobtainable = false; - break; - } - } - if (unobtainable) { - file[s * (Gen5Constants.bw2EncounterAreaCount + 1) - + 1] = 1; - } - } - areaNARC.files.set(i, file); - } - // Save - this.writeNARC(romEntry.getString("PokemonAreaData"), areaNARC); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - private void parseAreaData(byte[] entry, int startOffset, - List<byte[]> areaData, int season, int fileNumber) { - int[] amounts = Gen5Constants.encountersOfEachType; - - int offset = 8; - for (int i = 0; i < 7; i++) { - int rate = entry[startOffset + i] & 0xFF; - if (rate != 0) { - for (int e = 0; e < amounts[i]; e++) { - Pokemon pkmn = pokes[((entry[startOffset + offset + e * 4] & 0xFF) + ((entry[startOffset - + offset + 1 + e * 4] & 0x03) << 8))]; - byte[] pokeFile = areaData.get(pkmn.number - 1); - int areaIndex = Gen5Constants.wildFileToAreaMap[fileNumber]; - // Route 4? - if (areaIndex == Gen5Constants.bw2Route4AreaIndex) { - if ((fileNumber == Gen5Constants.b2Route4EncounterFile && romEntry.romCode - .charAt(2) == 'D') - || (fileNumber == Gen5Constants.w2Route4EncounterFile && romEntry.romCode - .charAt(2) == 'E')) { - areaIndex = -1; // wrong version - } - } - // Victory Road? - if (areaIndex == Gen5Constants.bw2VictoryRoadAreaIndex) { - if (romEntry.romCode.charAt(2) == 'D') { - // White 2 - if (fileNumber == Gen5Constants.b2VRExclusiveRoom1 - || fileNumber == Gen5Constants.b2VRExclusiveRoom2) { - areaIndex = -1; // wrong version - } - } else { - // Black 2 - if (fileNumber == Gen5Constants.w2VRExclusiveRoom1 - || fileNumber == Gen5Constants.w2VRExclusiveRoom2) { - areaIndex = -1; // wrong version - } - } - } - // Reversal Mountain? - if (areaIndex == Gen5Constants.bw2ReversalMountainAreaIndex) { - if (romEntry.romCode.charAt(2) == 'D') { - // White 2 - if (fileNumber >= Gen5Constants.b2ReversalMountainStart - && fileNumber <= Gen5Constants.b2ReversalMountainEnd) { - areaIndex = -1; // wrong version - } - } else { - // Black 2 - if (fileNumber >= Gen5Constants.w2ReversalMountainStart - && fileNumber <= Gen5Constants.w2ReversalMountainEnd) { - areaIndex = -1; // wrong version - } - } - } - // Skip stuff that isn't on the map or is wrong version - if (areaIndex != -1) { - pokeFile[season - * (Gen5Constants.bw2EncounterAreaCount + 1) + 2 - + areaIndex] |= (1 << i); - } - } - } - offset += amounts[i] * 4; - } - } - - @SuppressWarnings("unused") - private void addHabitats(byte[] entry, int startOffset, - Map<Pokemon, byte[]> pokemonHere, int season) { - int[] amounts = Gen5Constants.encountersOfEachType; - int[] type = Gen5Constants.habitatClassificationOfEachType; - - int offset = 8; - for (int i = 0; i < 7; i++) { - int rate = entry[startOffset + i] & 0xFF; - if (rate != 0) { - for (int e = 0; e < amounts[i]; e++) { - Pokemon pkmn = pokes[((entry[startOffset + offset + e * 4] & 0xFF) + ((entry[startOffset - + offset + 1 + e * 4] & 0x03) << 8))]; - if (pokemonHere.containsKey(pkmn)) { - pokemonHere.get(pkmn)[type[i] + season * 3] = 1; - } else { - byte[] locs = new byte[12]; - locs[type[i] + season * 3] = 1; - pokemonHere.put(pkmn, locs); - } - } - } - offset += amounts[i] * 4; - } - } - - private void writeEncounterEntry(Iterator<EncounterSet> encounters, - byte[] entry, int startOffset) { - int[] amounts = Gen5Constants.encountersOfEachType; - - int offset = 8; - for (int i = 0; i < 7; i++) { - int rate = entry[startOffset + i] & 0xFF; - if (rate != 0) { - EncounterSet area = encounters.next(); - for (int j = 0; j < amounts[i]; j++) { - Encounter enc = area.encounters.get(j); - writeWord(entry, startOffset + offset + j * 4, - enc.pokemon.number); - entry[startOffset + offset + j * 4 + 2] = (byte) enc.level; - entry[startOffset + offset + j * 4 + 3] = (byte) enc.maxLevel; - } - } - offset += amounts[i] * 4; - } - } - - private void loadWildMapNames() { - try { - wildMapNames = new HashMap<Integer, String>(); - byte[] mapHeaderData = this.readNARC(romEntry - .getString("MapTableFile")).files.get(0); - int numMapHeaders = mapHeaderData.length / 48; - List<String> allMapNames = getStrings(false, - romEntry.getInt("MapNamesTextOffset")); - for (int map = 0; map < numMapHeaders; map++) { - int baseOffset = map * 48; - int mapNameIndex = mapHeaderData[baseOffset + 26] & 0xFF; - String mapName = allMapNames.get(mapNameIndex); - if (romEntry.romType == Gen5Constants.Type_BW2) { - int wildSet = mapHeaderData[baseOffset + 20] & 0xFF; - if (wildSet != 255) { - wildMapNames.put(wildSet, mapName); - } - } else { - int wildSet = readWord(mapHeaderData, baseOffset + 20); - if (wildSet != 65535) { - wildMapNames.put(wildSet, mapName); - } - } - } - loadedWildMapNames = true; - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - @Override - public List<Trainer> getTrainers() { - List<Trainer> allTrainers = new ArrayList<Trainer>(); - try { - NARCContents trainers = this.readNARC(romEntry - .getString("TrainerData")); - NARCContents trpokes = this.readNARC(romEntry - .getString("TrainerPokemon")); - int trainernum = trainers.files.size(); - List<String> tclasses = this.getTrainerClassNames(); - List<String> tnames = this.getTrainerNames(); - for (int i = 1; i < trainernum; i++) { - byte[] trainer = trainers.files.get(i); - byte[] trpoke = trpokes.files.get(i); - Trainer tr = new Trainer(); - tr.poketype = trainer[0] & 0xFF; - tr.offset = i; - tr.trainerclass = trainer[1] & 0xFF; - int numPokes = trainer[3] & 0xFF; - int pokeOffs = 0; - tr.fullDisplayName = tclasses.get(tr.trainerclass) + " " - + tnames.get(i - 1); - // printBA(trpoke); - for (int poke = 0; poke < numPokes; poke++) { - // Structure is - // AI 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 ailevel = trpoke[pokeOffs] & 0xFF; - // int secondbyte = trpoke[pokeOffs + 1] & 0xFF; - int level = readWord(trpoke, pokeOffs + 2); - int species = readWord(trpoke, pokeOffs + 4); - // int formnum = readWord(trpoke, pokeOffs + 6); - TrainerPokemon tpk = new TrainerPokemon(); - tpk.level = level; - tpk.pokemon = pokes[species]; - tpk.AILevel = ailevel; - tpk.ability = trpoke[pokeOffs + 1] & 0xFF; - pokeOffs += 8; - if (tr.poketype >= 2) { - int heldItem = readWord(trpoke, pokeOffs); - tpk.heldItem = heldItem; - pokeOffs += 2; - } - if (tr.poketype % 2 == 1) { - int attack1 = readWord(trpoke, pokeOffs); - int attack2 = readWord(trpoke, pokeOffs + 2); - int attack3 = readWord(trpoke, pokeOffs + 4); - int attack4 = readWord(trpoke, pokeOffs + 6); - tpk.move1 = attack1; - tpk.move2 = attack2; - tpk.move3 = attack3; - tpk.move4 = attack4; - pokeOffs += 8; - } - tr.pokemon.add(tpk); - } - allTrainers.add(tr); - } - if (romEntry.romType == Gen5Constants.Type_BW) { - Gen5Constants.tagTrainersBW(allTrainers); - } else { - if (!romEntry.getString("DriftveilPokemon").isEmpty()) { - NARCContents driftveil = this.readNARC(romEntry - .getString("DriftveilPokemon")); - for (int trno = 0; trno < 2; trno++) { - Trainer tr = new Trainer(); - tr.poketype = 3; - tr.offset = 0; - for (int poke = 0; poke < 3; poke++) { - byte[] pkmndata = driftveil.files.get(trno * 3 - + poke + 1); - TrainerPokemon tpk = new TrainerPokemon(); - tpk.level = 25; - tpk.pokemon = pokes[readWord(pkmndata, 0)]; - tpk.AILevel = 255; - tpk.heldItem = readWord(pkmndata, 12); - tpk.move1 = readWord(pkmndata, 2); - tpk.move2 = readWord(pkmndata, 2); - tpk.move3 = readWord(pkmndata, 2); - tpk.move4 = readWord(pkmndata, 2); - tr.pokemon.add(tpk); - } - allTrainers.add(tr); - } - } - Gen5Constants.tagTrainersBW2(allTrainers); - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - return allTrainers; - } - - @Override - public void setTrainers(List<Trainer> trainerData) { - Iterator<Trainer> allTrainers = trainerData.iterator(); - try { - NARCContents trainers = this.readNARC(romEntry - .getString("TrainerData")); - NARCContents trpokes = new NARCContents(); - // empty entry - trpokes.files.add(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }); - int trainernum = trainers.files.size(); - for (int i = 1; i < trainernum; i++) { - byte[] trainer = trainers.files.get(i); - Trainer tr = allTrainers.next(); - tr.poketype = 0; // write as type 0 for no item/moves - trainer[0] = (byte) tr.poketype; - int numPokes = tr.pokemon.size(); - trainer[3] = (byte) numPokes; - - int bytesNeeded = 8 * numPokes; - if (tr.poketype % 2 == 1) { - bytesNeeded += 8 * numPokes; - } - if (tr.poketype >= 2) { - bytesNeeded += 2 * numPokes; - } - byte[] trpoke = new byte[bytesNeeded]; - int pokeOffs = 0; - Iterator<TrainerPokemon> tpokes = tr.pokemon.iterator(); - for (int poke = 0; poke < numPokes; poke++) { - TrainerPokemon tpk = tpokes.next(); - trpoke[pokeOffs] = (byte) tpk.AILevel; - // no gender or ability info, so no byte 1 - writeWord(trpoke, pokeOffs + 2, tpk.level); - writeWord(trpoke, pokeOffs + 4, tpk.pokemon.number); - // no form info, so no byte 6/7 - pokeOffs += 8; - if (tr.poketype >= 2) { - writeWord(trpoke, pokeOffs, tpk.heldItem); - pokeOffs += 2; - } - if (tr.poketype % 2 == 1) { - writeWord(trpoke, pokeOffs, tpk.move1); - writeWord(trpoke, pokeOffs + 2, tpk.move2); - writeWord(trpoke, pokeOffs + 4, tpk.move3); - writeWord(trpoke, pokeOffs + 6, tpk.move4); - pokeOffs += 8; - } - } - trpokes.files.add(trpoke); - } - this.writeNARC(romEntry.getString("TrainerData"), trainers); - this.writeNARC(romEntry.getString("TrainerPokemon"), trpokes); - // Deal with PWT - if (romEntry.romType == Gen5Constants.Type_BW2 - && !romEntry.getString("DriftveilPokemon").isEmpty()) { - NARCContents driftveil = this.readNARC(romEntry - .getString("DriftveilPokemon")); - Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); - for (int trno = 0; trno < 2; trno++) { - Trainer tr = allTrainers.next(); - Iterator<TrainerPokemon> tpks = tr.pokemon.iterator(); - for (int poke = 0; poke < 3; poke++) { - byte[] pkmndata = driftveil.files.get(trno * 3 + poke - + 1); - TrainerPokemon tpk = tpks.next(); - // pokemon and held item - writeWord(pkmndata, 0, tpk.pokemon.number); - writeWord(pkmndata, 12, tpk.heldItem); - // pick 4 moves, based on moveset@25 - int[] moves = new int[4]; - int moveCount = 0; - List<MoveLearnt> set = movesets.get(tpk.pokemon); - for (int i = 0; i < set.size(); i++) { - MoveLearnt ml = set.get(i); - if (ml.level > 25) { - break; - } - // unconditional learn? - if (moveCount < 4) { - moves[moveCount++] = ml.move; - } else { - // already knows? - boolean doTeach = true; - for (int j = 0; j < 4; j++) { - if (moves[j] == ml.move) { - doTeach = false; - break; - } - } - if (doTeach) { - // shift up - for (int j = 0; j < 3; j++) { - moves[j] = moves[j + 1]; - } - moves[3] = ml.move; - } - } - } - // write the moveset we calculated - for (int i = 0; i < 4; i++) { - writeWord(pkmndata, 2 + i * 2, moves[i]); - } - } - } - this.writeNARC(romEntry.getString("DriftveilPokemon"), - driftveil); - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public Map<Pokemon, List<MoveLearnt>> getMovesLearnt() { - Map<Pokemon, List<MoveLearnt>> movesets = new TreeMap<Pokemon, List<MoveLearnt>>(); - try { - NARCContents movesLearnt = this.readNARC(romEntry - .getString("PokemonMovesets")); - for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { - Pokemon pkmn = pokes[i]; - byte[] movedata = movesLearnt.files.get(i); - int moveDataLoc = 0; - List<MoveLearnt> learnt = new ArrayList<MoveLearnt>(); - 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, learnt); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return movesets; - } - - @Override - public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets) { - try { - NARCContents movesLearnt = readNARC(romEntry - .getString("PokemonMovesets")); - for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { - Pokemon pkmn = pokes[i]; - List<MoveLearnt> learnt = movesets.get(pkmn); - 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.files.set(i, moveset); - } - // Save - this.writeNARC(romEntry.getString("PokemonMovesets"), movesLearnt); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - private static class StaticPokemon { - private int[] files; - private int[] offsets; - - public Pokemon getPokemon(Gen5RomHandler parent, NARCContents scriptNARC) { - return parent.pokes[parent.readWord(scriptNARC.files.get(files[0]), - offsets[0])]; - } - - public void setPokemon(Gen5RomHandler parent, NARCContents scriptNARC, - Pokemon pkmn) { - int value = pkmn.number; - for (int i = 0; i < offsets.length; i++) { - byte[] file = scriptNARC.files.get(files[i]); - parent.writeWord(file, offsets[i], value); - } - } - } - - @Override - public boolean canChangeStaticPokemon() { - return romEntry.staticPokemonSupport; - } - - @Override - public List<Pokemon> getStaticPokemon() { - List<Pokemon> sp = new ArrayList<Pokemon>(); - if (!romEntry.staticPokemonSupport) { - return sp; - } - NARCContents scriptNARC = scriptNarc; - for (StaticPokemon statP : romEntry.staticPokemon) { - sp.add(statP.getPokemon(this, scriptNARC)); - } - return sp; - } - - @Override - public boolean setStaticPokemon(List<Pokemon> staticPokemon) { - if (!romEntry.staticPokemonSupport) { - return false; - } - if (staticPokemon.size() != romEntry.staticPokemon.size()) { - return false; - } - Iterator<Pokemon> statics = staticPokemon.iterator(); - NARCContents scriptNARC = scriptNarc; - for (StaticPokemon statP : romEntry.staticPokemon) { - statP.setPokemon(this, scriptNARC, statics.next()); - } - if (romEntry.offsetArrayEntries.containsKey("StaticPokemonFormValues")) { - OffsetWithinEntry[] formValues = romEntry.offsetArrayEntries - .get("StaticPokemonFormValues"); - for (OffsetWithinEntry owe : formValues) { - writeWord(scriptNARC.files.get(owe.entry), owe.offset, 0); - } - } - - return true; - } - - @Override - public int miscTweaksAvailable() { - int available = 0; - if (romEntry.romType == Gen5Constants.Type_BW2) { - available |= MiscTweak.RANDOMIZE_HIDDEN_HOLLOWS.getValue(); - } - if (romEntry.tweakFiles.get("FastestTextTweak") != null) { - available |= MiscTweak.FASTEST_TEXT.getValue(); - } - return available; - } - - @Override - public void applyMiscTweak(MiscTweak tweak) { - if (tweak == MiscTweak.RANDOMIZE_HIDDEN_HOLLOWS) { - randomizeHiddenHollowPokemon(); - } else if (tweak == MiscTweak.FASTEST_TEXT) { - applyFastestText(); - } - } - - private void randomizeHiddenHollowPokemon() { - if (romEntry.romType != Gen5Constants.Type_BW2) { - return; - } - int[] allowedUnovaPokemon = Gen5Constants.bw2HiddenHollowUnovaPokemon; - int randomSize = Gen5Constants.nonUnovaPokemonCount - + allowedUnovaPokemon.length; - try { - NARCContents hhNARC = this.readNARC(romEntry - .getString("HiddenHollows")); - for (byte[] hhEntry : hhNARC.files) { - for (int version = 0; version < 2; version++) { - for (int rarityslot = 0; rarityslot < 3; rarityslot++) { - for (int group = 0; group < 4; group++) { - int pokeChoice = this.random.nextInt(randomSize) + 1; - if (pokeChoice > Gen5Constants.nonUnovaPokemonCount) { - pokeChoice = allowedUnovaPokemon[pokeChoice - - (Gen5Constants.nonUnovaPokemonCount + 1)]; - } - writeWord(hhEntry, version * 78 + rarityslot * 26 - + group * 2, pokeChoice); - int genderRatio = this.random.nextInt(101); - hhEntry[version * 78 + rarityslot * 26 + 16 + group] = (byte) genderRatio; - hhEntry[version * 78 + rarityslot * 26 + 20 + group] = 0; // forme - } - } - } - // the rest of the file is items - } - this.writeNARC(romEntry.getString("HiddenHollows"), hhNARC); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void applyFastestText() { - genericIPSPatch(arm9, "FastestTextTweak"); - } - - private boolean genericIPSPatch(byte[] data, String ctName) { - String patchName = romEntry.tweakFiles.get(ctName); - if (patchName == null) { - return false; - } - - try { - FileFunctions.applyPatch(data, patchName); - return true; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public List<Integer> getTMMoves() { - String tmDataPrefix = Gen5Constants.tmDataPrefix; - int offset = find(arm9, tmDataPrefix); - if (offset > 0) { - offset += Gen5Constants.tmDataPrefix.length() / 2; // because it was - // a prefix - List<Integer> tms = new ArrayList<Integer>(); - for (int i = 0; i < Gen5Constants.tmBlockOneCount; i++) { - tms.add(readWord(arm9, offset + i * 2)); - } - // Skip past first 92 TMs and 6 HMs - offset += (Gen5Constants.tmBlockOneCount + Gen5Constants.hmCount) * 2; - for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { - tms.add(readWord(arm9, offset + i * 2)); - } - return tms; - } else { - return null; - } - } - - @Override - public List<Integer> getHMMoves() { - String tmDataPrefix = Gen5Constants.tmDataPrefix; - int offset = find(arm9, tmDataPrefix); - if (offset > 0) { - offset += Gen5Constants.tmDataPrefix.length() / 2; // because it was - // a prefix - offset += Gen5Constants.tmBlockOneCount * 2; // TM data - List<Integer> hms = new ArrayList<Integer>(); - for (int i = 0; i < Gen5Constants.hmCount; i++) { - hms.add(readWord(arm9, offset + i * 2)); - } - return hms; - } else { - return null; - } - } - - @Override - public void setTMMoves(List<Integer> moveIndexes) { - String tmDataPrefix = Gen5Constants.tmDataPrefix; - int offset = find(arm9, tmDataPrefix); - if (offset > 0) { - offset += Gen5Constants.tmDataPrefix.length() / 2; // because it was - // a prefix - for (int i = 0; i < Gen5Constants.tmBlockOneCount; i++) { - writeWord(arm9, offset + i * 2, moveIndexes.get(i)); - } - // Skip past those 92 TMs and 6 HMs - offset += (Gen5Constants.tmBlockOneCount + Gen5Constants.hmCount) * 2; - for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { - writeWord(arm9, offset + i * 2, - moveIndexes.get(i + Gen5Constants.tmBlockOneCount)); - } - - // Update TM item descriptions - List<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 < Gen5Constants.tmBlockOneCount; i++) { - itemDescriptions.set(i + Gen5Constants.tmBlockOneOffset, - moveDescriptions.get(moveIndexes.get(i))); - } - // TM93-95 are 618-620 - for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { - itemDescriptions.set( - i + Gen5Constants.tmBlockTwoOffset, - moveDescriptions.get(moveIndexes.get(i - + Gen5Constants.tmBlockOneCount))); - } - // Save the new item descriptions - setStrings(false, romEntry.getInt("ItemDescriptionsTextOffset"), - itemDescriptions); - // Palettes - String baseOfPalettes; - if (romEntry.romType == Gen5Constants.Type_BW) { - baseOfPalettes = Gen5Constants.bw1ItemPalettesPrefix; - } else { - baseOfPalettes = Gen5Constants.bw2ItemPalettesPrefix; - } - int offsPals = find(arm9, baseOfPalettes); - if (offsPals > 0) { - // Write pals - for (int i = 0; i < Gen5Constants.tmBlockOneCount; i++) { - int itmNum = Gen5Constants.tmBlockOneOffset + i; - Move m = this.moves[moveIndexes.get(i)]; - int pal = this.typeTMPaletteNumber(m.type); - writeWord(arm9, offsPals + itmNum * 4 + 2, pal); - } - for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { - int itmNum = Gen5Constants.tmBlockTwoOffset + i; - Move m = this.moves[moveIndexes.get(i - + Gen5Constants.tmBlockOneCount)]; - int pal = this.typeTMPaletteNumber(m.type); - writeWord(arm9, offsPals + itmNum * 4 + 2, pal); - } - } - } else { - } - } - - private static RomFunctions.StringSizeDeterminer ssd = new RomFunctions.StringSizeDeterminer() { - - @Override - public int lengthFor(String encodedText) { - int offs = 0; - int len = encodedText.length(); - while (encodedText.indexOf("\\x", offs) != -1) { - len -= 5; - offs = encodedText.indexOf("\\x", offs) + 1; - } - return len; - } - }; - - @Override - public int getTMCount() { - return Gen5Constants.tmCount; - } - - @Override - public int getHMCount() { - return Gen5Constants.hmCount; - } - - @Override - public Map<Pokemon, boolean[]> getTMHMCompatibility() { - Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); - for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { - byte[] data = pokeNarc.files.get(i); - Pokemon pkmn = pokes[i]; - boolean[] flags = new boolean[Gen5Constants.tmCount - + Gen5Constants.hmCount + 1]; - for (int j = 0; j < 13; j++) { - readByteIntoFlags(data, flags, j * 8 + 1, - Gen5Constants.bsTMHMCompatOffset + j); - } - compat.put(pkmn, flags); - } - return compat; - } - - @Override - public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData) { - for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { - Pokemon pkmn = compatEntry.getKey(); - boolean[] flags = compatEntry.getValue(); - byte[] data = pokeNarc.files.get(pkmn.number); - for (int j = 0; j < 13; j++) { - data[Gen5Constants.bsTMHMCompatOffset + j] = getByteFromFlags( - flags, j * 8 + 1); - } - } - } - - @Override - public boolean hasMoveTutors() { - return romEntry.romType == Gen5Constants.Type_BW2; - } - - @Override - public List<Integer> getMoveTutorMoves() { - if (!hasMoveTutors()) { - return new ArrayList<Integer>(); - } - int baseOffset = romEntry.getInt("MoveTutorDataOffset"); - int amount = Gen5Constants.bw2MoveTutorCount; - int bytesPer = Gen5Constants.bw2MoveTutorBytesPerEntry; - List<Integer> mtMoves = new ArrayList<Integer>(); - try { - byte[] mtFile = readOverlay(romEntry.getInt("MoveTutorOvlNumber")); - for (int i = 0; i < amount; i++) { - mtMoves.add(readWord(mtFile, baseOffset + i * bytesPer)); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return mtMoves; - } - - @Override - public void setMoveTutorMoves(List<Integer> moves) { - if (!hasMoveTutors()) { - return; - } - int baseOffset = romEntry.getInt("MoveTutorDataOffset"); - int amount = Gen5Constants.bw2MoveTutorCount; - int bytesPer = Gen5Constants.bw2MoveTutorBytesPerEntry; - if (moves.size() != amount) { - return; - } - try { - byte[] mtFile = readOverlay(romEntry.getInt("MoveTutorOvlNumber")); - for (int i = 0; i < amount; i++) { - writeWord(mtFile, baseOffset + i * bytesPer, moves.get(i)); - } - writeOverlay(romEntry.getInt("MoveTutorOvlNumber"), mtFile); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public Map<Pokemon, boolean[]> getMoveTutorCompatibility() { - if (!hasMoveTutors()) { - return new TreeMap<Pokemon, boolean[]>(); - } - Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); - int[] countsPersonalOrder = new int[] { 15, 17, 13, 15 }; - int[] countsMoveOrder = new int[] { 13, 15, 15, 17 }; - int[] personalToMoveOrder = new int[] { 1, 3, 0, 2 }; - for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { - byte[] data = pokeNarc.files.get(i); - Pokemon pkmn = pokes[i]; - boolean[] flags = new boolean[Gen5Constants.bw2MoveTutorCount + 1]; - for (int mt = 0; mt < 4; mt++) { - boolean[] mtflags = new boolean[countsPersonalOrder[mt] + 1]; - for (int j = 0; j < 4; j++) { - readByteIntoFlags(data, mtflags, j * 8 + 1, - Gen5Constants.bsMTCompatOffset + mt * 4 + j); - } - int offsetOfThisData = 0; - for (int cmoIndex = 0; cmoIndex < personalToMoveOrder[mt]; cmoIndex++) { - offsetOfThisData += countsMoveOrder[cmoIndex]; - } - System.arraycopy(mtflags, 1, flags, offsetOfThisData + 1, - countsPersonalOrder[mt]); - } - compat.put(pkmn, flags); - } - return compat; - } - - @Override - public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData) { - if (!hasMoveTutors()) { - return; - } - // BW2 move tutor flags aren't using the same order as the move tutor - // move data. - // We unscramble them from move data order to personal.narc flag order. - int[] countsPersonalOrder = new int[] { 15, 17, 13, 15 }; - int[] countsMoveOrder = new int[] { 13, 15, 15, 17 }; - int[] personalToMoveOrder = new int[] { 1, 3, 0, 2 }; - for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { - Pokemon pkmn = compatEntry.getKey(); - boolean[] flags = compatEntry.getValue(); - byte[] data = pokeNarc.files.get(pkmn.number); - for (int mt = 0; mt < 4; mt++) { - int offsetOfThisData = 0; - for (int cmoIndex = 0; cmoIndex < personalToMoveOrder[mt]; cmoIndex++) { - offsetOfThisData += countsMoveOrder[cmoIndex]; - } - boolean[] mtflags = new boolean[countsPersonalOrder[mt] + 1]; - System.arraycopy(flags, offsetOfThisData + 1, mtflags, 1, - countsPersonalOrder[mt]); - for (int j = 0; j < 4; j++) { - data[Gen5Constants.bsMTCompatOffset + mt * 4 + j] = getByteFromFlags( - mtflags, j * 8 + 1); - } - } - } - } - - private int find(byte[] data, String hexString) { - if (hexString.length() % 2 != 0) { - return -3; // error - } - byte[] searchFor = new byte[hexString.length() / 2]; - for (int i = 0; i < searchFor.length; i++) { - searchFor[i] = (byte) Integer.parseInt( - hexString.substring(i * 2, i * 2 + 2), 16); - } - List<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); - } - } - - private List<String> getStrings(boolean isStoryText, int index) { - NARCContents baseNARC = isStoryText ? storyTextNarc : stringsNarc; - byte[] rawFile = baseNARC.files.get(index); - return new ArrayList<String>(PPTxtHandler.readTexts(rawFile)); - } - - private void setStrings(boolean isStoryText, int index, List<String> strings) { - NARCContents baseNARC = isStoryText ? storyTextNarc : stringsNarc; - byte[] oldRawFile = baseNARC.files.get(index); - byte[] newRawFile = PPTxtHandler.saveEntry(oldRawFile, strings); - baseNARC.files.set(index, newRawFile); - } - - @Override - public String getROMName() { - return "Pokemon " + romEntry.name; - } - - @Override - public String getROMCode() { - return romEntry.romCode; - } - - @Override - public String getSupportLevel() { - return romEntry.staticPokemonSupport ? "Complete" : "No Static Pokemon"; - } - - @Override - public boolean hasTimeBasedEncounters() { - return true; // All BW/BW2 do [seasons] - } - - private void populateEvolutions() { - for (Pokemon pkmn : pokes) { - if (pkmn != null) { - pkmn.evolutionsFrom.clear(); - pkmn.evolutionsTo.clear(); - } - } - - // Read NARC - try { - NARCContents evoNARC = readNARC(romEntry - .getString("PokemonEvolutions")); - for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { - Pokemon pk = pokes[i]; - byte[] evoEntry = evoNARC.files.get(i); - for (int evo = 0; evo < 7; evo++) { - int method = readWord(evoEntry, evo * 6); - int species = readWord(evoEntry, evo * 6 + 4); - if (method >= 1 - && method <= Gen5Constants.evolutionMethodCount - && species >= 1) { - EvolutionType et = EvolutionType.fromIndex(5, method); - int extraInfo = readWord(evoEntry, evo * 6 + 2); - Evolution evol = new Evolution(pk, pokes[species], - true, et, extraInfo); - if (!pk.evolutionsFrom.contains(evol)) { - pk.evolutionsFrom.add(evol); - pokes[species].evolutionsTo.add(evol); - } - } - } - // split evos don't carry stats - if (pk.evolutionsFrom.size() > 1) { - for (Evolution e : pk.evolutionsFrom) { - e.carryStats = false; - } - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void writeEvolutions() { - try { - NARCContents evoNARC = readNARC(romEntry - .getString("PokemonEvolutions")); - for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { - byte[] evoEntry = evoNARC.files.get(i); - Pokemon pk = pokes[i]; - int evosWritten = 0; - for (Evolution evo : pk.evolutionsFrom) { - writeWord(evoEntry, evosWritten * 6, evo.type.toIndex(5)); - writeWord(evoEntry, evosWritten * 6 + 2, evo.extraInfo); - writeWord(evoEntry, evosWritten * 6 + 4, evo.to.number); - evosWritten++; - if (evosWritten == 7) { - break; - } - } - while (evosWritten < 7) { - writeWord(evoEntry, evosWritten * 6, 0); - writeWord(evoEntry, evosWritten * 6 + 2, 0); - writeWord(evoEntry, evosWritten * 6 + 4, 0); - evosWritten++; - } - } - writeNARC(romEntry.getString("PokemonEvolutions"), evoNARC); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public void removeTradeEvolutions(boolean changeMoveEvos) { - Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); - log("--Removing Trade Evolutions--"); - Set<Evolution> extraEvolutions = new HashSet<Evolution>(); - 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)) { - 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; - logEvoChangeLevel(evo.from.name, evo.to.name, - levelLearntAt); - } - // Pure Trade - if (evo.type == EvolutionType.TRADE) { - // Replace w/ level 37 - evo.type = EvolutionType.LEVEL; - evo.extraInfo = 37; - logEvoChangeLevel(evo.from.name, evo.to.name, 37); - } - // Trade w/ Item - if (evo.type == EvolutionType.TRADE_ITEM) { - // Get the current item & evolution - int item = evo.extraInfo; - if (evo.from.number == Gen5Constants.slowpokeIndex) { - // 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 = Gen5Constants.waterStoneIndex; // water - // stone - logEvoChangeStone(evo.from.name, evo.to.name, - itemNames - .get(Gen5Constants.waterStoneIndex)); - } else { - logEvoChangeLevelWithItem(evo.from.name, - evo.to.name, itemNames.get(item)); - // Replace, for this entry, w/ - // Level up w/ Held Item at Day - evo.type = EvolutionType.LEVEL_ITEM_DAY; - // now add an extra evo for - // Level up w/ Held Item at Night - Evolution extraEntry = new Evolution(evo.from, - evo.to, true, - EvolutionType.LEVEL_ITEM_NIGHT, item); - extraEvolutions.add(extraEntry); - } - } - if (evo.type == EvolutionType.TRADE_SPECIAL) { - // This is the karrablast <-> shelmet trade - // Replace it with Level up w/ Other Species in Party - // (22) - // Based on what species we're currently dealing with - evo.type = EvolutionType.LEVEL_WITH_OTHER; - evo.extraInfo = (evo.from.number == Gen5Constants.karrablastIndex ? Gen5Constants.shelmetIndex - : Gen5Constants.karrablastIndex); - logEvoChangeLevelWithPkmn( - evo.from.name, - evo.to.name, - pokes[(evo.from.number == Gen5Constants.karrablastIndex ? Gen5Constants.shelmetIndex - : Gen5Constants.karrablastIndex)].name); - } - } - - pkmn.evolutionsFrom.addAll(extraEvolutions); - for (Evolution ev : extraEvolutions) { - ev.to.evolutionsTo.add(ev); - } - } - } - logBlankLine(); - } - - @Override - public List<String> getTrainerNames() { - List<String> tnames = getStrings(false, - romEntry.getInt("TrainerNamesTextOffset")); - tnames.remove(0); // blank one - // Tack the mugshot names on the end - List<String> mnames = getStrings(false, - romEntry.getInt("TrainerMugshotsTextOffset")); - for (String mname : mnames) { - if (!mname.isEmpty() - && (mname.charAt(0) >= 'A' && mname.charAt(0) <= 'Z')) { - tnames.add(mname); - } - } - return tnames; - } - - @Override - public int maxTrainerNameLength() { - return 10;// based off the english ROMs - } - - @Override - public void setTrainerNames(List<String> trainerNames) { - List<String> tnames = getStrings(false, - romEntry.getInt("TrainerNamesTextOffset")); - // Grab the mugshot names off the back of the list of trainer names - // we got back - List<String> mnames = getStrings(false, - romEntry.getInt("TrainerMugshotsTextOffset")); - int trNamesSize = trainerNames.size(); - for (int i = mnames.size() - 1; i >= 0; i--) { - String origMName = mnames.get(i); - if (!origMName.isEmpty() - && (origMName.charAt(0) >= 'A' && origMName.charAt(0) <= 'Z')) { - // Grab replacement - String replacement = trainerNames.remove(--trNamesSize); - mnames.set(i, replacement); - } - } - // Save back mugshot names - setStrings(false, romEntry.getInt("TrainerMugshotsTextOffset"), mnames); - - // Now save the rest of trainer names - List<String> newTNames = new ArrayList<String>(trainerNames); - newTNames.add(0, tnames.get(0)); // the 0-entry, preserve it - setStrings(false, romEntry.getInt("TrainerNamesTextOffset"), newTNames); - - } - - @Override - public TrainerNameMode trainerNameMode() { - return TrainerNameMode.MAX_LENGTH; - } - - @Override - public List<Integer> getTCNameLengthsByTrainer() { - // not needed - return new ArrayList<Integer>(); - } - - @Override - public List<String> getTrainerClassNames() { - return getStrings(false, romEntry.getInt("TrainerClassesTextOffset")); - } - - @Override - public void setTrainerClassNames(List<String> trainerClassNames) { - setStrings(false, romEntry.getInt("TrainerClassesTextOffset"), - trainerClassNames); - } - - @Override - public int maxTrainerClassNameLength() { - return 12;// based off the english ROMs - } - - @Override - public boolean fixedTrainerClassNamesLength() { - return false; - } - - @Override - public String getDefaultExtension() { - return "nds"; - } - - @Override - public int abilitiesPerPokemon() { - return 3; - } - - @Override - public int highestAbilityIndex() { - return Gen5Constants.highestAbilityIndex; - } - - @Override - public int internalStringLength(String string) { - return ssd.lengthFor(string); - } - - @Override - public void applySignature() { - // For now, do nothing. - - } - - @Override - public ItemList getAllowedItems() { - return Gen5Constants.allowedItems; - } - - @Override - public ItemList getNonBadItems() { - return Gen5Constants.nonBadItems; - } - - @Override - public String[] getItemNames() { - return itemNames.toArray(new String[0]); - } - - @Override - public String abilityName(int number) { - return abilityNames.get(number); - } - - private List<Integer> getFieldItems() { - List<Integer> fieldItems = new ArrayList<Integer>(); - // normal items - int scriptFileNormal = romEntry.getInt("ItemBallsScriptOffset"); - int scriptFileHidden = romEntry.getInt("HiddenItemsScriptOffset"); - int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); - int[] skipTableH = romEntry.arrayEntries.get("HiddenItemsSkip"); - int setVarNormal = Gen5Constants.normalItemSetVarCommand; - int setVarHidden = Gen5Constants.hiddenItemSetVarCommand; - - byte[] itemScripts = scriptNarc.files.get(scriptFileNormal); - int offset = 0; - int skipTableOffset = 0; - while (true) { - int part1 = readWord(itemScripts, offset); - if (part1 == Gen5Constants.scriptListTerminator) { - // done - break; - } - int offsetInFile = readRelativePointer(itemScripts, offset); - offset += 4; - if (offsetInFile > itemScripts.length) { - break; - } - if (skipTableOffset < skipTable.length - && (skipTable[skipTableOffset] == (offset / 4) - 1)) { - skipTableOffset++; - continue; - } - int command = readWord(itemScripts, offsetInFile + 2); - int variable = readWord(itemScripts, offsetInFile + 4); - if (command == setVarNormal - && variable == Gen5Constants.normalItemVarSet) { - int item = readWord(itemScripts, offsetInFile + 6); - fieldItems.add(item); - } - - } - - // hidden items - byte[] hitemScripts = scriptNarc.files.get(scriptFileHidden); - offset = 0; - skipTableOffset = 0; - while (true) { - int part1 = readWord(hitemScripts, offset); - if (part1 == Gen5Constants.scriptListTerminator) { - // done - break; - } - int offsetInFile = readRelativePointer(hitemScripts, offset); - if (offsetInFile > hitemScripts.length) { - break; - } - offset += 4; - if (skipTableOffset < skipTable.length - && (skipTableH[skipTableOffset] == (offset / 4) - 1)) { - skipTableOffset++; - continue; - } - int command = readWord(hitemScripts, offsetInFile + 2); - int variable = readWord(hitemScripts, offsetInFile + 4); - if (command == setVarHidden - && variable == Gen5Constants.hiddenItemVarSet) { - int item = readWord(hitemScripts, offsetInFile + 6); - fieldItems.add(item); - } - - } - - return fieldItems; - } - - private void setFieldItems(List<Integer> fieldItems) { - Iterator<Integer> iterItems = fieldItems.iterator(); - - // normal items - int scriptFileNormal = romEntry.getInt("ItemBallsScriptOffset"); - int scriptFileHidden = romEntry.getInt("HiddenItemsScriptOffset"); - int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); - int[] skipTableH = romEntry.arrayEntries.get("HiddenItemsSkip"); - int setVarNormal = Gen5Constants.normalItemSetVarCommand; - int setVarHidden = Gen5Constants.hiddenItemSetVarCommand; - - byte[] itemScripts = scriptNarc.files.get(scriptFileNormal); - int offset = 0; - int skipTableOffset = 0; - while (true) { - int part1 = readWord(itemScripts, offset); - if (part1 == Gen5Constants.scriptListTerminator) { - // done - break; - } - int offsetInFile = readRelativePointer(itemScripts, offset); - offset += 4; - if (offsetInFile > itemScripts.length) { - break; - } - if (skipTableOffset < skipTable.length - && (skipTable[skipTableOffset] == (offset / 4) - 1)) { - skipTableOffset++; - continue; - } - int command = readWord(itemScripts, offsetInFile + 2); - int variable = readWord(itemScripts, offsetInFile + 4); - if (command == setVarNormal - && variable == Gen5Constants.normalItemVarSet) { - int item = iterItems.next(); - writeWord(itemScripts, offsetInFile + 6, item); - } - - } - - // hidden items - byte[] hitemScripts = scriptNarc.files.get(scriptFileHidden); - offset = 0; - skipTableOffset = 0; - while (true) { - int part1 = readWord(hitemScripts, offset); - if (part1 == Gen5Constants.scriptListTerminator) { - // done - break; - } - int offsetInFile = readRelativePointer(hitemScripts, offset); - offset += 4; - if (offsetInFile > hitemScripts.length) { - break; - } - if (skipTableOffset < skipTable.length - && (skipTableH[skipTableOffset] == (offset / 4) - 1)) { - skipTableOffset++; - continue; - } - int command = readWord(hitemScripts, offsetInFile + 2); - int variable = readWord(hitemScripts, offsetInFile + 4); - if (command == setVarHidden - && variable == Gen5Constants.hiddenItemVarSet) { - int item = iterItems.next(); - writeWord(hitemScripts, offsetInFile + 6, item); - } - - } - } - - private int tmFromIndex(int index) { - if (index >= Gen5Constants.tmBlockOneOffset - && index < Gen5Constants.tmBlockOneOffset - + Gen5Constants.tmBlockOneCount) { - return index - (Gen5Constants.tmBlockOneOffset - 1); - } else { - return (index + Gen5Constants.tmBlockOneCount) - - (Gen5Constants.tmBlockTwoOffset - 1); - } - } - - private int indexFromTM(int tm) { - if (tm >= 1 && tm <= Gen5Constants.tmBlockOneCount) { - return tm + (Gen5Constants.tmBlockOneOffset - 1); - } else { - return tm - + (Gen5Constants.tmBlockTwoOffset - 1 - Gen5Constants.tmBlockOneCount); - } - } - - @Override - public List<Integer> getCurrentFieldTMs() { - List<Integer> fieldItems = this.getFieldItems(); - List<Integer> fieldTMs = new ArrayList<Integer>(); - - for (int item : fieldItems) { - if (Gen5Constants.allowedItems.isTM(item)) { - fieldTMs.add(tmFromIndex(item)); - } - } - - return fieldTMs; - } - - @Override - public void setFieldTMs(List<Integer> fieldTMs) { - List<Integer> fieldItems = this.getFieldItems(); - int fiLength = fieldItems.size(); - Iterator<Integer> iterTMs = fieldTMs.iterator(); - - for (int i = 0; i < fiLength; i++) { - int oldItem = fieldItems.get(i); - if (Gen5Constants.allowedItems.isTM(oldItem)) { - int newItem = indexFromTM(iterTMs.next()); - fieldItems.set(i, newItem); - } - } - - this.setFieldItems(fieldItems); - } - - @Override - public List<Integer> getRegularFieldItems() { - List<Integer> fieldItems = this.getFieldItems(); - List<Integer> fieldRegItems = new ArrayList<Integer>(); - - for (int item : fieldItems) { - if (Gen5Constants.allowedItems.isAllowed(item) - && !(Gen5Constants.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(); - - for (int i = 0; i < fiLength; i++) { - int oldItem = fieldItems.get(i); - if (!(Gen5Constants.allowedItems.isTM(oldItem)) - && Gen5Constants.allowedItems.isAllowed(oldItem)) { - int newItem = iterNewItems.next(); - fieldItems.set(i, newItem); - } - } - - this.setFieldItems(fieldItems); - } - - @Override - public List<Integer> getRequiredFieldTMs() { - if (romEntry.romType == Gen5Constants.Type_BW) { - return Gen5Constants.bw1RequiredFieldTMs; - } else { - return Gen5Constants.bw2RequiredFieldTMs; - } - } - - @Override - public List<IngameTrade> getIngameTrades() { - List<IngameTrade> trades = new ArrayList<IngameTrade>(); - try { - NARCContents tradeNARC = this.readNARC(romEntry - .getString("InGameTrades")); - List<String> tradeStrings = getStrings(false, - romEntry.getInt("IngameTradesTextOffset")); - int[] unused = romEntry.arrayEntries.get("TradesUnused"); - int unusedOffset = 0; - int tableSize = tradeNARC.files.size(); - - for (int entry = 0; entry < tableSize; entry++) { - if (unusedOffset < unused.length - && unused[unusedOffset] == entry) { - unusedOffset++; - continue; - } - IngameTrade trade = new IngameTrade(); - byte[] tfile = tradeNARC.files.get(entry); - trade.nickname = tradeStrings.get(entry * 2); - trade.givenPokemon = pokes[readLong(tfile, 4)]; - trade.ivs = new int[6]; - for (int iv = 0; iv < 6; iv++) { - trade.ivs[iv] = readLong(tfile, 0x10 + iv * 4); - } - trade.otId = readWord(tfile, 0x34); - trade.item = readLong(tfile, 0x4C); - trade.otName = tradeStrings.get(entry * 2 + 1); - trade.requestedPokemon = pokes[readLong(tfile, 0x5C)]; - trades.add(trade); - } - } catch (Exception ex) { - throw new RuntimeException(ex); - } - - return trades; - - } - - @Override - public void setIngameTrades(List<IngameTrade> trades) { - // info - int tradeOffset = 0; - try { - NARCContents tradeNARC = this.readNARC(romEntry - .getString("InGameTrades")); - List<String> tradeStrings = getStrings(false, - romEntry.getInt("IngameTradesTextOffset")); - int tradeCount = tradeNARC.files.size(); - int[] unused = romEntry.arrayEntries.get("TradesUnused"); - int unusedOffset = 0; - for (int i = 0; i < tradeCount; i++) { - if (unusedOffset < unused.length && unused[unusedOffset] == i) { - unusedOffset++; - continue; - } - byte[] tfile = tradeNARC.files.get(i); - IngameTrade trade = trades.get(tradeOffset++); - tradeStrings.set(i * 2, trade.nickname); - tradeStrings.set(i * 2 + 1, trade.otName); - writeLong(tfile, 4, trade.givenPokemon.number); - writeLong(tfile, 8, 0); // disable forme - for (int iv = 0; iv < 6; iv++) { - writeLong(tfile, 0x10 + iv * 4, trade.ivs[iv]); - } - writeLong(tfile, 0x2C, 0xFF); // random nature - writeWord(tfile, 0x34, trade.otId); - writeLong(tfile, 0x4C, trade.item); - writeLong(tfile, 0x5C, trade.requestedPokemon.number); - } - this.writeNARC(romEntry.getString("InGameTrades"), tradeNARC); - this.setStrings(false, romEntry.getInt("IngameTradesTextOffset"), - tradeStrings); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public boolean hasDVs() { - return false; - } - - @Override - public int generationOfPokemon() { - return 5; - } - - @Override - public void removeEvosForPokemonPool() { - // slightly more complicated than gen2/3 - // we have to update a "baby table" too - List<Pokemon> pokemonIncluded = this.mainPokemonList; - Set<Evolution> keepEvos = new HashSet<Evolution>(); - 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 { - NARCContents babyNARC = readNARC(romEntry.getString("BabyPokemon")); - // baby pokemon - for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { - Pokemon baby = pokes[i]; - while (baby.evolutionsTo.size() > 0) { - // Grab the first "to evolution" even if there are multiple - baby = baby.evolutionsTo.get(0).from; - } - writeWord(babyNARC.files.get(i), 0, baby.number); - } - // finish up - writeNARC(romEntry.getString("BabyPokemon"), babyNARC); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean supportsFourStartingMoves() { - return true; - } - - @Override - public List<Integer> getFieldMoves() { - // cut, fly, surf, strength, flash, dig, teleport, waterfall, - // sweet scent, dive - return Gen5Constants.fieldMoves; - } - - @Override - public List<Integer> getEarlyRequiredHMMoves() { - // BW1: cut - // BW2: none - if (romEntry.romType == Gen5Constants.Type_BW2) { - return Gen5Constants.bw2EarlyRequiredHMMoves; - } else { - return Gen5Constants.bw1EarlyRequiredHMMoves; - } - } - - @Override - public BufferedImage getMascotImage() { - try { - Pokemon pk = randomPokemon(); - NARCContents pokespritesNARC = this.readNARC(romEntry - .getString("PokemonGraphics")); - - // First prepare the palette, it's the easy bit - byte[] rawPalette = pokespritesNARC.files.get(pk.number * 20 + 18); - int[] palette = new int[16]; - for (int i = 1; i < 16; i++) { - palette[i] = GFXFunctions.conv16BitColorToARGB(readWord( - rawPalette, 40 + i * 2)); - } - - // Get the picture and uncompress it. - byte[] compressedPic = pokespritesNARC.files.get(pk.number * 20); - byte[] uncompressedPic = DSDecmp.Decompress(compressedPic); - - // Output to 64x144 tiled image to prepare for unscrambling - BufferedImage bim = GFXFunctions.drawTiledImage(uncompressedPic, palette, 48, 64, 144, 4); - - // Unscramble the above onto a 96x96 canvas - BufferedImage finalImage = new BufferedImage(96, 96, BufferedImage.TYPE_INT_ARGB); - Graphics g = finalImage.getGraphics(); - g.drawImage(bim, 0, 0, 64, 64, 0, 0, 64, 64, null); - g.drawImage(bim, 64, 0, 96, 8, 0, 64, 32, 72, null); - g.drawImage(bim, 64, 8, 96, 16, 32, 64, 64, 72, null); - g.drawImage(bim, 64, 16, 96, 24, 0, 72, 32, 80, null); - g.drawImage(bim, 64, 24, 96, 32, 32, 72, 64, 80, null); - g.drawImage(bim, 64, 32, 96, 40, 0, 80, 32, 88, null); - g.drawImage(bim, 64, 40, 96, 48, 32, 80, 64, 88, null); - g.drawImage(bim, 64, 48, 96, 56, 0, 88, 32, 96, null); - g.drawImage(bim, 64, 56, 96, 64, 32, 88, 64, 96, null); - g.drawImage(bim, 0, 64, 64, 96, 0, 96, 64, 128, null); - g.drawImage(bim, 64, 64, 96, 72, 0, 128, 32, 136, null); - g.drawImage(bim, 64, 72, 96, 80, 32, 128, 64, 136, null); - g.drawImage(bim, 64, 80, 96, 88, 0, 136, 32, 144, null); - g.drawImage(bim, 64, 88, 96, 96, 32, 136, 64, 144, null); - - // Phew, all done. - return finalImage; - } catch (IOException e) { - throw new RuntimeException(e); - } - } + public static class Factory extends RomHandler.Factory { + + @Override + public Gen5RomHandler create(Random random, PrintStream logStream) { + return new Gen5RomHandler(random, logStream); + } + + public boolean isLoadable(String filename) { + return detectNDSRomInner(getROMCodeFromFile(filename)); + } + } + + public Gen5RomHandler(Random random) { + super(random, null); + } + + public Gen5RomHandler(Random random, PrintStream logStream) { + super(random, logStream); + } + + private static class OffsetWithinEntry { + private int entry; + private int offset; + } + + private static class RomEntry { + private String name; + private String romCode; + private int romType; + private boolean staticPokemonSupport = false, copyStaticPokemon = false; + private Map<String, String> strings = new HashMap<String, String>(); + private Map<String, Integer> numbers = new HashMap<String, Integer>(); + private Map<String, String> tweakFiles = new HashMap<String, String>(); + private Map<String, int[]> arrayEntries = new HashMap<String, int[]>(); + private Map<String, OffsetWithinEntry[]> offsetArrayEntries = new HashMap<String, OffsetWithinEntry[]>(); + private List<StaticPokemon> staticPokemon = new ArrayList<StaticPokemon>(); + + 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 static List<RomEntry> roms; + + static { + loadROMInfo(); + } + + private static void loadROMInfo() { + roms = new ArrayList<RomEntry>(); + RomEntry current = null; + try { + Scanner sc = new Scanner(FileFunctions.openConfig("gen5_offsets.ini"), "UTF-8"); + while (sc.hasNextLine()) { + String q = sc.nextLine().trim(); + if (q.contains("//")) { + q = q.substring(0, q.indexOf("//")).trim(); + } + if (!q.isEmpty()) { + if (q.startsWith("[") && q.endsWith("]")) { + // New rom + current = new RomEntry(); + current.name = q.substring(1, q.length() - 1); + roms.add(current); + } else { + String[] r = q.split("=", 2); + if (r.length == 1) { + System.err.println("invalid entry " + q); + continue; + } + if (r[1].endsWith("\r\n")) { + r[1] = r[1].substring(0, r[1].length() - 2); + } + r[1] = r[1].trim(); + if (r[0].equals("Game")) { + current.romCode = r[1]; + } else if (r[0].equals("Type")) { + if (r[1].equalsIgnoreCase("BW2")) { + current.romType = Gen5Constants.Type_BW2; + } else { + current.romType = Gen5Constants.Type_BW; + } + } else if (r[0].equals("CopyFrom")) { + for (RomEntry otherEntry : roms) { + if (r[1].equalsIgnoreCase(otherEntry.romCode)) { + // copy from here + current.arrayEntries.putAll(otherEntry.arrayEntries); + current.numbers.putAll(otherEntry.numbers); + current.strings.putAll(otherEntry.strings); + current.offsetArrayEntries.putAll(otherEntry.offsetArrayEntries); + if (current.copyStaticPokemon) { + current.staticPokemon.addAll(otherEntry.staticPokemon); + current.staticPokemonSupport = true; + } else { + current.staticPokemonSupport = false; + } + } + } + } else if (r[0].equals("StaticPokemon[]")) { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + int[] offs = new int[offsets.length]; + int[] files = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + String[] parts = off.split("\\:"); + files[c] = parseRIInt(parts[0]); + offs[c++] = parseRIInt(parts[1]); + } + StaticPokemon sp = new StaticPokemon(); + sp.files = files; + sp.offsets = offs; + current.staticPokemon.add(sp); + } else { + String[] parts = r[1].split("\\:"); + int files = parseRIInt(parts[0]); + int offs = parseRIInt(parts[1]); + StaticPokemon sp = new StaticPokemon(); + sp.files = new int[] { files }; + sp.offsets = new int[] { offs }; + } + } else if (r[0].equals("StaticPokemonSupport")) { + int spsupport = parseRIInt(r[1]); + current.staticPokemonSupport = (spsupport > 0); + } else if (r[0].equals("CopyStaticPokemon")) { + int csp = parseRIInt(r[1]); + current.copyStaticPokemon = (csp > 0); + } else if (r[0].startsWith("StarterOffsets") || r[0].equals("StaticPokemonFormValues")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + OffsetWithinEntry[] offs = new OffsetWithinEntry[offsets.length]; + int c = 0; + for (String off : offsets) { + String[] parts = off.split("\\:"); + OffsetWithinEntry owe = new OffsetWithinEntry(); + owe.entry = parseRIInt(parts[0]); + owe.offset = parseRIInt(parts[1]); + offs[c++] = owe; + } + current.offsetArrayEntries.put(r[0], offs); + } else if (r[0].endsWith("Tweak")) { + current.tweakFiles.put(r[0], r[1]); + } else { + if (r[1].startsWith("[") && r[1].endsWith("]")) { + String[] offsets = r[1].substring(1, r[1].length() - 1).split(","); + if (offsets.length == 1 && offsets[0].trim().isEmpty()) { + current.arrayEntries.put(r[0], new int[0]); + } else { + int[] offs = new int[offsets.length]; + int c = 0; + for (String off : offsets) { + offs[c++] = parseRIInt(off); + } + current.arrayEntries.put(r[0], offs); + } + } else if (r[0].endsWith("Offset") || r[0].endsWith("Count") || r[0].endsWith("Number")) { + int offs = parseRIInt(r[1]); + current.numbers.put(r[0], offs); + } else { + current.strings.put(r[0], r[1]); + } + } + } + } + } + sc.close(); + } catch (FileNotFoundException e) { + } + } + + 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; + } + } + + // This ROM + private Pokemon[] pokes; + private List<Pokemon> pokemonList; + private Move[] moves; + private RomEntry romEntry; + private byte[] arm9; + private List<String> abilityNames; + private List<String> itemNames; + private boolean loadedWildMapNames; + private Map<Integer, String> wildMapNames; + + private NARCContents pokeNarc, moveNarc, stringsNarc, storyTextNarc, scriptNarc; + + @Override + protected boolean detectNDSRom(String ndsCode) { + return detectNDSRomInner(ndsCode); + } + + private static boolean detectNDSRomInner(String ndsCode) { + return entryFor(ndsCode) != null; + } + + private static RomEntry entryFor(String ndsCode) { + if (ndsCode == null) { + return null; + } + + for (RomEntry re : roms) { + if (ndsCode.equals(re.romCode)) { + return re; + } + } + return null; + } + + @Override + protected void loadedROM(String romCode) { + this.romEntry = entryFor(romCode); + try { + arm9 = readARM9(); + } catch (IOException e) { + throw new RuntimeException(e); + } + try { + stringsNarc = readNARC(romEntry.getString("TextStrings")); + storyTextNarc = readNARC(romEntry.getString("TextStory")); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try { + scriptNarc = readNARC(romEntry.getString("Scripts")); + } catch (IOException e) { + throw new RuntimeException(e); + } + loadPokemonStats(); + pokemonList = Arrays.asList(pokes); + loadMoves(); + + abilityNames = getStrings(false, romEntry.getInt("AbilityNamesTextOffset")); + itemNames = getStrings(false, romEntry.getInt("ItemNamesTextOffset")); + loadedWildMapNames = false; + } + + private void loadPokemonStats() { + try { + pokeNarc = this.readNARC(romEntry.getString("PokemonStats")); + String[] pokeNames = readPokemonNames(); + pokes = new Pokemon[Gen5Constants.pokemonCount + 1]; + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + pokes[i] = new Pokemon(); + pokes[i].number = i; + loadBasicPokeStats(pokes[i], pokeNarc.files.get(i)); + // Name? + pokes[i].name = pokeNames[i]; + } + populateEvolutions(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + private void loadMoves() { + try { + moveNarc = this.readNARC(romEntry.getString("MoveData")); + moves = new Move[Gen5Constants.moveCount + 1]; + List<String> moveNames = getStrings(false, romEntry.getInt("MoveNamesTextOffset")); + for (int i = 1; i <= Gen5Constants.moveCount; i++) { + byte[] moveData = moveNarc.files.get(i); + moves[i] = new Move(); + moves[i].name = moveNames.get(i); + moves[i].number = i; + moves[i].internalId = i; + moves[i].hitratio = (moveData[4] & 0xFF); + moves[i].power = moveData[3] & 0xFF; + moves[i].pp = moveData[5] & 0xFF; + moves[i].type = Gen5Constants.typeTable[moveData[0] & 0xFF]; + moves[i].category = Gen5Constants.moveCategoryIndices[moveData[2] & 0xFF]; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + private void loadBasicPokeStats(Pokemon pkmn, byte[] stats) { + pkmn.hp = stats[Gen5Constants.bsHPOffset] & 0xFF; + pkmn.attack = stats[Gen5Constants.bsAttackOffset] & 0xFF; + pkmn.defense = stats[Gen5Constants.bsDefenseOffset] & 0xFF; + pkmn.speed = stats[Gen5Constants.bsSpeedOffset] & 0xFF; + pkmn.spatk = stats[Gen5Constants.bsSpAtkOffset] & 0xFF; + pkmn.spdef = stats[Gen5Constants.bsSpDefOffset] & 0xFF; + // Type + pkmn.primaryType = Gen5Constants.typeTable[stats[Gen5Constants.bsPrimaryTypeOffset] & 0xFF]; + pkmn.secondaryType = Gen5Constants.typeTable[stats[Gen5Constants.bsSecondaryTypeOffset] & 0xFF]; + // Only one type? + if (pkmn.secondaryType == pkmn.primaryType) { + pkmn.secondaryType = null; + } + pkmn.catchRate = stats[Gen5Constants.bsCatchRateOffset] & 0xFF; + pkmn.growthCurve = ExpCurve.fromByte(stats[Gen5Constants.bsGrowthCurveOffset]); + + pkmn.ability1 = stats[Gen5Constants.bsAbility1Offset] & 0xFF; + pkmn.ability2 = stats[Gen5Constants.bsAbility2Offset] & 0xFF; + pkmn.ability3 = stats[Gen5Constants.bsAbility3Offset] & 0xFF; + + // Held Items? + int item1 = readWord(stats, Gen5Constants.bsCommonHeldItemOffset); + int item2 = readWord(stats, Gen5Constants.bsRareHeldItemOffset); + + if (item1 == item2) { + // guaranteed + pkmn.guaranteedHeldItem = item1; + pkmn.commonHeldItem = 0; + pkmn.rareHeldItem = 0; + pkmn.darkGrassHeldItem = 0; + } else { + pkmn.guaranteedHeldItem = 0; + pkmn.commonHeldItem = item1; + pkmn.rareHeldItem = item2; + pkmn.darkGrassHeldItem = readWord(stats, Gen5Constants.bsDarkGrassHeldItemOffset); + } + } + + private String[] readPokemonNames() { + String[] pokeNames = new String[Gen5Constants.pokemonCount + 1]; + List<String> nameList = getStrings(false, romEntry.getInt("PokemonNamesTextOffset")); + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + pokeNames[i] = nameList.get(i); + } + return pokeNames; + } + + @Override + protected void savingROM() { + savePokemonStats(); + saveMoves(); + try { + writeARM9(arm9); + } catch (IOException e) { + throw new RuntimeException(e); + } + try { + writeNARC(romEntry.getString("TextStrings"), stringsNarc); + writeNARC(romEntry.getString("TextStory"), storyTextNarc); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try { + writeNARC(romEntry.getString("Scripts"), scriptNarc); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void saveMoves() { + for (int i = 1; i <= Gen5Constants.moveCount; i++) { + byte[] data = moveNarc.files.get(i); + data[2] = Gen5Constants.moveCategoryToByte(moves[i].category); + data[3] = (byte) moves[i].power; + data[0] = Gen5Constants.typeToByte(moves[i].type); + int hitratio = (int) Math.round(moves[i].hitratio); + if (hitratio < 0) { + hitratio = 0; + } + if (hitratio > 101) { + hitratio = 100; + } + data[4] = (byte) hitratio; + data[5] = (byte) moves[i].pp; + } + + try { + this.writeNARC(romEntry.getString("MoveData"), moveNarc); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + private void savePokemonStats() { + List<String> nameList = getStrings(false, romEntry.getInt("PokemonNamesTextOffset")); + + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + saveBasicPokeStats(pokes[i], pokeNarc.files.get(i)); + nameList.set(i, pokes[i].name); + } + + setStrings(false, romEntry.getInt("PokemonNamesTextOffset"), nameList); + + try { + this.writeNARC(romEntry.getString("PokemonStats"), pokeNarc); + } catch (IOException e) { + throw new RuntimeException(e); + } + + writeEvolutions(); + } + + private void saveBasicPokeStats(Pokemon pkmn, byte[] stats) { + stats[Gen5Constants.bsHPOffset] = (byte) pkmn.hp; + stats[Gen5Constants.bsAttackOffset] = (byte) pkmn.attack; + stats[Gen5Constants.bsDefenseOffset] = (byte) pkmn.defense; + stats[Gen5Constants.bsSpeedOffset] = (byte) pkmn.speed; + stats[Gen5Constants.bsSpAtkOffset] = (byte) pkmn.spatk; + stats[Gen5Constants.bsSpDefOffset] = (byte) pkmn.spdef; + stats[Gen5Constants.bsPrimaryTypeOffset] = Gen5Constants.typeToByte(pkmn.primaryType); + if (pkmn.secondaryType == null) { + stats[Gen5Constants.bsSecondaryTypeOffset] = stats[Gen5Constants.bsPrimaryTypeOffset]; + } else { + stats[Gen5Constants.bsSecondaryTypeOffset] = Gen5Constants.typeToByte(pkmn.secondaryType); + } + stats[Gen5Constants.bsCatchRateOffset] = (byte) pkmn.catchRate; + stats[Gen5Constants.bsGrowthCurveOffset] = pkmn.growthCurve.toByte(); + + stats[Gen5Constants.bsAbility1Offset] = (byte) pkmn.ability1; + stats[Gen5Constants.bsAbility2Offset] = (byte) pkmn.ability2; + stats[Gen5Constants.bsAbility3Offset] = (byte) pkmn.ability3; + + // Held items + if (pkmn.guaranteedHeldItem > 0) { + writeWord(stats, Gen5Constants.bsCommonHeldItemOffset, pkmn.guaranteedHeldItem); + writeWord(stats, Gen5Constants.bsRareHeldItemOffset, pkmn.guaranteedHeldItem); + writeWord(stats, Gen5Constants.bsDarkGrassHeldItemOffset, 0); + } else { + writeWord(stats, Gen5Constants.bsCommonHeldItemOffset, pkmn.commonHeldItem); + writeWord(stats, Gen5Constants.bsRareHeldItemOffset, pkmn.rareHeldItem); + writeWord(stats, Gen5Constants.bsDarkGrassHeldItemOffset, pkmn.darkGrassHeldItem); + } + } + + @Override + public List<Pokemon> getPokemon() { + return pokemonList; + } + + @Override + public List<Pokemon> getStarters() { + NARCContents scriptNARC = scriptNarc; + List<Pokemon> starters = new ArrayList<Pokemon>(); + for (int i = 0; i < 3; i++) { + OffsetWithinEntry[] thisStarter = romEntry.offsetArrayEntries.get("StarterOffsets" + (i + 1)); + starters.add(pokes[readWord(scriptNARC.files.get(thisStarter[0].entry), thisStarter[0].offset)]); + } + return starters; + } + + @Override + public boolean setStarters(List<Pokemon> newStarters) { + if (newStarters.size() != 3) { + return false; + } + + // Fix up starter offsets + try { + NARCContents scriptNARC = scriptNarc; + for (int i = 0; i < 3; i++) { + int starter = newStarters.get(i).number; + OffsetWithinEntry[] thisStarter = romEntry.offsetArrayEntries.get("StarterOffsets" + (i + 1)); + for (OffsetWithinEntry entry : thisStarter) { + writeWord(scriptNARC.files.get(entry.entry), entry.offset, starter); + } + } + // GIVE ME BACK MY PURRLOIN + if (romEntry.romType == Gen5Constants.Type_BW2) { + byte[] newScript = Gen5Constants.bw2NewStarterScript; + byte[] oldFile = scriptNARC.files.get(romEntry.getInt("PokedexGivenFileOffset")); + byte[] newFile = new byte[oldFile.length + newScript.length]; + int offset = find(oldFile, Gen5Constants.bw2StarterScriptMagic); + if (offset > 0) { + System.arraycopy(oldFile, 0, newFile, 0, oldFile.length); + System.arraycopy(newScript, 0, newFile, oldFile.length, newScript.length); + if (romEntry.romCode.charAt(3) == 'J') { + newFile[oldFile.length + 0x6] -= 4; + } + newFile[offset++] = 0x1E; + newFile[offset++] = 0x0; + writeRelativePointer(newFile, offset, oldFile.length); + scriptNARC.files.set(romEntry.getInt("PokedexGivenFileOffset"), newFile); + } + } else { + byte[] newScript = Gen5Constants.bw1NewStarterScript; + + byte[] oldFile = scriptNARC.files.get(romEntry.getInt("PokedexGivenFileOffset")); + byte[] newFile = new byte[oldFile.length + newScript.length]; + int offset = find(oldFile, Gen5Constants.bw1StarterScriptMagic); + if (offset > 0) { + System.arraycopy(oldFile, 0, newFile, 0, oldFile.length); + System.arraycopy(newScript, 0, newFile, oldFile.length, newScript.length); + if (romEntry.romCode.charAt(3) == 'J') { + newFile[oldFile.length + 0x4] -= 4; + newFile[oldFile.length + 0x8] -= 4; + } + newFile[offset++] = 0x04; + newFile[offset++] = 0x0; + writeRelativePointer(newFile, offset, oldFile.length); + scriptNARC.files.set(romEntry.getInt("PokedexGivenFileOffset"), newFile); + } + } + + // Starter sprites + NARCContents starterNARC = this.readNARC(romEntry.getString("StarterGraphics")); + NARCContents pokespritesNARC = this.readNARC(romEntry.getString("PokemonGraphics")); + replaceStarterFiles(starterNARC, pokespritesNARC, 0, newStarters.get(0).number); + replaceStarterFiles(starterNARC, pokespritesNARC, 1, newStarters.get(1).number); + replaceStarterFiles(starterNARC, pokespritesNARC, 2, newStarters.get(2).number); + writeNARC(romEntry.getString("StarterGraphics"), starterNARC); + } catch (IOException ex) { + throw new RuntimeException(ex); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + // Fix text depending on version + if (romEntry.romType == Gen5Constants.Type_BW) { + List<String> yourHouseStrings = getStrings(true, romEntry.getInt("StarterLocationTextOffset")); + for (int i = 0; i < 3; i++) { + yourHouseStrings.set(Gen5Constants.bw1StarterTextOffset - i, + "\\xF000\\xBD02\\x0000The " + newStarters.get(i).primaryType.camelCase() + + "-type Pok\\x00E9mon\\xFFFE\\xF000\\xBD02\\x0000" + newStarters.get(i).name); + } + // Update what the friends say + yourHouseStrings + .set(Gen5Constants.bw1CherenText1Offset, + "Cheren: Hey, how come you get to pick\\xFFFEout my Pok\\x00E9mon?" + + "\\xF000\\xBE01\\x0000\\xFFFEOh, never mind. I wanted this one\\xFFFEfrom the start, anyway." + + "\\xF000\\xBE01\\x0000"); + yourHouseStrings.set(Gen5Constants.bw1CherenText2Offset, + "It's decided. You'll be my opponent...\\xFFFEin our first Pok\\x00E9mon battle!" + + "\\xF000\\xBE01\\x0000\\xFFFELet's see what you can do, \\xFFFEmy Pok\\x00E9mon!" + + "\\xF000\\xBE01\\x0000"); + + // rewrite + setStrings(true, romEntry.getInt("StarterLocationTextOffset"), yourHouseStrings); + } else { + List<String> starterTownStrings = getStrings(true, romEntry.getInt("StarterLocationTextOffset")); + for (int i = 0; i < 3; i++) { + starterTownStrings.set(Gen5Constants.bw2StarterTextOffset - i, "\\xF000\\xBD02\\x0000The " + + newStarters.get(i).primaryType.camelCase() + + "-type Pok\\x00E9mon\\xFFFE\\xF000\\xBD02\\x0000" + newStarters.get(i).name); + } + // Update what the rival says + starterTownStrings.set(Gen5Constants.bw2RivalTextOffset, + "\\xF000\\x0100\\x0001\\x0001: Let's see how good\\xFFFEa Trainer you are!" + + "\\xF000\\xBE01\\x0000\\xFFFEI'll use my Pok\\x00E9mon" + + "\\xFFFEthat I raised from an Egg!\\xF000\\xBE01\\x0000"); + + // rewrite + setStrings(true, romEntry.getInt("StarterLocationTextOffset"), starterTownStrings); + } + return true; + } + + @Override + public List<Integer> getStarterHeldItems() { + // do nothing + return new ArrayList<Integer>(); + } + + @Override + public void setStarterHeldItems(List<Integer> items) { + // do nothing + } + + private void replaceStarterFiles(NARCContents starterNARC, NARCContents pokespritesNARC, int starterIndex, + int pokeNumber) throws IOException, InterruptedException { + starterNARC.files.set(starterIndex * 2, pokespritesNARC.files.get(pokeNumber * 20 + 18)); + // Get the picture... + byte[] compressedPic = pokespritesNARC.files.get(pokeNumber * 20); + // Decompress it with JavaDSDecmp + byte[] uncompressedPic = DSDecmp.Decompress(compressedPic); + starterNARC.files.set(12 + starterIndex, uncompressedPic); + } + + @Override + public void shufflePokemonStats() { + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + pokes[i].shuffleStats(this.random); + } + + } + + @Override + public List<Move> getMoves() { + return Arrays.asList(moves); + } + + @Override + public List<EncounterSet> getEncounters(boolean useTimeOfDay) { + if (!loadedWildMapNames) { + loadWildMapNames(); + } + try { + NARCContents encounterNARC = readNARC(romEntry.getString("WildPokemon")); + List<EncounterSet> encounters = new ArrayList<EncounterSet>(); + int idx = -1; + for (byte[] entry : encounterNARC.files) { + idx++; + if (entry.length > Gen5Constants.perSeasonEncounterDataLength && useTimeOfDay) { + for (int i = 0; i < 4; i++) { + processEncounterEntry(encounters, entry, i * Gen5Constants.perSeasonEncounterDataLength, idx); + } + } else { + processEncounterEntry(encounters, entry, 0, idx); + } + } + return encounters; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void processEncounterEntry(List<EncounterSet> encounters, byte[] entry, int startOffset, int idx) { + + if (!wildMapNames.containsKey(idx)) { + wildMapNames.put(idx, "? Unknown ?"); + } + String mapName = wildMapNames.get(idx); + + int[] amounts = Gen5Constants.encountersOfEachType; + + int offset = 8; + for (int i = 0; i < 7; i++) { + int rate = entry[startOffset + i] & 0xFF; + if (rate != 0) { + List<Encounter> encs = readEncounters(entry, startOffset + offset, amounts[i]); + EncounterSet area = new EncounterSet(); + area.rate = rate; + area.encounters = encs; + area.offset = idx; + area.displayName = mapName + " " + Gen5Constants.encounterTypeNames[i]; + encounters.add(area); + } + offset += amounts[i] * 4; + } + + } + + private List<Encounter> readEncounters(byte[] data, int offset, int number) { + List<Encounter> encs = new ArrayList<Encounter>(); + for (int i = 0; i < number; i++) { + Encounter enc1 = new Encounter(); + enc1.pokemon = pokes[((data[offset + i * 4] & 0xFF) + ((data[offset + 1 + i * 4] & 0x03) << 8))]; + enc1.level = data[offset + 2 + i * 4] & 0xFF; + enc1.maxLevel = data[offset + 3 + i * 4] & 0xFF; + encs.add(enc1); + } + return encs; + } + + @Override + public void setEncounters(boolean useTimeOfDay, List<EncounterSet> encountersList) { + try { + NARCContents encounterNARC = readNARC(romEntry.getString("WildPokemon")); + Iterator<EncounterSet> encounters = encountersList.iterator(); + for (byte[] entry : encounterNARC.files) { + writeEncounterEntry(encounters, entry, 0); + if (entry.length > 232) { + if (useTimeOfDay) { + for (int i = 1; i < 4; i++) { + writeEncounterEntry(encounters, entry, i * 232); + } + } else { + // copy for other 3 seasons + System.arraycopy(entry, 0, entry, 232, 232); + System.arraycopy(entry, 0, entry, 464, 232); + System.arraycopy(entry, 0, entry, 696, 232); + } + } + } + + // Save + writeNARC(romEntry.getString("WildPokemon"), encounterNARC); + + // Habitat List / Area Data? + if (romEntry.romType == Gen5Constants.Type_BW2) { + // disabled: habitat list changes cause a crash if too many + // entries for now. + + // NARCContents habitatNARC = readNARC(romEntry + // .getString("HabitatList")); + // for (int i = 0; i < habitatNARC.files.size(); i++) { + // byte[] oldEntry = habitatNARC.files.get(i); + // int[] encounterFiles = habitatListEntries[i]; + // Map<Pokemon, byte[]> pokemonHere = new TreeMap<Pokemon, + // byte[]>(); + // for (int encFile : encounterFiles) { + // byte[] encEntry = encounterNARC.files.get(encFile); + // if (encEntry.length > 232) { + // for (int s = 0; s < 4; s++) { + // addHabitats(encEntry, s * 232, pokemonHere, s); + // } + // } else { + // for (int s = 0; s < 4; s++) { + // addHabitats(encEntry, 0, pokemonHere, s); + // } + // } + // } + // // Make the new file + // byte[] habitatEntry = new byte[10 + pokemonHere.size() * 28]; + // System.arraycopy(oldEntry, 0, habitatEntry, 0, 10); + // habitatEntry[8] = (byte) pokemonHere.size(); + // // 28-byte entries for each pokemon + // int num = -1; + // for (Pokemon pkmn : pokemonHere.keySet()) { + // num++; + // writeWord(habitatEntry, 10 + num * 28, pkmn.number); + // byte[] slots = pokemonHere.get(pkmn); + // System.arraycopy(slots, 0, habitatEntry, 12 + num * 28, + // 12); + // } + // // Save + // habitatNARC.files.set(i, habitatEntry); + // } + // // Save habitat + // this.writeNARC(romEntry.getString("HabitatList"), + // habitatNARC); + + // Area Data + NARCContents areaNARC = this.readNARC(romEntry.getString("PokemonAreaData")); + List<byte[]> newFiles = new ArrayList<byte[]>(); + for (int i = 0; i < Gen5Constants.pokemonCount; i++) { + byte[] nf = new byte[Gen5Constants.bw2AreaDataEntryLength]; + nf[0] = 1; + newFiles.add(nf); + } + // Get data now + for (int i = 0; i < encounterNARC.files.size(); i++) { + byte[] encEntry = encounterNARC.files.get(i); + if (encEntry.length > Gen5Constants.perSeasonEncounterDataLength) { + for (int s = 0; s < 4; s++) { + parseAreaData(encEntry, s * Gen5Constants.perSeasonEncounterDataLength, newFiles, s, i); + } + } else { + for (int s = 0; s < 4; s++) { + parseAreaData(encEntry, 0, newFiles, s, i); + } + } + } + // Now update unobtainables & save + for (int i = 0; i < Gen5Constants.pokemonCount; i++) { + byte[] file = newFiles.get(i); + for (int s = 0; s < 4; s++) { + boolean unobtainable = true; + for (int e = 0; e < Gen5Constants.bw2EncounterAreaCount; e++) { + if (file[s * (Gen5Constants.bw2EncounterAreaCount + 1) + e + 2] != 0) { + unobtainable = false; + break; + } + } + if (unobtainable) { + file[s * (Gen5Constants.bw2EncounterAreaCount + 1) + 1] = 1; + } + } + areaNARC.files.set(i, file); + } + // Save + this.writeNARC(romEntry.getString("PokemonAreaData"), areaNARC); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + private void parseAreaData(byte[] entry, int startOffset, List<byte[]> areaData, int season, int fileNumber) { + int[] amounts = Gen5Constants.encountersOfEachType; + + int offset = 8; + for (int i = 0; i < 7; i++) { + int rate = entry[startOffset + i] & 0xFF; + if (rate != 0) { + for (int e = 0; e < amounts[i]; e++) { + Pokemon pkmn = pokes[((entry[startOffset + offset + e * 4] & 0xFF) + ((entry[startOffset + offset + + 1 + e * 4] & 0x03) << 8))]; + byte[] pokeFile = areaData.get(pkmn.number - 1); + int areaIndex = Gen5Constants.wildFileToAreaMap[fileNumber]; + // Route 4? + if (areaIndex == Gen5Constants.bw2Route4AreaIndex) { + if ((fileNumber == Gen5Constants.b2Route4EncounterFile && romEntry.romCode.charAt(2) == 'D') + || (fileNumber == Gen5Constants.w2Route4EncounterFile && romEntry.romCode.charAt(2) == 'E')) { + areaIndex = -1; // wrong version + } + } + // Victory Road? + if (areaIndex == Gen5Constants.bw2VictoryRoadAreaIndex) { + if (romEntry.romCode.charAt(2) == 'D') { + // White 2 + if (fileNumber == Gen5Constants.b2VRExclusiveRoom1 + || fileNumber == Gen5Constants.b2VRExclusiveRoom2) { + areaIndex = -1; // wrong version + } + } else { + // Black 2 + if (fileNumber == Gen5Constants.w2VRExclusiveRoom1 + || fileNumber == Gen5Constants.w2VRExclusiveRoom2) { + areaIndex = -1; // wrong version + } + } + } + // Reversal Mountain? + if (areaIndex == Gen5Constants.bw2ReversalMountainAreaIndex) { + if (romEntry.romCode.charAt(2) == 'D') { + // White 2 + if (fileNumber >= Gen5Constants.b2ReversalMountainStart + && fileNumber <= Gen5Constants.b2ReversalMountainEnd) { + areaIndex = -1; // wrong version + } + } else { + // Black 2 + if (fileNumber >= Gen5Constants.w2ReversalMountainStart + && fileNumber <= Gen5Constants.w2ReversalMountainEnd) { + areaIndex = -1; // wrong version + } + } + } + // Skip stuff that isn't on the map or is wrong version + if (areaIndex != -1) { + pokeFile[season * (Gen5Constants.bw2EncounterAreaCount + 1) + 2 + areaIndex] |= (1 << i); + } + } + } + offset += amounts[i] * 4; + } + } + + @SuppressWarnings("unused") + private void addHabitats(byte[] entry, int startOffset, Map<Pokemon, byte[]> pokemonHere, int season) { + int[] amounts = Gen5Constants.encountersOfEachType; + int[] type = Gen5Constants.habitatClassificationOfEachType; + + int offset = 8; + for (int i = 0; i < 7; i++) { + int rate = entry[startOffset + i] & 0xFF; + if (rate != 0) { + for (int e = 0; e < amounts[i]; e++) { + Pokemon pkmn = pokes[((entry[startOffset + offset + e * 4] & 0xFF) + ((entry[startOffset + offset + + 1 + e * 4] & 0x03) << 8))]; + if (pokemonHere.containsKey(pkmn)) { + pokemonHere.get(pkmn)[type[i] + season * 3] = 1; + } else { + byte[] locs = new byte[12]; + locs[type[i] + season * 3] = 1; + pokemonHere.put(pkmn, locs); + } + } + } + offset += amounts[i] * 4; + } + } + + private void writeEncounterEntry(Iterator<EncounterSet> encounters, byte[] entry, int startOffset) { + int[] amounts = Gen5Constants.encountersOfEachType; + + int offset = 8; + for (int i = 0; i < 7; i++) { + int rate = entry[startOffset + i] & 0xFF; + if (rate != 0) { + EncounterSet area = encounters.next(); + for (int j = 0; j < amounts[i]; j++) { + Encounter enc = area.encounters.get(j); + writeWord(entry, startOffset + offset + j * 4, enc.pokemon.number); + entry[startOffset + offset + j * 4 + 2] = (byte) enc.level; + entry[startOffset + offset + j * 4 + 3] = (byte) enc.maxLevel; + } + } + offset += amounts[i] * 4; + } + } + + private void loadWildMapNames() { + try { + wildMapNames = new HashMap<Integer, String>(); + byte[] mapHeaderData = this.readNARC(romEntry.getString("MapTableFile")).files.get(0); + int numMapHeaders = mapHeaderData.length / 48; + List<String> allMapNames = getStrings(false, romEntry.getInt("MapNamesTextOffset")); + for (int map = 0; map < numMapHeaders; map++) { + int baseOffset = map * 48; + int mapNameIndex = mapHeaderData[baseOffset + 26] & 0xFF; + String mapName = allMapNames.get(mapNameIndex); + if (romEntry.romType == Gen5Constants.Type_BW2) { + int wildSet = mapHeaderData[baseOffset + 20] & 0xFF; + if (wildSet != 255) { + wildMapNames.put(wildSet, mapName); + } + } else { + int wildSet = readWord(mapHeaderData, baseOffset + 20); + if (wildSet != 65535) { + wildMapNames.put(wildSet, mapName); + } + } + } + loadedWildMapNames = true; + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + @Override + public List<Trainer> getTrainers() { + List<Trainer> allTrainers = new ArrayList<Trainer>(); + try { + NARCContents trainers = this.readNARC(romEntry.getString("TrainerData")); + NARCContents trpokes = this.readNARC(romEntry.getString("TrainerPokemon")); + int trainernum = trainers.files.size(); + List<String> tclasses = this.getTrainerClassNames(); + List<String> tnames = this.getTrainerNames(); + for (int i = 1; i < trainernum; i++) { + byte[] trainer = trainers.files.get(i); + byte[] trpoke = trpokes.files.get(i); + Trainer tr = new Trainer(); + tr.poketype = trainer[0] & 0xFF; + tr.offset = i; + tr.trainerclass = trainer[1] & 0xFF; + int numPokes = trainer[3] & 0xFF; + int pokeOffs = 0; + tr.fullDisplayName = tclasses.get(tr.trainerclass) + " " + tnames.get(i - 1); + // printBA(trpoke); + for (int poke = 0; poke < numPokes; poke++) { + // Structure is + // AI 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 ailevel = trpoke[pokeOffs] & 0xFF; + // int secondbyte = trpoke[pokeOffs + 1] & 0xFF; + int level = readWord(trpoke, pokeOffs + 2); + int species = readWord(trpoke, pokeOffs + 4); + // int formnum = readWord(trpoke, pokeOffs + 6); + TrainerPokemon tpk = new TrainerPokemon(); + tpk.level = level; + tpk.pokemon = pokes[species]; + tpk.AILevel = ailevel; + tpk.ability = trpoke[pokeOffs + 1] & 0xFF; + pokeOffs += 8; + if (tr.poketype >= 2) { + int heldItem = readWord(trpoke, pokeOffs); + tpk.heldItem = heldItem; + pokeOffs += 2; + } + if (tr.poketype % 2 == 1) { + int attack1 = readWord(trpoke, pokeOffs); + int attack2 = readWord(trpoke, pokeOffs + 2); + int attack3 = readWord(trpoke, pokeOffs + 4); + int attack4 = readWord(trpoke, pokeOffs + 6); + tpk.move1 = attack1; + tpk.move2 = attack2; + tpk.move3 = attack3; + tpk.move4 = attack4; + pokeOffs += 8; + } + tr.pokemon.add(tpk); + } + allTrainers.add(tr); + } + if (romEntry.romType == Gen5Constants.Type_BW) { + Gen5Constants.tagTrainersBW(allTrainers); + } else { + if (!romEntry.getString("DriftveilPokemon").isEmpty()) { + NARCContents driftveil = this.readNARC(romEntry.getString("DriftveilPokemon")); + for (int trno = 0; trno < 2; trno++) { + Trainer tr = new Trainer(); + tr.poketype = 3; + tr.offset = 0; + for (int poke = 0; poke < 3; poke++) { + byte[] pkmndata = driftveil.files.get(trno * 3 + poke + 1); + TrainerPokemon tpk = new TrainerPokemon(); + tpk.level = 25; + tpk.pokemon = pokes[readWord(pkmndata, 0)]; + tpk.AILevel = 255; + tpk.heldItem = readWord(pkmndata, 12); + tpk.move1 = readWord(pkmndata, 2); + tpk.move2 = readWord(pkmndata, 2); + tpk.move3 = readWord(pkmndata, 2); + tpk.move4 = readWord(pkmndata, 2); + tr.pokemon.add(tpk); + } + allTrainers.add(tr); + } + } + Gen5Constants.tagTrainersBW2(allTrainers); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + return allTrainers; + } + + @Override + public void setTrainers(List<Trainer> trainerData) { + Iterator<Trainer> allTrainers = trainerData.iterator(); + try { + NARCContents trainers = this.readNARC(romEntry.getString("TrainerData")); + NARCContents trpokes = new NARCContents(); + // empty entry + trpokes.files.add(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }); + int trainernum = trainers.files.size(); + for (int i = 1; i < trainernum; i++) { + byte[] trainer = trainers.files.get(i); + Trainer tr = allTrainers.next(); + tr.poketype = 0; // write as type 0 for no item/moves + trainer[0] = (byte) tr.poketype; + int numPokes = tr.pokemon.size(); + trainer[3] = (byte) numPokes; + + int bytesNeeded = 8 * numPokes; + if (tr.poketype % 2 == 1) { + bytesNeeded += 8 * numPokes; + } + if (tr.poketype >= 2) { + bytesNeeded += 2 * numPokes; + } + byte[] trpoke = new byte[bytesNeeded]; + int pokeOffs = 0; + Iterator<TrainerPokemon> tpokes = tr.pokemon.iterator(); + for (int poke = 0; poke < numPokes; poke++) { + TrainerPokemon tpk = tpokes.next(); + trpoke[pokeOffs] = (byte) tpk.AILevel; + // no gender or ability info, so no byte 1 + writeWord(trpoke, pokeOffs + 2, tpk.level); + writeWord(trpoke, pokeOffs + 4, tpk.pokemon.number); + // no form info, so no byte 6/7 + pokeOffs += 8; + if (tr.poketype >= 2) { + writeWord(trpoke, pokeOffs, tpk.heldItem); + pokeOffs += 2; + } + if (tr.poketype % 2 == 1) { + writeWord(trpoke, pokeOffs, tpk.move1); + writeWord(trpoke, pokeOffs + 2, tpk.move2); + writeWord(trpoke, pokeOffs + 4, tpk.move3); + writeWord(trpoke, pokeOffs + 6, tpk.move4); + pokeOffs += 8; + } + } + trpokes.files.add(trpoke); + } + this.writeNARC(romEntry.getString("TrainerData"), trainers); + this.writeNARC(romEntry.getString("TrainerPokemon"), trpokes); + // Deal with PWT + if (romEntry.romType == Gen5Constants.Type_BW2 && !romEntry.getString("DriftveilPokemon").isEmpty()) { + NARCContents driftveil = this.readNARC(romEntry.getString("DriftveilPokemon")); + Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); + for (int trno = 0; trno < 2; trno++) { + Trainer tr = allTrainers.next(); + Iterator<TrainerPokemon> tpks = tr.pokemon.iterator(); + for (int poke = 0; poke < 3; poke++) { + byte[] pkmndata = driftveil.files.get(trno * 3 + poke + 1); + TrainerPokemon tpk = tpks.next(); + // pokemon and held item + writeWord(pkmndata, 0, tpk.pokemon.number); + writeWord(pkmndata, 12, tpk.heldItem); + // pick 4 moves, based on moveset@25 + int[] moves = new int[4]; + int moveCount = 0; + List<MoveLearnt> set = movesets.get(tpk.pokemon); + for (int i = 0; i < set.size(); i++) { + MoveLearnt ml = set.get(i); + if (ml.level > 25) { + break; + } + // unconditional learn? + if (moveCount < 4) { + moves[moveCount++] = ml.move; + } else { + // already knows? + boolean doTeach = true; + for (int j = 0; j < 4; j++) { + if (moves[j] == ml.move) { + doTeach = false; + break; + } + } + if (doTeach) { + // shift up + for (int j = 0; j < 3; j++) { + moves[j] = moves[j + 1]; + } + moves[3] = ml.move; + } + } + } + // write the moveset we calculated + for (int i = 0; i < 4; i++) { + writeWord(pkmndata, 2 + i * 2, moves[i]); + } + } + } + this.writeNARC(romEntry.getString("DriftveilPokemon"), driftveil); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + @Override + public Map<Pokemon, List<MoveLearnt>> getMovesLearnt() { + Map<Pokemon, List<MoveLearnt>> movesets = new TreeMap<Pokemon, List<MoveLearnt>>(); + try { + NARCContents movesLearnt = this.readNARC(romEntry.getString("PokemonMovesets")); + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + Pokemon pkmn = pokes[i]; + byte[] movedata = movesLearnt.files.get(i); + int moveDataLoc = 0; + List<MoveLearnt> learnt = new ArrayList<MoveLearnt>(); + 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, learnt); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return movesets; + } + + @Override + public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets) { + try { + NARCContents movesLearnt = readNARC(romEntry.getString("PokemonMovesets")); + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + Pokemon pkmn = pokes[i]; + List<MoveLearnt> learnt = movesets.get(pkmn); + 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.files.set(i, moveset); + } + // Save + this.writeNARC(romEntry.getString("PokemonMovesets"), movesLearnt); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + private static class StaticPokemon { + private int[] files; + private int[] offsets; + + public Pokemon getPokemon(Gen5RomHandler parent, NARCContents scriptNARC) { + return parent.pokes[parent.readWord(scriptNARC.files.get(files[0]), offsets[0])]; + } + + public void setPokemon(Gen5RomHandler parent, NARCContents scriptNARC, Pokemon pkmn) { + int value = pkmn.number; + for (int i = 0; i < offsets.length; i++) { + byte[] file = scriptNARC.files.get(files[i]); + parent.writeWord(file, offsets[i], value); + } + } + } + + @Override + public boolean canChangeStaticPokemon() { + return romEntry.staticPokemonSupport; + } + + @Override + public List<Pokemon> getStaticPokemon() { + List<Pokemon> sp = new ArrayList<Pokemon>(); + if (!romEntry.staticPokemonSupport) { + return sp; + } + NARCContents scriptNARC = scriptNarc; + for (StaticPokemon statP : romEntry.staticPokemon) { + sp.add(statP.getPokemon(this, scriptNARC)); + } + return sp; + } + + @Override + public boolean setStaticPokemon(List<Pokemon> staticPokemon) { + if (!romEntry.staticPokemonSupport) { + return false; + } + if (staticPokemon.size() != romEntry.staticPokemon.size()) { + return false; + } + Iterator<Pokemon> statics = staticPokemon.iterator(); + NARCContents scriptNARC = scriptNarc; + for (StaticPokemon statP : romEntry.staticPokemon) { + statP.setPokemon(this, scriptNARC, statics.next()); + } + if (romEntry.offsetArrayEntries.containsKey("StaticPokemonFormValues")) { + OffsetWithinEntry[] formValues = romEntry.offsetArrayEntries.get("StaticPokemonFormValues"); + for (OffsetWithinEntry owe : formValues) { + writeWord(scriptNARC.files.get(owe.entry), owe.offset, 0); + } + } + + return true; + } + + @Override + public int miscTweaksAvailable() { + int available = 0; + if (romEntry.romType == Gen5Constants.Type_BW2) { + available |= MiscTweak.RANDOMIZE_HIDDEN_HOLLOWS.getValue(); + } + if (romEntry.tweakFiles.get("FastestTextTweak") != null) { + available |= MiscTweak.FASTEST_TEXT.getValue(); + } + return available; + } + + @Override + public void applyMiscTweak(MiscTweak tweak) { + if (tweak == MiscTweak.RANDOMIZE_HIDDEN_HOLLOWS) { + randomizeHiddenHollowPokemon(); + } else if (tweak == MiscTweak.FASTEST_TEXT) { + applyFastestText(); + } + } + + private void randomizeHiddenHollowPokemon() { + if (romEntry.romType != Gen5Constants.Type_BW2) { + return; + } + int[] allowedUnovaPokemon = Gen5Constants.bw2HiddenHollowUnovaPokemon; + int randomSize = Gen5Constants.nonUnovaPokemonCount + allowedUnovaPokemon.length; + try { + NARCContents hhNARC = this.readNARC(romEntry.getString("HiddenHollows")); + for (byte[] hhEntry : hhNARC.files) { + for (int version = 0; version < 2; version++) { + for (int rarityslot = 0; rarityslot < 3; rarityslot++) { + for (int group = 0; group < 4; group++) { + int pokeChoice = this.random.nextInt(randomSize) + 1; + if (pokeChoice > Gen5Constants.nonUnovaPokemonCount) { + pokeChoice = allowedUnovaPokemon[pokeChoice - (Gen5Constants.nonUnovaPokemonCount + 1)]; + } + writeWord(hhEntry, version * 78 + rarityslot * 26 + group * 2, pokeChoice); + int genderRatio = this.random.nextInt(101); + hhEntry[version * 78 + rarityslot * 26 + 16 + group] = (byte) genderRatio; + hhEntry[version * 78 + rarityslot * 26 + 20 + group] = 0; // forme + } + } + } + // the rest of the file is items + } + this.writeNARC(romEntry.getString("HiddenHollows"), hhNARC); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void applyFastestText() { + genericIPSPatch(arm9, "FastestTextTweak"); + } + + private boolean genericIPSPatch(byte[] data, String ctName) { + String patchName = romEntry.tweakFiles.get(ctName); + if (patchName == null) { + return false; + } + + try { + FileFunctions.applyPatch(data, patchName); + return true; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List<Integer> getTMMoves() { + String tmDataPrefix = Gen5Constants.tmDataPrefix; + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += Gen5Constants.tmDataPrefix.length() / 2; // because it was + // a prefix + List<Integer> tms = new ArrayList<Integer>(); + for (int i = 0; i < Gen5Constants.tmBlockOneCount; i++) { + tms.add(readWord(arm9, offset + i * 2)); + } + // Skip past first 92 TMs and 6 HMs + offset += (Gen5Constants.tmBlockOneCount + Gen5Constants.hmCount) * 2; + for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { + tms.add(readWord(arm9, offset + i * 2)); + } + return tms; + } else { + return null; + } + } + + @Override + public List<Integer> getHMMoves() { + String tmDataPrefix = Gen5Constants.tmDataPrefix; + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += Gen5Constants.tmDataPrefix.length() / 2; // because it was + // a prefix + offset += Gen5Constants.tmBlockOneCount * 2; // TM data + List<Integer> hms = new ArrayList<Integer>(); + for (int i = 0; i < Gen5Constants.hmCount; i++) { + hms.add(readWord(arm9, offset + i * 2)); + } + return hms; + } else { + return null; + } + } + + @Override + public void setTMMoves(List<Integer> moveIndexes) { + String tmDataPrefix = Gen5Constants.tmDataPrefix; + int offset = find(arm9, tmDataPrefix); + if (offset > 0) { + offset += Gen5Constants.tmDataPrefix.length() / 2; // because it was + // a prefix + for (int i = 0; i < Gen5Constants.tmBlockOneCount; i++) { + writeWord(arm9, offset + i * 2, moveIndexes.get(i)); + } + // Skip past those 92 TMs and 6 HMs + offset += (Gen5Constants.tmBlockOneCount + Gen5Constants.hmCount) * 2; + for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { + writeWord(arm9, offset + i * 2, moveIndexes.get(i + Gen5Constants.tmBlockOneCount)); + } + + // Update TM item descriptions + List<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 < Gen5Constants.tmBlockOneCount; i++) { + itemDescriptions.set(i + Gen5Constants.tmBlockOneOffset, moveDescriptions.get(moveIndexes.get(i))); + } + // TM93-95 are 618-620 + for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { + itemDescriptions.set(i + Gen5Constants.tmBlockTwoOffset, + moveDescriptions.get(moveIndexes.get(i + Gen5Constants.tmBlockOneCount))); + } + // Save the new item descriptions + setStrings(false, romEntry.getInt("ItemDescriptionsTextOffset"), itemDescriptions); + // Palettes + String baseOfPalettes; + if (romEntry.romType == Gen5Constants.Type_BW) { + baseOfPalettes = Gen5Constants.bw1ItemPalettesPrefix; + } else { + baseOfPalettes = Gen5Constants.bw2ItemPalettesPrefix; + } + int offsPals = find(arm9, baseOfPalettes); + if (offsPals > 0) { + // Write pals + for (int i = 0; i < Gen5Constants.tmBlockOneCount; i++) { + int itmNum = Gen5Constants.tmBlockOneOffset + i; + Move m = this.moves[moveIndexes.get(i)]; + int pal = this.typeTMPaletteNumber(m.type); + writeWord(arm9, offsPals + itmNum * 4 + 2, pal); + } + for (int i = 0; i < (Gen5Constants.tmCount - Gen5Constants.tmBlockOneCount); i++) { + int itmNum = Gen5Constants.tmBlockTwoOffset + i; + Move m = this.moves[moveIndexes.get(i + Gen5Constants.tmBlockOneCount)]; + int pal = this.typeTMPaletteNumber(m.type); + writeWord(arm9, offsPals + itmNum * 4 + 2, pal); + } + } + } else { + } + } + + private static RomFunctions.StringSizeDeterminer ssd = new RomFunctions.StringSizeDeterminer() { + + @Override + public int lengthFor(String encodedText) { + int offs = 0; + int len = encodedText.length(); + while (encodedText.indexOf("\\x", offs) != -1) { + len -= 5; + offs = encodedText.indexOf("\\x", offs) + 1; + } + return len; + } + }; + + @Override + public int getTMCount() { + return Gen5Constants.tmCount; + } + + @Override + public int getHMCount() { + return Gen5Constants.hmCount; + } + + @Override + public Map<Pokemon, boolean[]> getTMHMCompatibility() { + Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + byte[] data = pokeNarc.files.get(i); + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen5Constants.tmCount + Gen5Constants.hmCount + 1]; + for (int j = 0; j < 13; j++) { + readByteIntoFlags(data, flags, j * 8 + 1, Gen5Constants.bsTMHMCompatOffset + j); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData) { + for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + byte[] data = pokeNarc.files.get(pkmn.number); + for (int j = 0; j < 13; j++) { + data[Gen5Constants.bsTMHMCompatOffset + j] = getByteFromFlags(flags, j * 8 + 1); + } + } + } + + @Override + public boolean hasMoveTutors() { + return romEntry.romType == Gen5Constants.Type_BW2; + } + + @Override + public List<Integer> getMoveTutorMoves() { + if (!hasMoveTutors()) { + return new ArrayList<Integer>(); + } + int baseOffset = romEntry.getInt("MoveTutorDataOffset"); + int amount = Gen5Constants.bw2MoveTutorCount; + int bytesPer = Gen5Constants.bw2MoveTutorBytesPerEntry; + List<Integer> mtMoves = new ArrayList<Integer>(); + try { + byte[] mtFile = readOverlay(romEntry.getInt("MoveTutorOvlNumber")); + for (int i = 0; i < amount; i++) { + mtMoves.add(readWord(mtFile, baseOffset + i * bytesPer)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return mtMoves; + } + + @Override + public void setMoveTutorMoves(List<Integer> moves) { + if (!hasMoveTutors()) { + return; + } + int baseOffset = romEntry.getInt("MoveTutorDataOffset"); + int amount = Gen5Constants.bw2MoveTutorCount; + int bytesPer = Gen5Constants.bw2MoveTutorBytesPerEntry; + if (moves.size() != amount) { + return; + } + try { + byte[] mtFile = readOverlay(romEntry.getInt("MoveTutorOvlNumber")); + for (int i = 0; i < amount; i++) { + writeWord(mtFile, baseOffset + i * bytesPer, moves.get(i)); + } + writeOverlay(romEntry.getInt("MoveTutorOvlNumber"), mtFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public Map<Pokemon, boolean[]> getMoveTutorCompatibility() { + if (!hasMoveTutors()) { + return new TreeMap<Pokemon, boolean[]>(); + } + Map<Pokemon, boolean[]> compat = new TreeMap<Pokemon, boolean[]>(); + int[] countsPersonalOrder = new int[] { 15, 17, 13, 15 }; + int[] countsMoveOrder = new int[] { 13, 15, 15, 17 }; + int[] personalToMoveOrder = new int[] { 1, 3, 0, 2 }; + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + byte[] data = pokeNarc.files.get(i); + Pokemon pkmn = pokes[i]; + boolean[] flags = new boolean[Gen5Constants.bw2MoveTutorCount + 1]; + for (int mt = 0; mt < 4; mt++) { + boolean[] mtflags = new boolean[countsPersonalOrder[mt] + 1]; + for (int j = 0; j < 4; j++) { + readByteIntoFlags(data, mtflags, j * 8 + 1, Gen5Constants.bsMTCompatOffset + mt * 4 + j); + } + int offsetOfThisData = 0; + for (int cmoIndex = 0; cmoIndex < personalToMoveOrder[mt]; cmoIndex++) { + offsetOfThisData += countsMoveOrder[cmoIndex]; + } + System.arraycopy(mtflags, 1, flags, offsetOfThisData + 1, countsPersonalOrder[mt]); + } + compat.put(pkmn, flags); + } + return compat; + } + + @Override + public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData) { + if (!hasMoveTutors()) { + return; + } + // BW2 move tutor flags aren't using the same order as the move tutor + // move data. + // We unscramble them from move data order to personal.narc flag order. + int[] countsPersonalOrder = new int[] { 15, 17, 13, 15 }; + int[] countsMoveOrder = new int[] { 13, 15, 15, 17 }; + int[] personalToMoveOrder = new int[] { 1, 3, 0, 2 }; + for (Map.Entry<Pokemon, boolean[]> compatEntry : compatData.entrySet()) { + Pokemon pkmn = compatEntry.getKey(); + boolean[] flags = compatEntry.getValue(); + byte[] data = pokeNarc.files.get(pkmn.number); + for (int mt = 0; mt < 4; mt++) { + int offsetOfThisData = 0; + for (int cmoIndex = 0; cmoIndex < personalToMoveOrder[mt]; cmoIndex++) { + offsetOfThisData += countsMoveOrder[cmoIndex]; + } + boolean[] mtflags = new boolean[countsPersonalOrder[mt] + 1]; + System.arraycopy(flags, offsetOfThisData + 1, mtflags, 1, countsPersonalOrder[mt]); + for (int j = 0; j < 4; j++) { + data[Gen5Constants.bsMTCompatOffset + mt * 4 + j] = getByteFromFlags(mtflags, j * 8 + 1); + } + } + } + } + + private int find(byte[] data, String hexString) { + if (hexString.length() % 2 != 0) { + return -3; // error + } + byte[] searchFor = new byte[hexString.length() / 2]; + for (int i = 0; i < searchFor.length; i++) { + searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16); + } + List<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); + } + } + + private List<String> getStrings(boolean isStoryText, int index) { + NARCContents baseNARC = isStoryText ? storyTextNarc : stringsNarc; + byte[] rawFile = baseNARC.files.get(index); + return new ArrayList<String>(PPTxtHandler.readTexts(rawFile)); + } + + private void setStrings(boolean isStoryText, int index, List<String> strings) { + NARCContents baseNARC = isStoryText ? storyTextNarc : stringsNarc; + byte[] oldRawFile = baseNARC.files.get(index); + byte[] newRawFile = PPTxtHandler.saveEntry(oldRawFile, strings); + baseNARC.files.set(index, newRawFile); + } + + @Override + public String getROMName() { + return "Pokemon " + romEntry.name; + } + + @Override + public String getROMCode() { + return romEntry.romCode; + } + + @Override + public String getSupportLevel() { + return romEntry.staticPokemonSupport ? "Complete" : "No Static Pokemon"; + } + + @Override + public boolean hasTimeBasedEncounters() { + return true; // All BW/BW2 do [seasons] + } + + private void populateEvolutions() { + for (Pokemon pkmn : pokes) { + if (pkmn != null) { + pkmn.evolutionsFrom.clear(); + pkmn.evolutionsTo.clear(); + } + } + + // Read NARC + try { + NARCContents evoNARC = readNARC(romEntry.getString("PokemonEvolutions")); + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + Pokemon pk = pokes[i]; + byte[] evoEntry = evoNARC.files.get(i); + for (int evo = 0; evo < 7; evo++) { + int method = readWord(evoEntry, evo * 6); + int species = readWord(evoEntry, evo * 6 + 4); + if (method >= 1 && method <= Gen5Constants.evolutionMethodCount && species >= 1) { + EvolutionType et = EvolutionType.fromIndex(5, method); + int extraInfo = readWord(evoEntry, evo * 6 + 2); + Evolution evol = new Evolution(pk, pokes[species], true, et, extraInfo); + if (!pk.evolutionsFrom.contains(evol)) { + pk.evolutionsFrom.add(evol); + pokes[species].evolutionsTo.add(evol); + } + } + } + // split evos don't carry stats + if (pk.evolutionsFrom.size() > 1) { + for (Evolution e : pk.evolutionsFrom) { + e.carryStats = false; + } + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void writeEvolutions() { + try { + NARCContents evoNARC = readNARC(romEntry.getString("PokemonEvolutions")); + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + byte[] evoEntry = evoNARC.files.get(i); + Pokemon pk = pokes[i]; + int evosWritten = 0; + for (Evolution evo : pk.evolutionsFrom) { + writeWord(evoEntry, evosWritten * 6, evo.type.toIndex(5)); + writeWord(evoEntry, evosWritten * 6 + 2, evo.extraInfo); + writeWord(evoEntry, evosWritten * 6 + 4, evo.to.number); + evosWritten++; + if (evosWritten == 7) { + break; + } + } + while (evosWritten < 7) { + writeWord(evoEntry, evosWritten * 6, 0); + writeWord(evoEntry, evosWritten * 6 + 2, 0); + writeWord(evoEntry, evosWritten * 6 + 4, 0); + evosWritten++; + } + } + writeNARC(romEntry.getString("PokemonEvolutions"), evoNARC); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void removeTradeEvolutions(boolean changeMoveEvos) { + Map<Pokemon, List<MoveLearnt>> movesets = this.getMovesLearnt(); + log("--Removing Trade Evolutions--"); + Set<Evolution> extraEvolutions = new HashSet<Evolution>(); + 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)) { + 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; + logEvoChangeLevel(evo.from.name, evo.to.name, levelLearntAt); + } + // Pure Trade + if (evo.type == EvolutionType.TRADE) { + // Replace w/ level 37 + evo.type = EvolutionType.LEVEL; + evo.extraInfo = 37; + logEvoChangeLevel(evo.from.name, evo.to.name, 37); + } + // Trade w/ Item + if (evo.type == EvolutionType.TRADE_ITEM) { + // Get the current item & evolution + int item = evo.extraInfo; + if (evo.from.number == Gen5Constants.slowpokeIndex) { + // 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 = Gen5Constants.waterStoneIndex; // water + // stone + logEvoChangeStone(evo.from.name, evo.to.name, itemNames.get(Gen5Constants.waterStoneIndex)); + } else { + logEvoChangeLevelWithItem(evo.from.name, evo.to.name, itemNames.get(item)); + // Replace, for this entry, w/ + // Level up w/ Held Item at Day + evo.type = EvolutionType.LEVEL_ITEM_DAY; + // now add an extra evo for + // Level up w/ Held Item at Night + Evolution extraEntry = new Evolution(evo.from, evo.to, true, + EvolutionType.LEVEL_ITEM_NIGHT, item); + extraEvolutions.add(extraEntry); + } + } + if (evo.type == EvolutionType.TRADE_SPECIAL) { + // This is the karrablast <-> shelmet trade + // Replace it with Level up w/ Other Species in Party + // (22) + // Based on what species we're currently dealing with + evo.type = EvolutionType.LEVEL_WITH_OTHER; + evo.extraInfo = (evo.from.number == Gen5Constants.karrablastIndex ? Gen5Constants.shelmetIndex + : Gen5Constants.karrablastIndex); + logEvoChangeLevelWithPkmn(evo.from.name, evo.to.name, + pokes[(evo.from.number == Gen5Constants.karrablastIndex ? Gen5Constants.shelmetIndex + : Gen5Constants.karrablastIndex)].name); + } + } + + pkmn.evolutionsFrom.addAll(extraEvolutions); + for (Evolution ev : extraEvolutions) { + ev.to.evolutionsTo.add(ev); + } + } + } + logBlankLine(); + } + + @Override + public List<String> getTrainerNames() { + List<String> tnames = getStrings(false, romEntry.getInt("TrainerNamesTextOffset")); + tnames.remove(0); // blank one + // Tack the mugshot names on the end + List<String> mnames = getStrings(false, romEntry.getInt("TrainerMugshotsTextOffset")); + for (String mname : mnames) { + if (!mname.isEmpty() && (mname.charAt(0) >= 'A' && mname.charAt(0) <= 'Z')) { + tnames.add(mname); + } + } + return tnames; + } + + @Override + public int maxTrainerNameLength() { + return 10;// based off the english ROMs + } + + @Override + public void setTrainerNames(List<String> trainerNames) { + List<String> tnames = getStrings(false, romEntry.getInt("TrainerNamesTextOffset")); + // Grab the mugshot names off the back of the list of trainer names + // we got back + List<String> mnames = getStrings(false, romEntry.getInt("TrainerMugshotsTextOffset")); + int trNamesSize = trainerNames.size(); + for (int i = mnames.size() - 1; i >= 0; i--) { + String origMName = mnames.get(i); + if (!origMName.isEmpty() && (origMName.charAt(0) >= 'A' && origMName.charAt(0) <= 'Z')) { + // Grab replacement + String replacement = trainerNames.remove(--trNamesSize); + mnames.set(i, replacement); + } + } + // Save back mugshot names + setStrings(false, romEntry.getInt("TrainerMugshotsTextOffset"), mnames); + + // Now save the rest of trainer names + List<String> newTNames = new ArrayList<String>(trainerNames); + newTNames.add(0, tnames.get(0)); // the 0-entry, preserve it + setStrings(false, romEntry.getInt("TrainerNamesTextOffset"), newTNames); + + } + + @Override + public TrainerNameMode trainerNameMode() { + return TrainerNameMode.MAX_LENGTH; + } + + @Override + public List<Integer> getTCNameLengthsByTrainer() { + // not needed + return new ArrayList<Integer>(); + } + + @Override + public List<String> getTrainerClassNames() { + return getStrings(false, romEntry.getInt("TrainerClassesTextOffset")); + } + + @Override + public void setTrainerClassNames(List<String> trainerClassNames) { + setStrings(false, romEntry.getInt("TrainerClassesTextOffset"), trainerClassNames); + } + + @Override + public int maxTrainerClassNameLength() { + return 12;// based off the english ROMs + } + + @Override + public boolean fixedTrainerClassNamesLength() { + return false; + } + + @Override + public String getDefaultExtension() { + return "nds"; + } + + @Override + public int abilitiesPerPokemon() { + return 3; + } + + @Override + public int highestAbilityIndex() { + return Gen5Constants.highestAbilityIndex; + } + + @Override + public int internalStringLength(String string) { + return ssd.lengthFor(string); + } + + @Override + public void applySignature() { + // For now, do nothing. + + } + + @Override + public ItemList getAllowedItems() { + return Gen5Constants.allowedItems; + } + + @Override + public ItemList getNonBadItems() { + return Gen5Constants.nonBadItems; + } + + @Override + public String[] getItemNames() { + return itemNames.toArray(new String[0]); + } + + @Override + public String abilityName(int number) { + return abilityNames.get(number); + } + + private List<Integer> getFieldItems() { + List<Integer> fieldItems = new ArrayList<Integer>(); + // normal items + int scriptFileNormal = romEntry.getInt("ItemBallsScriptOffset"); + int scriptFileHidden = romEntry.getInt("HiddenItemsScriptOffset"); + int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); + int[] skipTableH = romEntry.arrayEntries.get("HiddenItemsSkip"); + int setVarNormal = Gen5Constants.normalItemSetVarCommand; + int setVarHidden = Gen5Constants.hiddenItemSetVarCommand; + + byte[] itemScripts = scriptNarc.files.get(scriptFileNormal); + int offset = 0; + int skipTableOffset = 0; + while (true) { + int part1 = readWord(itemScripts, offset); + if (part1 == Gen5Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(itemScripts, offset); + offset += 4; + if (offsetInFile > itemScripts.length) { + break; + } + if (skipTableOffset < skipTable.length && (skipTable[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(itemScripts, offsetInFile + 2); + int variable = readWord(itemScripts, offsetInFile + 4); + if (command == setVarNormal && variable == Gen5Constants.normalItemVarSet) { + int item = readWord(itemScripts, offsetInFile + 6); + fieldItems.add(item); + } + + } + + // hidden items + byte[] hitemScripts = scriptNarc.files.get(scriptFileHidden); + offset = 0; + skipTableOffset = 0; + while (true) { + int part1 = readWord(hitemScripts, offset); + if (part1 == Gen5Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(hitemScripts, offset); + if (offsetInFile > hitemScripts.length) { + break; + } + offset += 4; + if (skipTableOffset < skipTable.length && (skipTableH[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(hitemScripts, offsetInFile + 2); + int variable = readWord(hitemScripts, offsetInFile + 4); + if (command == setVarHidden && variable == Gen5Constants.hiddenItemVarSet) { + int item = readWord(hitemScripts, offsetInFile + 6); + fieldItems.add(item); + } + + } + + return fieldItems; + } + + private void setFieldItems(List<Integer> fieldItems) { + Iterator<Integer> iterItems = fieldItems.iterator(); + + // normal items + int scriptFileNormal = romEntry.getInt("ItemBallsScriptOffset"); + int scriptFileHidden = romEntry.getInt("HiddenItemsScriptOffset"); + int[] skipTable = romEntry.arrayEntries.get("ItemBallsSkip"); + int[] skipTableH = romEntry.arrayEntries.get("HiddenItemsSkip"); + int setVarNormal = Gen5Constants.normalItemSetVarCommand; + int setVarHidden = Gen5Constants.hiddenItemSetVarCommand; + + byte[] itemScripts = scriptNarc.files.get(scriptFileNormal); + int offset = 0; + int skipTableOffset = 0; + while (true) { + int part1 = readWord(itemScripts, offset); + if (part1 == Gen5Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(itemScripts, offset); + offset += 4; + if (offsetInFile > itemScripts.length) { + break; + } + if (skipTableOffset < skipTable.length && (skipTable[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(itemScripts, offsetInFile + 2); + int variable = readWord(itemScripts, offsetInFile + 4); + if (command == setVarNormal && variable == Gen5Constants.normalItemVarSet) { + int item = iterItems.next(); + writeWord(itemScripts, offsetInFile + 6, item); + } + + } + + // hidden items + byte[] hitemScripts = scriptNarc.files.get(scriptFileHidden); + offset = 0; + skipTableOffset = 0; + while (true) { + int part1 = readWord(hitemScripts, offset); + if (part1 == Gen5Constants.scriptListTerminator) { + // done + break; + } + int offsetInFile = readRelativePointer(hitemScripts, offset); + offset += 4; + if (offsetInFile > hitemScripts.length) { + break; + } + if (skipTableOffset < skipTable.length && (skipTableH[skipTableOffset] == (offset / 4) - 1)) { + skipTableOffset++; + continue; + } + int command = readWord(hitemScripts, offsetInFile + 2); + int variable = readWord(hitemScripts, offsetInFile + 4); + if (command == setVarHidden && variable == Gen5Constants.hiddenItemVarSet) { + int item = iterItems.next(); + writeWord(hitemScripts, offsetInFile + 6, item); + } + + } + } + + private int tmFromIndex(int index) { + if (index >= Gen5Constants.tmBlockOneOffset + && index < Gen5Constants.tmBlockOneOffset + Gen5Constants.tmBlockOneCount) { + return index - (Gen5Constants.tmBlockOneOffset - 1); + } else { + return (index + Gen5Constants.tmBlockOneCount) - (Gen5Constants.tmBlockTwoOffset - 1); + } + } + + private int indexFromTM(int tm) { + if (tm >= 1 && tm <= Gen5Constants.tmBlockOneCount) { + return tm + (Gen5Constants.tmBlockOneOffset - 1); + } else { + return tm + (Gen5Constants.tmBlockTwoOffset - 1 - Gen5Constants.tmBlockOneCount); + } + } + + @Override + public List<Integer> getCurrentFieldTMs() { + List<Integer> fieldItems = this.getFieldItems(); + List<Integer> fieldTMs = new ArrayList<Integer>(); + + for (int item : fieldItems) { + if (Gen5Constants.allowedItems.isTM(item)) { + fieldTMs.add(tmFromIndex(item)); + } + } + + return fieldTMs; + } + + @Override + public void setFieldTMs(List<Integer> fieldTMs) { + List<Integer> fieldItems = this.getFieldItems(); + int fiLength = fieldItems.size(); + Iterator<Integer> iterTMs = fieldTMs.iterator(); + + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (Gen5Constants.allowedItems.isTM(oldItem)) { + int newItem = indexFromTM(iterTMs.next()); + fieldItems.set(i, newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List<Integer> getRegularFieldItems() { + List<Integer> fieldItems = this.getFieldItems(); + List<Integer> fieldRegItems = new ArrayList<Integer>(); + + for (int item : fieldItems) { + if (Gen5Constants.allowedItems.isAllowed(item) && !(Gen5Constants.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(); + + for (int i = 0; i < fiLength; i++) { + int oldItem = fieldItems.get(i); + if (!(Gen5Constants.allowedItems.isTM(oldItem)) && Gen5Constants.allowedItems.isAllowed(oldItem)) { + int newItem = iterNewItems.next(); + fieldItems.set(i, newItem); + } + } + + this.setFieldItems(fieldItems); + } + + @Override + public List<Integer> getRequiredFieldTMs() { + if (romEntry.romType == Gen5Constants.Type_BW) { + return Gen5Constants.bw1RequiredFieldTMs; + } else { + return Gen5Constants.bw2RequiredFieldTMs; + } + } + + @Override + public List<IngameTrade> getIngameTrades() { + List<IngameTrade> trades = new ArrayList<IngameTrade>(); + try { + NARCContents tradeNARC = this.readNARC(romEntry.getString("InGameTrades")); + List<String> tradeStrings = getStrings(false, romEntry.getInt("IngameTradesTextOffset")); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + int tableSize = tradeNARC.files.size(); + + for (int entry = 0; entry < tableSize; entry++) { + if (unusedOffset < unused.length && unused[unusedOffset] == entry) { + unusedOffset++; + continue; + } + IngameTrade trade = new IngameTrade(); + byte[] tfile = tradeNARC.files.get(entry); + trade.nickname = tradeStrings.get(entry * 2); + trade.givenPokemon = pokes[readLong(tfile, 4)]; + trade.ivs = new int[6]; + for (int iv = 0; iv < 6; iv++) { + trade.ivs[iv] = readLong(tfile, 0x10 + iv * 4); + } + trade.otId = readWord(tfile, 0x34); + trade.item = readLong(tfile, 0x4C); + trade.otName = tradeStrings.get(entry * 2 + 1); + trade.requestedPokemon = pokes[readLong(tfile, 0x5C)]; + trades.add(trade); + } + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + return trades; + + } + + @Override + public void setIngameTrades(List<IngameTrade> trades) { + // info + int tradeOffset = 0; + try { + NARCContents tradeNARC = this.readNARC(romEntry.getString("InGameTrades")); + List<String> tradeStrings = getStrings(false, romEntry.getInt("IngameTradesTextOffset")); + int tradeCount = tradeNARC.files.size(); + int[] unused = romEntry.arrayEntries.get("TradesUnused"); + int unusedOffset = 0; + for (int i = 0; i < tradeCount; i++) { + if (unusedOffset < unused.length && unused[unusedOffset] == i) { + unusedOffset++; + continue; + } + byte[] tfile = tradeNARC.files.get(i); + IngameTrade trade = trades.get(tradeOffset++); + tradeStrings.set(i * 2, trade.nickname); + tradeStrings.set(i * 2 + 1, trade.otName); + writeLong(tfile, 4, trade.givenPokemon.number); + writeLong(tfile, 8, 0); // disable forme + for (int iv = 0; iv < 6; iv++) { + writeLong(tfile, 0x10 + iv * 4, trade.ivs[iv]); + } + writeLong(tfile, 0x2C, 0xFF); // random nature + writeWord(tfile, 0x34, trade.otId); + writeLong(tfile, 0x4C, trade.item); + writeLong(tfile, 0x5C, trade.requestedPokemon.number); + } + this.writeNARC(romEntry.getString("InGameTrades"), tradeNARC); + this.setStrings(false, romEntry.getInt("IngameTradesTextOffset"), tradeStrings); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + @Override + public boolean hasDVs() { + return false; + } + + @Override + public int generationOfPokemon() { + return 5; + } + + @Override + public void removeEvosForPokemonPool() { + // slightly more complicated than gen2/3 + // we have to update a "baby table" too + List<Pokemon> pokemonIncluded = this.mainPokemonList; + Set<Evolution> keepEvos = new HashSet<Evolution>(); + 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 { + NARCContents babyNARC = readNARC(romEntry.getString("BabyPokemon")); + // baby pokemon + for (int i = 1; i <= Gen5Constants.pokemonCount; i++) { + Pokemon baby = pokes[i]; + while (baby.evolutionsTo.size() > 0) { + // Grab the first "to evolution" even if there are multiple + baby = baby.evolutionsTo.get(0).from; + } + writeWord(babyNARC.files.get(i), 0, baby.number); + } + // finish up + writeNARC(romEntry.getString("BabyPokemon"), babyNARC); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean supportsFourStartingMoves() { + return true; + } + + @Override + public List<Integer> getFieldMoves() { + // cut, fly, surf, strength, flash, dig, teleport, waterfall, + // sweet scent, dive + return Gen5Constants.fieldMoves; + } + + @Override + public List<Integer> getEarlyRequiredHMMoves() { + // BW1: cut + // BW2: none + if (romEntry.romType == Gen5Constants.Type_BW2) { + return Gen5Constants.bw2EarlyRequiredHMMoves; + } else { + return Gen5Constants.bw1EarlyRequiredHMMoves; + } + } + + @Override + public BufferedImage getMascotImage() { + try { + Pokemon pk = randomPokemon(); + NARCContents pokespritesNARC = this.readNARC(romEntry.getString("PokemonGraphics")); + + // First prepare the palette, it's the easy bit + byte[] rawPalette = pokespritesNARC.files.get(pk.number * 20 + 18); + int[] palette = new int[16]; + for (int i = 1; i < 16; i++) { + palette[i] = GFXFunctions.conv16BitColorToARGB(readWord(rawPalette, 40 + i * 2)); + } + + // Get the picture and uncompress it. + byte[] compressedPic = pokespritesNARC.files.get(pk.number * 20); + byte[] uncompressedPic = DSDecmp.Decompress(compressedPic); + + // Output to 64x144 tiled image to prepare for unscrambling + BufferedImage bim = GFXFunctions.drawTiledImage(uncompressedPic, palette, 48, 64, 144, 4); + + // Unscramble the above onto a 96x96 canvas + BufferedImage finalImage = new BufferedImage(96, 96, BufferedImage.TYPE_INT_ARGB); + Graphics g = finalImage.getGraphics(); + g.drawImage(bim, 0, 0, 64, 64, 0, 0, 64, 64, null); + g.drawImage(bim, 64, 0, 96, 8, 0, 64, 32, 72, null); + g.drawImage(bim, 64, 8, 96, 16, 32, 64, 64, 72, null); + g.drawImage(bim, 64, 16, 96, 24, 0, 72, 32, 80, null); + g.drawImage(bim, 64, 24, 96, 32, 32, 72, 64, 80, null); + g.drawImage(bim, 64, 32, 96, 40, 0, 80, 32, 88, null); + g.drawImage(bim, 64, 40, 96, 48, 32, 80, 64, 88, null); + g.drawImage(bim, 64, 48, 96, 56, 0, 88, 32, 96, null); + g.drawImage(bim, 64, 56, 96, 64, 32, 88, 64, 96, null); + g.drawImage(bim, 0, 64, 64, 96, 0, 96, 64, 128, null); + g.drawImage(bim, 64, 64, 96, 72, 0, 128, 32, 136, null); + g.drawImage(bim, 64, 72, 96, 80, 32, 128, 64, 136, null); + g.drawImage(bim, 64, 80, 96, 88, 0, 136, 32, 144, null); + g.drawImage(bim, 64, 88, 96, 96, 32, 136, 64, 144, null); + + // Phew, all done. + return finalImage; + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/com/dabomstew/pkrandom/romhandlers/NARCContents.java b/src/com/dabomstew/pkrandom/romhandlers/NARCContents.java index 80505dc..27ac72a 100755 --- a/src/com/dabomstew/pkrandom/romhandlers/NARCContents.java +++ b/src/com/dabomstew/pkrandom/romhandlers/NARCContents.java @@ -1,36 +1,36 @@ -package com.dabomstew.pkrandom.romhandlers;
-
-/*----------------------------------------------------------------------------*/
-/*-- NARCContents.java - represents the contents of a NARC archive. --*/
-/*-- --*/
-/*-- Part of "Universal Pokemon Randomizer" by Dabomstew --*/
-/*-- Pokemon and any associated names and the like are --*/
-/*-- trademark and (C) Nintendo 1996-2012. --*/
-/*-- --*/
-/*-- 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 java.util.ArrayList;
-import java.util.List;
-
-public class NARCContents {
-
- public List<String> filenames = new ArrayList<String>();
- public List<byte[]> files = new ArrayList<byte[]>();
-
- public boolean hasFilenames = false;
-
-}
+package com.dabomstew.pkrandom.romhandlers; + +/*----------------------------------------------------------------------------*/ +/*-- NARCContents.java - represents the contents of a NARC archive. --*/ +/*-- --*/ +/*-- Part of "Universal Pokemon Randomizer" by Dabomstew --*/ +/*-- Pokemon and any associated names and the like are --*/ +/*-- trademark and (C) Nintendo 1996-2012. --*/ +/*-- --*/ +/*-- 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 java.util.ArrayList; +import java.util.List; + +public class NARCContents { + + public List<String> filenames = new ArrayList<String>(); + public List<byte[]> files = new ArrayList<byte[]>(); + + public boolean hasFilenames = false; + +} diff --git a/src/com/dabomstew/pkrandom/romhandlers/RomHandler.java b/src/com/dabomstew/pkrandom/romhandlers/RomHandler.java index 2877a76..2a96e26 100755 --- a/src/com/dabomstew/pkrandom/romhandlers/RomHandler.java +++ b/src/com/dabomstew/pkrandom/romhandlers/RomHandler.java @@ -43,388 +43,381 @@ import com.dabomstew.pkrandom.pokemon.Type; public interface RomHandler { - public abstract class Factory { - public RomHandler create(Random random) { - return create(random, null); - } + public abstract class Factory { + public RomHandler create(Random random) { + return create(random, null); + } - public abstract RomHandler create(Random random, PrintStream log); + public abstract RomHandler create(Random random, PrintStream log); - public abstract boolean isLoadable(String filename); - } + public abstract boolean isLoadable(String filename); + } - // Basic load/save to filenames + // Basic load/save to filenames - public boolean loadRom(String filename); + public boolean loadRom(String filename); - public boolean saveRom(String filename); + public boolean saveRom(String filename); - public String loadedFilename(); + public String loadedFilename(); - // Log stuff + // Log stuff - public void setLog(PrintStream logStream); + public void setLog(PrintStream logStream); - // Get a List of Pokemon objects in this game. - // 0 = null 1-whatever = the Pokemon. - public List<Pokemon> getPokemon(); + // Get a List of Pokemon objects in this game. + // 0 = null 1-whatever = the Pokemon. + public List<Pokemon> getPokemon(); - // Setup Gen Restrictions. - public void setPokemonPool(GenRestrictions restrictions); + // Setup Gen Restrictions. + public void setPokemonPool(GenRestrictions restrictions); - public void removeEvosForPokemonPool(); + public void removeEvosForPokemonPool(); - // Randomizer: Starters - // Get starters, they should be ordered with Pokemon - // following the one it is SE against. - // E.g. Grass, Fire, Water or Fire, Water, Grass etc. - public List<Pokemon> getStarters(); + // Randomizer: Starters + // Get starters, they should be ordered with Pokemon + // following the one it is SE against. + // E.g. Grass, Fire, Water or Fire, Water, Grass etc. + public List<Pokemon> getStarters(); - // Change the starter data in the ROM. - // Optionally also change the starter used by the rival in - // the level 5 battle, if there is one. - public boolean setStarters(List<Pokemon> newStarters); + // Change the starter data in the ROM. + // Optionally also change the starter used by the rival in + // the level 5 battle, if there is one. + public boolean setStarters(List<Pokemon> newStarters); - // Tells whether this ROM has the ability to have starters changed. - // Was for before CUE's compressors were found and arm9 was untouchable. - public boolean canChangeStarters(); + // Tells whether this ROM has the ability to have starters changed. + // Was for before CUE's compressors were found and arm9 was untouchable. + public boolean canChangeStarters(); - // Randomizer: Pokemon stats + // Randomizer: Pokemon stats - // Run the stats shuffler on each Pokemon. - public void shufflePokemonStats(); + // Run the stats shuffler on each Pokemon. + public void shufflePokemonStats(); - // Randomise stats following evolutions for proportions or not (see - // tooltips) - public void randomizePokemonStats(boolean evolutionSanity); + // Randomise stats following evolutions for proportions or not (see + // tooltips) + public void randomizePokemonStats(boolean evolutionSanity); - // Give a random Pokemon who's in this game - public Pokemon randomPokemon(); + // Give a random Pokemon who's in this game + public Pokemon randomPokemon(); - // Give a random non-legendary Pokemon who's in this game - // Business rules for who's legendary are in Pokemon class - public Pokemon randomNonLegendaryPokemon(); + // Give a random non-legendary Pokemon who's in this game + // Business rules for who's legendary are in Pokemon class + public Pokemon randomNonLegendaryPokemon(); - // Give a random legendary Pokemon who's in this game - // Business rules for who's legendary are in Pokemon class - public Pokemon randomLegendaryPokemon(); + // Give a random legendary Pokemon who's in this game + // Business rules for who's legendary are in Pokemon class + public Pokemon randomLegendaryPokemon(); - // Give a random Pokemon who has 2 evolution stages - // Should make a good starter Pokemon - public Pokemon random2EvosPokemon(); + // Give a random Pokemon who has 2 evolution stages + // Should make a good starter Pokemon + public Pokemon random2EvosPokemon(); - // Randomizer: moves + // Randomizer: moves - public void randomizeMovePowers(); + public void randomizeMovePowers(); - public void randomizeMovePPs(); + public void randomizeMovePPs(); - public void randomizeMoveAccuracies(); + public void randomizeMoveAccuracies(); - public void randomizeMoveTypes(); + public void randomizeMoveTypes(); - public boolean hasPhysicalSpecialSplit(); + public boolean hasPhysicalSpecialSplit(); - public void randomizeMoveCategory(); + public void randomizeMoveCategory(); - // Update all moves to gen5 definitions as much as possible - // e.g. change typing, power, accuracy, but don't try to - // stuff around with effects. - public void updateMovesToGen5(); + // Update all moves to gen5 definitions as much as possible + // e.g. change typing, power, accuracy, but don't try to + // stuff around with effects. + public void updateMovesToGen5(); - // same for gen6 - public void updateMovesToGen6(); + // same for gen6 + public void updateMovesToGen6(); - // stuff for printing move changes - public void initMoveUpdates(); + // stuff for printing move changes + public void initMoveUpdates(); - public void printMoveUpdates(); + public void printMoveUpdates(); - // return all the moves valid in this game. - public List<Move> getMoves(); + // return all the moves valid in this game. + public List<Move> getMoves(); - // Randomizer: types + // Randomizer: types - // return a random type valid in this game. - // straightforward except for gen1 where dark&steel are excluded. - public Type randomType(); + // return a random type valid in this game. + // straightforward except for gen1 where dark&steel are excluded. + public Type randomType(); - // randomise Pokemon types, with a switch on whether evolutions - // should follow the same types or not. - // some evolutions dont anyway, e.g. Eeveelutions, Hitmons - public void randomizePokemonTypes(boolean evolutionSanity); + // randomise Pokemon types, with a switch on whether evolutions + // should follow the same types or not. + // some evolutions dont anyway, e.g. Eeveelutions, Hitmons + public void randomizePokemonTypes(boolean evolutionSanity); - // Randomizer: wild pokemon - public List<EncounterSet> getEncounters(boolean useTimeOfDay); + // Randomizer: wild pokemon + public List<EncounterSet> getEncounters(boolean useTimeOfDay); - public void setEncounters(boolean useTimeOfDay, - List<EncounterSet> encounters); + public void setEncounters(boolean useTimeOfDay, List<EncounterSet> encounters); - public void randomEncounters(boolean useTimeOfDay, boolean catchEmAll, - boolean typeThemed, boolean usePowerLevels, boolean noLegendaries); + public void randomEncounters(boolean useTimeOfDay, boolean catchEmAll, boolean typeThemed, boolean usePowerLevels, + boolean noLegendaries); - public void area1to1Encounters(boolean useTimeOfDay, boolean catchEmAll, - boolean typeThemed, boolean usePowerLevels, boolean noLegendaries); + public void area1to1Encounters(boolean useTimeOfDay, boolean catchEmAll, boolean typeThemed, + boolean usePowerLevels, boolean noLegendaries); - public void game1to1Encounters(boolean useTimeOfDay, - boolean usePowerLevels, boolean noLegendaries); + public void game1to1Encounters(boolean useTimeOfDay, boolean usePowerLevels, boolean noLegendaries); - public boolean hasTimeBasedEncounters(); + public boolean hasTimeBasedEncounters(); - public List<Pokemon> bannedForWildEncounters(); + public List<Pokemon> bannedForWildEncounters(); - // Randomizer: trainer pokemon - public List<Trainer> getTrainers(); + // Randomizer: trainer pokemon + public List<Trainer> getTrainers(); - public void setTrainers(List<Trainer> trainerData); + public void setTrainers(List<Trainer> trainerData); - public void randomizeTrainerPokes(boolean rivalCarriesStarter, - boolean usePowerLevels, boolean noLegendaries, - boolean noEarlyWonderGuard); + public void randomizeTrainerPokes(boolean rivalCarriesStarter, boolean usePowerLevels, boolean noLegendaries, + boolean noEarlyWonderGuard); - public void typeThemeTrainerPokes(boolean rivalCarriesStarter, - boolean usePowerLevels, boolean weightByFrequency, - boolean noLegendaries, boolean noEarlyWonderGuard); + public void typeThemeTrainerPokes(boolean rivalCarriesStarter, boolean usePowerLevels, boolean weightByFrequency, + boolean noLegendaries, boolean noEarlyWonderGuard); - public boolean typeInGame(Type type); + public boolean typeInGame(Type type); - // Randomizer: moves learnt + // Randomizer: moves learnt - public Map<Pokemon, List<MoveLearnt>> getMovesLearnt(); + public Map<Pokemon, List<MoveLearnt>> getMovesLearnt(); - public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets); + public void setMovesLearnt(Map<Pokemon, List<MoveLearnt>> movesets); - public List<Integer> getMovesBannedFromLevelup(); + public List<Integer> getMovesBannedFromLevelup(); - public void randomizeMovesLearnt(boolean typeThemed, boolean noBroken, - boolean forceFourStartingMoves); + public void randomizeMovesLearnt(boolean typeThemed, boolean noBroken, boolean forceFourStartingMoves); - public void metronomeOnlyMode(); + public void metronomeOnlyMode(); - public boolean supportsFourStartingMoves(); + public boolean supportsFourStartingMoves(); - // Randomizer: static pokemon (except starters) + // Randomizer: static pokemon (except starters) - public List<Pokemon> getStaticPokemon(); + public List<Pokemon> getStaticPokemon(); - public boolean setStaticPokemon(List<Pokemon> staticPokemon); + public boolean setStaticPokemon(List<Pokemon> staticPokemon); - public void randomizeStaticPokemon(boolean legendForLegend); + public void randomizeStaticPokemon(boolean legendForLegend); - public boolean canChangeStaticPokemon(); + public boolean canChangeStaticPokemon(); - public List<Pokemon> bannedForStaticPokemon(); + public List<Pokemon> bannedForStaticPokemon(); - // Randomizer: TMs/HMs + // Randomizer: TMs/HMs - public List<Integer> getTMMoves(); + public List<Integer> getTMMoves(); - public List<Integer> getHMMoves(); + public List<Integer> getHMMoves(); - public void setTMMoves(List<Integer> moveIndexes); + public void setTMMoves(List<Integer> moveIndexes); - public void randomizeTMMoves(boolean noBroken, boolean preserveField); + public void randomizeTMMoves(boolean noBroken, boolean preserveField); - public int getTMCount(); + public int getTMCount(); - public int getHMCount(); + public int getHMCount(); - /** - * Get TM/HM compatibility data from this rom. The result should contain a - * boolean array for each Pokemon indexed as such: - * - * 0: blank (false) / 1 - (getTMCount()) : TM compatibility / - * (getTMCount()+1) - (getTMCount()+getHMCount()) - HM compatibility - * - * @return - */ + /** + * Get TM/HM compatibility data from this rom. The result should contain a + * boolean array for each Pokemon indexed as such: + * + * 0: blank (false) / 1 - (getTMCount()) : TM compatibility / + * (getTMCount()+1) - (getTMCount()+getHMCount()) - HM compatibility + * + * @return + */ - public Map<Pokemon, boolean[]> getTMHMCompatibility(); + public Map<Pokemon, boolean[]> getTMHMCompatibility(); - public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData); + public void setTMHMCompatibility(Map<Pokemon, boolean[]> compatData); - public void randomizeTMHMCompatibility(boolean preferSameType); + public void randomizeTMHMCompatibility(boolean preferSameType); - public void fullTMHMCompatibility(); + public void fullTMHMCompatibility(); - // tm/moveset sanity + // tm/moveset sanity - public void ensureTMCompatSanity(); + public void ensureTMCompatSanity(); - // new 170: full HM (but not TM) compat override + // new 170: full HM (but not TM) compat override - public void fullHMCompatibility(); + public void fullHMCompatibility(); - // Randomizer: move tutors + // Randomizer: move tutors - public boolean hasMoveTutors(); + public boolean hasMoveTutors(); - public List<Integer> getMoveTutorMoves(); + public List<Integer> getMoveTutorMoves(); - public void setMoveTutorMoves(List<Integer> moves); + public void setMoveTutorMoves(List<Integer> moves); - public void randomizeMoveTutorMoves(boolean noBroken, boolean preserveField); + public void randomizeMoveTutorMoves(boolean noBroken, boolean preserveField); - public Map<Pokemon, boolean[]> getMoveTutorCompatibility(); + public Map<Pokemon, boolean[]> getMoveTutorCompatibility(); - public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData); + public void setMoveTutorCompatibility(Map<Pokemon, boolean[]> compatData); - public void randomizeMoveTutorCompatibility(boolean preferSameType); + public void randomizeMoveTutorCompatibility(boolean preferSameType); - public void fullMoveTutorCompatibility(); + public void fullMoveTutorCompatibility(); - // mt/moveset sanity + // mt/moveset sanity - public void ensureMoveTutorCompatSanity(); + public void ensureMoveTutorCompatSanity(); - // Randomizer: trainer names + // Randomizer: trainer names - public List<String> getTrainerNames(); + public List<String> getTrainerNames(); - public void setTrainerNames(List<String> trainerNames); + public void setTrainerNames(List<String> trainerNames); - public enum TrainerNameMode { - SAME_LENGTH, MAX_LENGTH, MAX_LENGTH_WITH_CLASS - }; + public enum TrainerNameMode { + SAME_LENGTH, MAX_LENGTH, MAX_LENGTH_WITH_CLASS + }; - public TrainerNameMode trainerNameMode(); + public TrainerNameMode trainerNameMode(); - // Returns this with or without the class - public int maxTrainerNameLength(); + // Returns this with or without the class + public int maxTrainerNameLength(); - // Only needed if above mode is "MAX LENGTH WITH CLASS" - public List<Integer> getTCNameLengthsByTrainer(); + // Only needed if above mode is "MAX LENGTH WITH CLASS" + public List<Integer> getTCNameLengthsByTrainer(); - public void randomizeTrainerNames(byte[] presetNames); + public void randomizeTrainerNames(byte[] presetNames); - // Randomizer: trainer class names + // Randomizer: trainer class names - public List<String> getTrainerClassNames(); + public List<String> getTrainerClassNames(); - public void setTrainerClassNames(List<String> trainerClassNames); + public void setTrainerClassNames(List<String> trainerClassNames); - public boolean fixedTrainerClassNamesLength(); + public boolean fixedTrainerClassNamesLength(); - public int maxTrainerClassNameLength(); + public int maxTrainerClassNameLength(); - public void randomizeTrainerClassNames(byte[] presetNames); + public void randomizeTrainerClassNames(byte[] presetNames); - // Randomizer: pokemon abilities - public int abilitiesPerPokemon(); + // Randomizer: pokemon abilities + public int abilitiesPerPokemon(); - public int highestAbilityIndex(); + public int highestAbilityIndex(); - public String abilityName(int number); + public String abilityName(int number); - public void randomizeAbilities(boolean allowWonderGuard); + public void randomizeAbilities(boolean allowWonderGuard); - // Items + // Items - public ItemList getAllowedItems(); + public ItemList getAllowedItems(); - public ItemList getNonBadItems(); + public ItemList getNonBadItems(); - public void randomizeWildHeldItems(boolean banBadItems); + public void randomizeWildHeldItems(boolean banBadItems); - public String[] getItemNames(); + public String[] getItemNames(); - public List<Integer> getStarterHeldItems(); + public List<Integer> getStarterHeldItems(); - public void setStarterHeldItems(List<Integer> items); + public void setStarterHeldItems(List<Integer> items); - public void randomizeStarterHeldItems(boolean banBadItems); + public void randomizeStarterHeldItems(boolean banBadItems); - // Field Items + // Field Items - // TMs on the field + // TMs on the field - public List<Integer> getRequiredFieldTMs(); + public List<Integer> getRequiredFieldTMs(); - public List<Integer> getCurrentFieldTMs(); + public List<Integer> getCurrentFieldTMs(); - public void setFieldTMs(List<Integer> fieldTMs); + public void setFieldTMs(List<Integer> fieldTMs); - // Everything else + // Everything else - public List<Integer> getRegularFieldItems(); + public List<Integer> getRegularFieldItems(); - public void setRegularFieldItems(List<Integer> items); + public void setRegularFieldItems(List<Integer> items); - // Randomizer methods + // Randomizer methods - public void shuffleFieldItems(); + public void shuffleFieldItems(); - public void randomizeFieldItems(boolean banBadItems); + public void randomizeFieldItems(boolean banBadItems); - // Trades + // Trades - public List<IngameTrade> getIngameTrades(); + public List<IngameTrade> getIngameTrades(); - public void setIngameTrades(List<IngameTrade> trades); + public void setIngameTrades(List<IngameTrade> trades); - public void randomizeIngameTrades(boolean randomizeRequest, - byte[] presetNicknames, boolean randomNickname, - byte[] presetTrainerNames, boolean randomOT, boolean randomStats, - boolean randomItem); + public void randomizeIngameTrades(boolean randomizeRequest, byte[] presetNicknames, boolean randomNickname, + byte[] presetTrainerNames, boolean randomOT, boolean randomStats, boolean randomItem); - public boolean hasDVs(); + public boolean hasDVs(); - public int maxTradeNicknameLength(); + public int maxTradeNicknameLength(); - public int maxTradeOTNameLength(); + public int maxTradeOTNameLength(); - // Evos + // Evos - public void removeTradeEvolutions(boolean changeMoveEvos); + public void removeTradeEvolutions(boolean changeMoveEvos); - public void condenseLevelEvolutions(int maxLevel, int maxIntermediateLevel); + public void condenseLevelEvolutions(int maxLevel, int maxIntermediateLevel); - public void randomizeEvolutions(boolean similarStrength, boolean sameType, - boolean limitToThreeStages, boolean forceChange); + public void randomizeEvolutions(boolean similarStrength, boolean sameType, boolean limitToThreeStages, + boolean forceChange); - // stats stuff - public void minimumCatchRate(int rateNonLegendary, int rateLegendary); + // stats stuff + public void minimumCatchRate(int rateNonLegendary, int rateLegendary); - public void standardizeEXPCurves(); + public void standardizeEXPCurves(); - // (Mostly) unchanging lists of moves + // (Mostly) unchanging lists of moves - public List<Integer> getGameBreakingMoves(); + public List<Integer> getGameBreakingMoves(); - // includes game or gen-specific moves like Secret Power - // but NOT healing moves (Softboiled, Milk Drink) - public List<Integer> getFieldMoves(); + // includes game or gen-specific moves like Secret Power + // but NOT healing moves (Softboiled, Milk Drink) + public List<Integer> getFieldMoves(); - // any HMs required to obtain 4 badges - // (excluding Gameshark codes or early drink in RBY) - public List<Integer> getEarlyRequiredHMMoves(); + // any HMs required to obtain 4 badges + // (excluding Gameshark codes or early drink in RBY) + public List<Integer> getEarlyRequiredHMMoves(); - // Misc + // Misc - public boolean isYellow(); + public boolean isYellow(); - public String getROMName(); + public String getROMName(); - public String getROMCode(); + public String getROMCode(); - public String getSupportLevel(); + public String getSupportLevel(); - public String getDefaultExtension(); + public String getDefaultExtension(); - public int internalStringLength(String string); + public int internalStringLength(String string); - public void applySignature(); - - public BufferedImage getMascotImage(); + public void applySignature(); - public boolean isROMHack(); + public BufferedImage getMascotImage(); - public int generationOfPokemon(); + public boolean isROMHack(); - // code tweaks + public int generationOfPokemon(); - public int miscTweaksAvailable(); + // code tweaks - public void applyMiscTweak(MiscTweak tweak); + public int miscTweaksAvailable(); + + public void applyMiscTweak(MiscTweak tweak); } |