package com.dabomstew.pkrandom;
/*----------------------------------------------------------------------------*/
/*-- RomFunctions.java - contains functions useful throughout the program. --*/
/*-- --*/
/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/
/*-- Originally part of "Universal Pokemon Randomizer" by Dabomstew --*/
/*-- Pokemon and any associated names and the like are --*/
/*-- trademark and (C) Nintendo 1996-2020. --*/
/*-- --*/
/*-- The custom code written here is licensed under the terms of the GPL: --*/
/*-- --*/
/*-- This program is free software: you can redistribute it and/or modify --*/
/*-- it under the terms of the GNU General Public License as published by --*/
/*-- the Free Software Foundation, either version 3 of the License, or --*/
/*-- (at your option) any later version. --*/
/*-- --*/
/*-- This program is distributed in the hope that it will be useful, --*/
/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/
/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/
/*-- GNU General Public License for more details. --*/
/*-- --*/
/*-- You should have received a copy of the GNU General Public License --*/
/*-- along with this program. If not, see . --*/
/*----------------------------------------------------------------------------*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import com.dabomstew.pkrandom.pokemon.Evolution;
import com.dabomstew.pkrandom.pokemon.MoveLearnt;
import com.dabomstew.pkrandom.pokemon.Pokemon;
import com.dabomstew.pkrandom.romhandlers.RomHandler;
public class RomFunctions {
public static Set getBasicPokemon(RomHandler baseRom) {
List allPokes = baseRom.getPokemonInclFormes();
Set basicPokes = new TreeSet<>();
for (Pokemon pkmn : allPokes) {
if (pkmn != null) {
if (pkmn.evolutionsTo.size() < 1) {
basicPokes.add(pkmn);
}
}
}
return basicPokes;
}
public static Set getSplitEvolutions(RomHandler baseRom) {
List allPokes = baseRom.getPokemonInclFormes();
Set splitEvos = new TreeSet<>();
for (Pokemon pkmn : allPokes) {
if (pkmn != null) {
if (pkmn.evolutionsTo.size() > 0) {
Evolution onlyEvo = pkmn.evolutionsTo.get(0);
if (!onlyEvo.carryStats) {
splitEvos.add(pkmn);
}
}
}
}
return splitEvos;
}
public static Set getMiddleEvolutions(RomHandler baseRom, boolean includeSplitEvos) {
List allPokes = baseRom.getPokemon();
Set middleEvolutions = new TreeSet<>();
for (Pokemon pkmn : allPokes) {
if (pkmn != null) {
if (pkmn.evolutionsTo.size() == 1 && pkmn.evolutionsFrom.size() > 0) {
Evolution onlyEvo = pkmn.evolutionsTo.get(0);
if (onlyEvo.carryStats || includeSplitEvos) {
middleEvolutions.add(pkmn);
}
}
}
}
return middleEvolutions;
}
public static Set getFinalEvolutions(RomHandler baseRom, boolean includeSplitEvos) {
List allPokes = baseRom.getPokemon();
Set finalEvolutions = new TreeSet<>();
for (Pokemon pkmn : allPokes) {
if (pkmn != null) {
if (pkmn.evolutionsTo.size() == 1 && pkmn.evolutionsFrom.size() == 0) {
Evolution onlyEvo = pkmn.evolutionsTo.get(0);
if (onlyEvo.carryStats || includeSplitEvos) {
finalEvolutions.add(pkmn);
}
}
}
}
return finalEvolutions;
}
/**
* Get the 4 moves known by a Pokemon at a particular level.
*
* @param pkmn Pokemon index to get moves for.
* @param movesets Map of Pokemon indices mapped to movesets.
* @param level Level to get at.
* @return Array with move indices.
*/
public static int[] getMovesAtLevel(int pkmn, Map> movesets, int level) {
return getMovesAtLevel(pkmn, movesets, level, 0);
}
public static int[] getMovesAtLevel(int pkmn, Map> movesets, int level, int emptyValue) {
int[] curMoves = new int[4];
if (emptyValue != 0) {
Arrays.fill(curMoves, emptyValue);
}
int moveCount = 0;
List movepool = movesets.get(pkmn);
for (MoveLearnt ml : movepool) {
if (ml.level > level) {
// we're done
break;
}
boolean alreadyKnownMove = false;
for (int i = 0; i < moveCount; i++) {
if (curMoves[i] == ml.move) {
alreadyKnownMove = true;
break;
}
}
if (!alreadyKnownMove) {
// add this move to the moveset
if (moveCount == 4) {
// shift moves up and add to last slot
System.arraycopy(curMoves, 1, curMoves, 0, 3);
curMoves[3] = ml.move;
} else {
// add to next available slot
curMoves[moveCount++] = ml.move;
}
}
}
return curMoves;
}
public static String camelCase(String original) {
char[] string = original.toLowerCase().toCharArray();
boolean docap = true;
for (int j = 0; j < string.length; j++) {
char current = string[j];
if (docap && Character.isLetter(current)) {
string[j] = Character.toUpperCase(current);
docap = false;
} else {
if (!docap && !Character.isLetter(current) && current != '\'') {
docap = true;
}
}
}
return new String(string);
}
public static int freeSpaceFinder(byte[] rom, byte freeSpace, int amount, int offset) {
// by default align to 4 bytes to make sure things don't break
return freeSpaceFinder(rom, freeSpace, amount, offset, true);
}
public static int freeSpaceFinder(byte[] rom, byte freeSpace, int amount, int offset, boolean longAligned) {
if (!longAligned) {
// Find 2 more than necessary and return 2 into it,
// to preserve stuff like FF terminators for strings
// 161: and FFFF terminators for movesets
byte[] searchNeedle = new byte[amount + 2];
for (int i = 0; i < amount + 2; i++) {
searchNeedle[i] = freeSpace;
}
return searchForFirst(rom, offset, searchNeedle) + 2;
} else {
// Find 5 more than necessary and return into it as necessary for
// 4-alignment,
// to preserve stuff like FF terminators for strings
// 161: and FFFF terminators for movesets
byte[] searchNeedle = new byte[amount + 5];
for (int i = 0; i < amount + 5; i++) {
searchNeedle[i] = freeSpace;
}
return (searchForFirst(rom, offset, searchNeedle) + 5) & ~3;
}
}
public static List search(byte[] haystack, byte[] needle) {
return search(haystack, 0, haystack.length, needle);
}
public static List search(byte[] haystack, int beginOffset, byte[] needle) {
return search(haystack, beginOffset, haystack.length, needle);
}
public static List search(byte[] haystack, int beginOffset, int endOffset, byte[] needle) {
int currentMatchStart = beginOffset;
int currentCharacterPosition = 0;
int needleSize = needle.length;
int[] toFillTable = buildKMPSearchTable(needle);
List results = new ArrayList<>();
while ((currentMatchStart + currentCharacterPosition) < endOffset) {
if (needle[currentCharacterPosition] == (haystack[currentCharacterPosition + currentMatchStart])) {
currentCharacterPosition = currentCharacterPosition + 1;
if (currentCharacterPosition == (needleSize)) {
results.add(currentMatchStart);
currentCharacterPosition = 0;
currentMatchStart = currentMatchStart + needleSize;
}
} else {
currentMatchStart = currentMatchStart + currentCharacterPosition
- toFillTable[currentCharacterPosition];
if (toFillTable[currentCharacterPosition] > -1) {
currentCharacterPosition = toFillTable[currentCharacterPosition];
}
else {
currentCharacterPosition = 0;
}
}
}
return results;
}
private static int searchForFirst(byte[] haystack, int beginOffset, byte[] needle) {
int currentMatchStart = beginOffset;
int currentCharacterPosition = 0;
int docSize = haystack.length;
int needleSize = needle.length;
int[] toFillTable = buildKMPSearchTable(needle);
while ((currentMatchStart + currentCharacterPosition) < docSize) {
if (needle[currentCharacterPosition] == (haystack[currentCharacterPosition + currentMatchStart])) {
currentCharacterPosition = currentCharacterPosition + 1;
if (currentCharacterPosition == (needleSize)) {
return currentMatchStart;
}
} else {
currentMatchStart = currentMatchStart + currentCharacterPosition
- toFillTable[currentCharacterPosition];
if (toFillTable[currentCharacterPosition] > -1) {
currentCharacterPosition = toFillTable[currentCharacterPosition];
}
else {
currentCharacterPosition = 0;
}
}
}
return -1;
}
private static int[] buildKMPSearchTable(byte[] needle) {
int[] stable = new int[needle.length];
int pos = 2;
int j = 0;
stable[0] = -1;
stable[1] = 0;
while (pos < needle.length) {
if (needle[pos - 1] == needle[j]) {
stable[pos] = j + 1;
pos++;
j++;
} else if (j > 0) {
j = stable[j];
} else {
stable[pos] = 0;
pos++;
}
}
return stable;
}
public static String rewriteDescriptionForNewLineSize(String moveDesc, String newline, int lineSize,
StringSizeDeterminer ssd) {
// We rewrite the description we're given based on some new chars per
// line.
moveDesc = moveDesc.replace("-" + newline, "").replace(newline, " ");
// Keep spatk/spdef as one word on one line
moveDesc = moveDesc.replace("Sp. Atk", "Sp__Atk");
moveDesc = moveDesc.replace("Sp. Def", "Sp__Def");
moveDesc = moveDesc.replace("SP. ATK", "SP__ATK");
moveDesc = moveDesc.replace("SP. DEF", "SP__DEF");
String[] words = moveDesc.split(" ");
StringBuilder fullDesc = new StringBuilder();
StringBuilder thisLine = new StringBuilder();
int currLineWC = 0;
int currLineCC = 0;
int linesWritten = 0;
for (int i = 0; i < words.length; i++) {
// Reverse the spatk/spdef preservation from above
words[i] = words[i].replace("SP__", "SP. ");
words[i] = words[i].replace("Sp__", "Sp. ");
int reqLength = ssd.lengthFor(words[i]);
if (currLineWC > 0) {
reqLength++;
}
if (currLineCC + reqLength <= lineSize) {
// add to current line
if (currLineWC > 0) {
thisLine.append(' ');
}
thisLine.append(words[i]);
currLineWC++;
currLineCC += reqLength;
} else {
// Save current line, if applicable
if (currLineWC > 0) {
if (linesWritten > 0) {
fullDesc.append(newline);
}
fullDesc.append(thisLine.toString());
linesWritten++;
thisLine = new StringBuilder();
}
// Start the new line
thisLine.append(words[i]);
currLineWC = 1;
currLineCC = ssd.lengthFor(words[i]);
}
}
// If the last line has anything add it
if (currLineWC > 0) {
if (linesWritten > 0) {
fullDesc.append(newline);
}
fullDesc.append(thisLine.toString());
}
return fullDesc.toString();
}
public static String formatTextWithReplacements(String text, Map replacements, String newline,
String extraline, String newpara, int maxLineLength, StringSizeDeterminer ssd) {
// Ends with a paragraph indicator?
boolean endsWithPara = false;
if (text.endsWith(newpara)) {
endsWithPara = true;
text = text.substring(0, text.length() - newpara.length());
}
// Replace current line endings with spaces
text = text.replace(newline, " ").replace(extraline, " ");
// Replace words if replacements are set
// do it in two stages so the rules don't conflict
if (replacements != null) {
int index = 0;
for (Map.Entry toReplace : replacements.entrySet()) {
index++;
text = text.replace(toReplace.getKey(), "");
}
index = 0;
for (Map.Entry toReplace : replacements.entrySet()) {
index++;
text = text.replace("", toReplace.getValue());
}
}
// Split on paragraphs and deal with each one individually
String[] oldParagraphs = text.split(newpara.replace("\\", "\\\\"));
StringBuilder finalResult = new StringBuilder();
int sentenceNewLineSize = Math.max(10, maxLineLength / 2);
for (int para = 0; para < oldParagraphs.length; para++) {
String[] words = oldParagraphs[para].split(" ");
StringBuilder fullPara = new StringBuilder();
StringBuilder thisLine = new StringBuilder();
int currLineWC = 0;
int currLineCC = 0;
int linesWritten = 0;
char currLineLastChar = 0;
for (String word : words) {
int reqLength = ssd.lengthFor(word);
if (currLineWC > 0) {
reqLength++;
}
if ((currLineCC + reqLength > maxLineLength)
|| (currLineCC >= sentenceNewLineSize && (currLineLastChar == '.' || currLineLastChar == '?'
|| currLineLastChar == '!' || currLineLastChar == '…' || currLineLastChar == ','))) {
// new line
// Save current line, if applicable
if (currLineWC > 0) {
if (linesWritten > 1) {
fullPara.append(extraline);
} else if (linesWritten == 1) {
fullPara.append(newline);
}
fullPara.append(thisLine.toString());
linesWritten++;
thisLine = new StringBuilder();
}
// Start the new line
thisLine.append(word);
currLineWC = 1;
currLineCC = ssd.lengthFor(word);
if (word.length() == 0) {
currLineLastChar = 0;
} else {
currLineLastChar = word.charAt(word.length() - 1);
}
} else {
// add to current line
if (currLineWC > 0) {
thisLine.append(' ');
}
thisLine.append(word);
currLineWC++;
currLineCC += reqLength;
if (word.length() == 0) {
currLineLastChar = 0;
} else {
currLineLastChar = word.charAt(word.length() - 1);
}
}
}
// If the last line has anything add it
if (currLineWC > 0) {
if (linesWritten > 1) {
fullPara.append(extraline);
} else if (linesWritten == 1) {
fullPara.append(newline);
}
fullPara.append(thisLine.toString());
}
if (para > 0) {
finalResult.append(newpara);
}
finalResult.append(fullPara.toString());
}
if (endsWithPara) {
finalResult.append(newpara);
}
return finalResult.toString();
}
public interface StringSizeDeterminer {
int lengthFor(String encodedText);
}
public static class StringLengthSD implements StringSizeDeterminer {
@Override
public int lengthFor(String encodedText) {
return encodedText.length();
}
}
}