summaryrefslogtreecommitdiff
path: root/src/com/dabomstew/pkrandom/romhandlers
diff options
context:
space:
mode:
authorDabomstew <dbs.stew@gmail.com>2016-02-22 10:57:03 +1300
committerDabomstew <dbs.stew@gmail.com>2016-02-22 10:57:03 +1300
commita97ccb1d4cdc5961d90ad5cbb68743705ca12941 (patch)
treea75ecb9e55776ad56ad41309a9fbbf08bfa4adf1 /src/com/dabomstew/pkrandom/romhandlers
parentb824cc40c298095ad46e2e02563688158e49a505 (diff)
Reformat code en masse.
Diffstat (limited to 'src/com/dabomstew/pkrandom/romhandlers')
-rwxr-xr-xsrc/com/dabomstew/pkrandom/romhandlers/AbstractDSRomHandler.java744
-rwxr-xr-xsrc/com/dabomstew/pkrandom/romhandlers/AbstractGBRomHandler.java245
-rwxr-xr-xsrc/com/dabomstew/pkrandom/romhandlers/AbstractRomHandler.java6599
-rwxr-xr-xsrc/com/dabomstew/pkrandom/romhandlers/Gen1RomHandler.java5016
-rwxr-xr-xsrc/com/dabomstew/pkrandom/romhandlers/Gen2RomHandler.java4638
-rwxr-xr-xsrc/com/dabomstew/pkrandom/romhandlers/Gen3RomHandler.java6092
-rwxr-xr-xsrc/com/dabomstew/pkrandom/romhandlers/Gen4RomHandler.java5413
-rwxr-xr-xsrc/com/dabomstew/pkrandom/romhandlers/Gen5RomHandler.java4734
-rwxr-xr-xsrc/com/dabomstew/pkrandom/romhandlers/NARCContents.java72
-rwxr-xr-xsrc/com/dabomstew/pkrandom/romhandlers/RomHandler.java449
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);
}