mirror of
https://github.com/kuroppoi/entralinked.git
synced 2026-03-21 17:24:40 -05:00
This commit is contained in:
parent
ff8615401e
commit
356900d591
|
|
@ -30,6 +30,8 @@ dependencies {
|
|||
implementation 'com.formdev:flatlaf:3.1.1'
|
||||
implementation 'com.formdev:flatlaf-extras:3.1.1'
|
||||
implementation 'com.formdev:flatlaf-intellij-themes:3.1.1'
|
||||
implementation 'com.miglayout:miglayout-swing:4.2' // Finally, a good fucking layout manager.
|
||||
implementation 'org.swinglabs.swingx:swingx-autocomplete:1.6.5-1'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import org.apache.logging.log4j.Logger;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import entralinked.gui.MainView;
|
||||
import entralinked.gui.view.MainView;
|
||||
import entralinked.model.dlc.DlcList;
|
||||
import entralinked.model.player.PlayerManager;
|
||||
import entralinked.model.user.UserManager;
|
||||
|
|
@ -119,7 +119,6 @@ public class Entralinked {
|
|||
|
||||
if(mainView != null) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
mainView.setDashboardButtonEnabled(true);
|
||||
mainView.setStatusLabelText("Configure your DS to use the following DNS server: %s".formatted(hostIpAddress));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
public enum GameVersion {
|
||||
|
||||
|
||||
// ==================================
|
||||
// Black Version & White Version
|
||||
// ==================================
|
||||
|
|
@ -29,21 +29,35 @@ public enum GameVersion {
|
|||
// Black Version 2 & White Version 2
|
||||
// ==================================
|
||||
|
||||
BLACK_2_JAPANESE(23, 1, "IREJ", "ブラック2", true),
|
||||
BLACK_2_ENGLISH(23, 2, "IREO", "Black Version 2", true),
|
||||
BLACK_2_FRENCH(23, 3, "IREF", "Version Noire 2", true),
|
||||
BLACK_2_ITALIAN(23, 4, "IREI", "Versione Nera 2", true),
|
||||
BLACK_2_GERMAN(23, 5, "IRED", "Schwarze Edition 2", true),
|
||||
BLACK_2_SPANISH(23, 7, "IRES", "Edicion Negra 2", true),
|
||||
BLACK_2_KOREAN(23, 8, "IREK", "블랙2", true),
|
||||
BLACK_2_JAPANESE(23, 1, "IREJ", "ブラック2"),
|
||||
BLACK_2_ENGLISH(23, 2, "IREO", "Black Version 2"),
|
||||
BLACK_2_FRENCH(23, 3, "IREF", "Version Noire 2"),
|
||||
BLACK_2_ITALIAN(23, 4, "IREI", "Versione Nera 2"),
|
||||
BLACK_2_GERMAN(23, 5, "IRED", "Schwarze Edition 2"),
|
||||
BLACK_2_SPANISH(23, 7, "IRES", "Edicion Negra 2"),
|
||||
BLACK_2_KOREAN(23, 8, "IREK", "블랙2"),
|
||||
|
||||
WHITE_2_JAPANESE(22, 1, "IRDJ", "ホワイト2", true),
|
||||
WHITE_2_ENGLISH(22, 2, "IRDO", "White Version 2", true),
|
||||
WHITE_2_FRENCH(22, 3, "IRDF", "Version Blanche 2", true),
|
||||
WHITE_2_ITALIAN(22, 4, "IRDI", "Versione Bianca 2", true),
|
||||
WHITE_2_GERMAN(22, 5, "IRDD", "Weisse Edition 2", true),
|
||||
WHITE_2_SPANISH(22, 7, "IRDS", "Edicion Blanca 2", true),
|
||||
WHITE_2_KOREAN(22, 8, "IRDK", "화이트2", true);
|
||||
WHITE_2_JAPANESE(22, 1, "IRDJ", "ホワイト2"),
|
||||
WHITE_2_ENGLISH(22, 2, "IRDO", "White Version 2"),
|
||||
WHITE_2_FRENCH(22, 3, "IRDF", "Version Blanche 2"),
|
||||
WHITE_2_ITALIAN(22, 4, "IRDI", "Versione Bianca 2"),
|
||||
WHITE_2_GERMAN(22, 5, "IRDD", "Weisse Edition 2"),
|
||||
WHITE_2_SPANISH(22, 7, "IRDS", "Edicion Blanca 2"),
|
||||
WHITE_2_KOREAN(22, 8, "IRDK", "화이트2");
|
||||
|
||||
// Masks
|
||||
public static final int BW_MASK = 0b110011111111;
|
||||
public static final int B2W2_MASK = 0b001111111111;
|
||||
public static final int ALL_MASK = BW_MASK | B2W2_MASK;
|
||||
public static final int JAP_MASK = 0b111100000001;
|
||||
public static final int ENG_MASK = 0b111100000010;
|
||||
public static final int FRE_MASK = 0b111100000100;
|
||||
public static final int ITA_MASK = 0b111100001000;
|
||||
public static final int GER_MASK = 0b111100010000;
|
||||
public static final int SPA_MASK = 0b111101000000;
|
||||
public static final int KOR_MASK = 0b111110000000;
|
||||
public static final int JAP_KOR_MASK = JAP_MASK | KOR_MASK;
|
||||
public static final int NA_EUR_MASK = ENG_MASK | FRE_MASK | ITA_MASK | GER_MASK | SPA_MASK;
|
||||
|
||||
// Lookup maps
|
||||
private static final Map<String, GameVersion> mapBySerial = new HashMap<>();
|
||||
|
|
@ -52,7 +66,7 @@ public enum GameVersion {
|
|||
static {
|
||||
for(GameVersion version : values()) {
|
||||
mapBySerial.put(version.getSerial(), version);
|
||||
mapByCodes.put(version.getRomCode() << version.getLanguageCode(), version);
|
||||
mapByCodes.put(version.getBits(), version);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,18 +74,12 @@ public enum GameVersion {
|
|||
private final int languageCode; // Values are not tested
|
||||
private final String serial;
|
||||
private final String displayName;
|
||||
private final boolean isVersion2;
|
||||
|
||||
private GameVersion(int romCode, int languageCode, String serial, String displayName, boolean isVersion2) {
|
||||
private GameVersion(int romCode, int languageCode, String serial, String displayName) {
|
||||
this.romCode = romCode;
|
||||
this.languageCode = languageCode;
|
||||
this.serial = serial;
|
||||
this.displayName = displayName;
|
||||
this.isVersion2 = isVersion2;
|
||||
}
|
||||
|
||||
private GameVersion(int romCode, int languageCode, String serial, String displayName) {
|
||||
this(romCode, languageCode, serial, displayName, false);
|
||||
}
|
||||
|
||||
public static GameVersion lookup(String serial) {
|
||||
|
|
@ -79,7 +87,11 @@ public enum GameVersion {
|
|||
}
|
||||
|
||||
public static GameVersion lookup(int romCode, int languageCode) {
|
||||
return mapByCodes.get(romCode << languageCode);
|
||||
return mapByCodes.get(getBits(romCode, languageCode));
|
||||
}
|
||||
|
||||
private static int getBits(int romCode, int languageCode) {
|
||||
return (1 << (8 - (romCode - 23))) | (1 << languageCode - 1) & 0b111111111111;
|
||||
}
|
||||
|
||||
public int getRomCode() {
|
||||
|
|
@ -99,6 +111,15 @@ public enum GameVersion {
|
|||
}
|
||||
|
||||
public boolean isVersion2() {
|
||||
return isVersion2;
|
||||
return checkMask(B2W2_MASK);
|
||||
}
|
||||
|
||||
public boolean checkMask(int mask) {
|
||||
int bits = getBits();
|
||||
return (bits & mask) == bits;
|
||||
}
|
||||
|
||||
public int getBits() {
|
||||
return getBits(romCode, languageCode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
src/main/java/entralinked/gui/FileChooser.java
Normal file
55
src/main/java/entralinked/gui/FileChooser.java
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package entralinked.gui;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
|
||||
public class FileChooser {
|
||||
|
||||
private static final JFileChooser fileChooser = new JFileChooser(".");
|
||||
|
||||
public static void showFileOpenDialog(Component parent, Consumer<FileSelection> handler) {
|
||||
showFileOpenDialog(parent, Collections.emptyList(), handler);
|
||||
}
|
||||
|
||||
public static void showFileOpenDialog(Component parent, FileFilter fileFilter, Consumer<FileSelection> handler) {
|
||||
showFileOpenDialog(parent, List.of(fileFilter), handler);
|
||||
}
|
||||
|
||||
public static void showFileOpenDialog(Component parent, List<FileFilter> fileFilters, Consumer<FileSelection> handler) {
|
||||
showDialog(parent, fileFilters, fileChooser::showOpenDialog, handler);
|
||||
}
|
||||
|
||||
private static void showDialog(Component parent, List<FileFilter> fileFilters, Function<Component, Integer> dialogFunction, Consumer<FileSelection> handler) {
|
||||
FileFilter currentFilter = fileChooser.getFileFilter();
|
||||
fileChooser.resetChoosableFileFilters();
|
||||
fileChooser.setAcceptAllFileFilterUsed(fileFilters.isEmpty());
|
||||
fileFilters.forEach(fileChooser::addChoosableFileFilter);
|
||||
|
||||
if(fileFilters.contains(currentFilter)) {
|
||||
fileChooser.setFileFilter(currentFilter);
|
||||
}
|
||||
|
||||
if(dialogFunction.apply(parent) == JFileChooser.APPROVE_OPTION) {
|
||||
File file = fileChooser.getSelectedFile();
|
||||
handler.accept(new FileSelection(file, fileChooser.getFileFilter(), getFileExtension(file)));
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFileExtension(File file) {
|
||||
String name = file.getName();
|
||||
int index = name.lastIndexOf('.');
|
||||
|
||||
if(index == -1 || index + 1 == name.length()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return name.substring(index + 1).toLowerCase();
|
||||
}
|
||||
}
|
||||
7
src/main/java/entralinked/gui/FileSelection.java
Normal file
7
src/main/java/entralinked/gui/FileSelection.java
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package entralinked.gui;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
|
||||
public record FileSelection(File file, FileFilter filter, String extension) {}
|
||||
60
src/main/java/entralinked/gui/ImageLoader.java
Normal file
60
src/main/java/entralinked/gui/ImageLoader.java
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package entralinked.gui;
|
||||
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import entralinked.gui.data.DataManager;
|
||||
|
||||
public class ImageLoader {
|
||||
|
||||
private static final Map<String, BufferedImage> cache = new HashMap<>();
|
||||
|
||||
public static BufferedImage getImage(String path) {
|
||||
return cache.computeIfAbsent(path, ImageLoader::loadImage);
|
||||
}
|
||||
|
||||
private static BufferedImage loadImage(String path) {
|
||||
try {
|
||||
return ImageIO.read(DataManager.class.getResource(path));
|
||||
} catch(Exception e) {
|
||||
BufferedImage image = new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
graphics.drawString("IMAGE LOAD ERROR", 0, 16);
|
||||
graphics.drawString("Please report this!", 0, 124);
|
||||
drawWrappedString(graphics, path, 0, 32, image.getWidth());
|
||||
drawWrappedString(graphics, e.getMessage(), 0, 80, image.getWidth());
|
||||
graphics.dispose();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawWrappedString(Graphics2D graphics, String string, int x, int y, int width) {
|
||||
int length = string.length();
|
||||
FontMetrics metrics = graphics.getFontMetrics();
|
||||
String line = "";
|
||||
int currentY = y;
|
||||
|
||||
for(int i = 0; i < length; i++) {
|
||||
char next = string.charAt(i);
|
||||
|
||||
if((!line.isEmpty() && x + metrics.stringWidth(line + next) >= width)) {
|
||||
graphics.drawString(line, x, currentY);
|
||||
line = "";
|
||||
currentY += metrics.getHeight();
|
||||
}
|
||||
|
||||
line += next;
|
||||
|
||||
if(i + 1 == length) {
|
||||
graphics.drawString(line, x, currentY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/main/java/entralinked/gui/InputVerifierCellEditor.java
Normal file
25
src/main/java/entralinked/gui/InputVerifierCellEditor.java
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package entralinked.gui;
|
||||
|
||||
import javax.swing.DefaultCellEditor;
|
||||
import javax.swing.InputVerifier;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class InputVerifierCellEditor extends DefaultCellEditor {
|
||||
|
||||
private final InputVerifier verifier;
|
||||
|
||||
public InputVerifierCellEditor(InputVerifier verifier) {
|
||||
this(verifier, new JTextField());
|
||||
}
|
||||
|
||||
public InputVerifierCellEditor(InputVerifier verifier, JTextField textField) {
|
||||
super(textField);
|
||||
this.verifier = verifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopCellEditing() {
|
||||
return verifier.verify(editorComponent) && super.stopCellEditing();
|
||||
}
|
||||
}
|
||||
38
src/main/java/entralinked/gui/ModelListCellRenderer.java
Normal file
38
src/main/java/entralinked/gui/ModelListCellRenderer.java
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package entralinked.gui;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Font;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.DefaultListCellRenderer;
|
||||
import javax.swing.JList;
|
||||
|
||||
import entralinked.utility.SwingUtility;
|
||||
|
||||
@SuppressWarnings({"serial", "unchecked"})
|
||||
public class ModelListCellRenderer<T> extends DefaultListCellRenderer {
|
||||
|
||||
private final Class<T> type;
|
||||
private final Function<T, String> textSupplier;
|
||||
private final String nullValue;
|
||||
|
||||
public ModelListCellRenderer(Class<T> type, Function<T, String> textSupplier, String nullValue) {
|
||||
this.type = type;
|
||||
this.textSupplier = textSupplier;
|
||||
this.nullValue = nullValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
setText(value == null ? String.valueOf(nullValue) : type.isAssignableFrom(value.getClass()) ? textSupplier.apply((T)value) : String.valueOf(value));
|
||||
Font font = component.getFont();
|
||||
String text = getText();
|
||||
|
||||
if(font.canDisplayUpTo(text) != -1) {
|
||||
component.setFont(SwingUtility.findSupportingFont(text, font));
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
}
|
||||
38
src/main/java/entralinked/gui/ModelTableCellRenderer.java
Normal file
38
src/main/java/entralinked/gui/ModelTableCellRenderer.java
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package entralinked.gui;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Font;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
import entralinked.utility.SwingUtility;
|
||||
|
||||
@SuppressWarnings({"serial", "unchecked"})
|
||||
public class ModelTableCellRenderer<T> extends DefaultTableCellRenderer {
|
||||
|
||||
private final Class<T> type;
|
||||
private final Function<T, String> textSupplier;
|
||||
private final String nullValue;
|
||||
|
||||
public ModelTableCellRenderer(Class<T> type, Function<T, String> textSupplier, String nullValue) {
|
||||
this.type = type;
|
||||
this.textSupplier = textSupplier;
|
||||
this.nullValue = nullValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
setText(value == null ? String.valueOf(nullValue) : type.isAssignableFrom(value.getClass()) ? textSupplier.apply((T)value) : String.valueOf(value));
|
||||
Font font = component.getFont();
|
||||
String text = getText();
|
||||
|
||||
if(font.canDisplayUpTo(text) != -1) {
|
||||
component.setFont(SwingUtility.findSupportingFont(text, font));
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
}
|
||||
229
src/main/java/entralinked/gui/component/ConfigTable.java
Normal file
229
src/main/java/entralinked/gui/component/ConfigTable.java
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
package entralinked.gui.component;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.CellEditor;
|
||||
import javax.swing.DefaultCellEditor;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.table.TableCellEditor;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
|
||||
import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator;
|
||||
import org.jdesktop.swingx.autocomplete.ComboBoxCellEditor;
|
||||
import org.jdesktop.swingx.autocomplete.ObjectToStringConverter;
|
||||
|
||||
import entralinked.gui.ModelListCellRenderer;
|
||||
import entralinked.gui.ModelTableCellRenderer;
|
||||
|
||||
/**
|
||||
* TODO look into custom table models because this current implementation is absolutely ass.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ConfigTable extends JTable {
|
||||
|
||||
private final Map<Integer, TableCellRenderer> tableCellRenderers = new HashMap<>();
|
||||
private final Map<Integer, ListCellRenderer<Object>> listCellRenderers = new HashMap<>();
|
||||
private final Map<Integer, ObjectToStringConverter> converters = new HashMap<>();
|
||||
private TableCellEditor[][] cellEditors;
|
||||
private boolean[][] disabledCells;
|
||||
|
||||
@Override
|
||||
public TableCellEditor getCellEditor(int row, int column) {
|
||||
resizeArrays();
|
||||
return cellEditors[row][column];
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableCellRenderer getCellRenderer(int row, int column) {
|
||||
return tableCellRenderers.getOrDefault(column, super.getCellRenderer(row, column));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
resizeArrays();
|
||||
return !disabledCells[row][column] && cellEditors[row][column] != null;
|
||||
}
|
||||
|
||||
public void setCellEditable(int row, int column, boolean editable) {
|
||||
resizeArrays();
|
||||
disabledCells[row][column] = !editable;
|
||||
}
|
||||
|
||||
public void setCellEditor(int row, int column, TableCellEditor editor) {
|
||||
resizeArrays();
|
||||
cellEditors[row][column] = editor;
|
||||
}
|
||||
|
||||
public <T> void setCellRenderers(int column, Class<T> type, Function<T, String> supplier) {
|
||||
setCellRenderers(column, type, supplier, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> void setCellRenderers(int column, Class<T> type, Function<T, String> supplier, String nullValue) {
|
||||
tableCellRenderers.put(column, new ModelTableCellRenderer<T>(type, supplier, nullValue));
|
||||
listCellRenderers.put(column, new ModelListCellRenderer<T>(type, supplier, nullValue));
|
||||
converters.put(column, new ObjectToStringConverter() {
|
||||
@Override
|
||||
public String getPreferredStringForItem(Object item) {
|
||||
return (item == null || !type.isAssignableFrom(item.getClass())) ? null : supplier.apply((T)item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void enableOption(int row, int column) {
|
||||
if(isCellEditable(row, column)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object initialValue = "N/A";
|
||||
CellEditor editor = getCellEditor(row, column);
|
||||
|
||||
if(editor instanceof DefaultCellEditor) {
|
||||
Component component = ((DefaultCellEditor)editor).getComponent();
|
||||
|
||||
if(component instanceof JComboBox) {
|
||||
JComboBox<?> comboBox = (JComboBox<?>)component;
|
||||
|
||||
if(comboBox.getItemCount() > 0) {
|
||||
initialValue = comboBox.getItemAt(0);
|
||||
}
|
||||
} else if(component instanceof JTextField) {
|
||||
initialValue = "";
|
||||
}
|
||||
}
|
||||
|
||||
enableOption(row, column, initialValue);
|
||||
}
|
||||
|
||||
public void enableOption(int row, int column, Object initialValue) {
|
||||
if(isCellEditable(row, column)) {
|
||||
return;
|
||||
}
|
||||
|
||||
getModel().setValueAt(initialValue, row, column);
|
||||
setCellEditable(row, column, true);
|
||||
}
|
||||
|
||||
public void disableOption(int row, int column) {
|
||||
setCellEditable(row, column, false);
|
||||
getModel().setValueAt("", row, column);
|
||||
}
|
||||
|
||||
public void setOptionsAt(int row, int column, Collection<?> options) {
|
||||
setOptionsAt(row, column, options, false);
|
||||
}
|
||||
|
||||
public void setOptionsAt(int row, int column, Collection<?> options, boolean includeNullOption) {
|
||||
if(options.isEmpty()) {
|
||||
setCellEditor(row, column, null);
|
||||
getModel().setValueAt("N/A", row, column);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<?> vector = new Vector<>(options);
|
||||
|
||||
if(includeNullOption) {
|
||||
vector.add(0, null);
|
||||
}
|
||||
|
||||
Object currentValue = getValueAt(row, column);
|
||||
|
||||
if(currentValue == null || !vector.contains(currentValue)) {
|
||||
currentValue = vector.firstElement();
|
||||
getModel().setValueAt(currentValue, row, column);
|
||||
}
|
||||
|
||||
JComboBox<?> comboBox = new JComboBox<>(vector);
|
||||
AutoCompleteDecorator.decorate(comboBox, converters.getOrDefault(column, ObjectToStringConverter.DEFAULT_IMPLEMENTATION));
|
||||
|
||||
if(listCellRenderers.containsKey(column)) {
|
||||
comboBox.setRenderer(listCellRenderers.get(column));
|
||||
}
|
||||
|
||||
setCellEditor(row, column, new ComboBoxCellEditor(comboBox));
|
||||
}
|
||||
|
||||
public void randomizeSelection(int row, int column) {
|
||||
randomizeSelection(row, column, true);
|
||||
}
|
||||
|
||||
public void randomizeSelection(int row, int column, boolean allowNull) {
|
||||
CellEditor editor = getCellEditor(row, column);
|
||||
|
||||
if(!(editor instanceof DefaultCellEditor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Component component = ((DefaultCellEditor)editor).getComponent();
|
||||
|
||||
if(component instanceof JComboBox) {
|
||||
JComboBox<?> comboBox = (JComboBox<?>)component;
|
||||
int count = comboBox.getItemCount();
|
||||
|
||||
if(count <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = (int)(Math.random() * count);
|
||||
Object item = comboBox.getItemAt(index);
|
||||
|
||||
if(!allowNull) {
|
||||
// TODO potential infinite loop
|
||||
while(item == null) {
|
||||
item = comboBox.getItemAt((int)(Math.random() * count));
|
||||
}
|
||||
}
|
||||
|
||||
getModel().setValueAt(item, row, column);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T getValueAt(int row, int column, Class<T> type) {
|
||||
return getValueAt(row, column, type, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getValueAt(int row, int column, Class<T> type, T def) {
|
||||
Object value = getValueAt(row, column);
|
||||
|
||||
if(value != null && type.isAssignableFrom(value.getClass())) {
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
private void resizeArrays() {
|
||||
int rows = getRowCount();
|
||||
int columns = getColumnCount();
|
||||
|
||||
if(cellEditors == null) {
|
||||
cellEditors = new TableCellEditor[rows][columns];
|
||||
disabledCells = new boolean[rows][columns];
|
||||
return;
|
||||
}
|
||||
|
||||
// Check row size
|
||||
if(rows != cellEditors.length) {
|
||||
cellEditors = Arrays.copyOf(cellEditors, rows);
|
||||
disabledCells = Arrays.copyOf(disabledCells, rows);
|
||||
}
|
||||
|
||||
// Check column size
|
||||
if(rows > 0 && cellEditors[0].length != columns) {
|
||||
for(int i = 0; i < rows; i++) {
|
||||
cellEditors[i] = Arrays.copyOf(cellEditors[i], columns);
|
||||
disabledCells[i] = Arrays.copyOf(disabledCells[i], columns);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
src/main/java/entralinked/gui/component/PropertyDisplay.java
Normal file
90
src/main/java/entralinked/gui/component/PropertyDisplay.java
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package entralinked.gui.component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPasswordField;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class PropertyDisplay extends JPanel {
|
||||
|
||||
private final Map<Integer, String> keys = new HashMap<>();
|
||||
private final Map<String, JLabel> labels = new HashMap<>();
|
||||
private final Map<String, JTextField> fields = new HashMap<>();
|
||||
|
||||
public PropertyDisplay() {
|
||||
setLayout(new MigLayout("insets 0, fill"));
|
||||
}
|
||||
|
||||
public void addProperty(int x, int y, String key, String label) {
|
||||
addProperty(x, y, key, label, false);
|
||||
}
|
||||
|
||||
public void addProperty(int x, int y, String key, String label, boolean hidden) {
|
||||
int coordHash = Objects.hash(x, y);
|
||||
String existingKey = keys.get(coordHash);
|
||||
|
||||
if(existingKey != null) {
|
||||
removeProperty(existingKey);
|
||||
}
|
||||
|
||||
removeProperty(key);
|
||||
String labelConstraints = "cell %s %s, sg proplabel";
|
||||
|
||||
if(x > 0) {
|
||||
labelConstraints += ", gapx 4";
|
||||
}
|
||||
|
||||
JLabel labelComponent = new JLabel(label);
|
||||
labels.put(key, labelComponent);
|
||||
add(labelComponent, labelConstraints.formatted(x * 2, y));
|
||||
add(createValueField(key, hidden), "cell %s %s, sg propfield, width 96".formatted(x * 2 + 1, y));
|
||||
keys.put(coordHash, key);
|
||||
}
|
||||
|
||||
public boolean removeProperty(String key) {
|
||||
if(!labels.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
remove(labels.remove(key));
|
||||
remove(fields.remove(key));
|
||||
keys.values().remove(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setValue(String key, Object value) {
|
||||
JTextField textField = fields.get(key);
|
||||
|
||||
if(textField != null) {
|
||||
textField.setText(String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
private JTextField createValueField(String key, boolean hidden) {
|
||||
JTextField textField = null;
|
||||
|
||||
if(!hidden) {
|
||||
textField = new JTextField();
|
||||
} else {
|
||||
textField = new JPasswordField();
|
||||
textField.putClientProperty(FlatClientProperties.STYLE, "showRevealButton: true; showCapsLock: false");
|
||||
}
|
||||
|
||||
textField.setEditable(false);
|
||||
fields.put(key, textField);
|
||||
return textField;
|
||||
}
|
||||
|
||||
public JTextField getValueField(String key) {
|
||||
return fields.get(key);
|
||||
}
|
||||
}
|
||||
84
src/main/java/entralinked/gui/component/ShadowedSprite.java
Normal file
84
src/main/java/entralinked/gui/component/ShadowedSprite.java
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package entralinked.gui.component;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class ShadowedSprite extends JComponent {
|
||||
|
||||
private BufferedImage image;
|
||||
private BufferedImage shadowImage;
|
||||
private int scale;
|
||||
|
||||
public ShadowedSprite() {
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics graphics) {
|
||||
super.paintComponent(graphics);
|
||||
|
||||
if(image == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dimension size = getSize();
|
||||
int width = image.getWidth() * scale;
|
||||
int height = image.getHeight() * scale;
|
||||
int x = size.width / 2 - width / 2;
|
||||
int y = size.height / 2 - height / 2;
|
||||
graphics.drawImage(shadowImage, x + 4, y + 2, width, height, null);
|
||||
graphics.drawImage(image, x, y, width, height, null);
|
||||
}
|
||||
|
||||
private void imageChanged() {
|
||||
if(image == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
shadowImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
// TODO shadow can be cut off if pixels touch the border
|
||||
for(int i = 0; i < image.getWidth(); i++) {
|
||||
for(int j = 0; j < image.getHeight(); j++) {
|
||||
shadowImage.setRGB(i, j, image.getRGB(i, j) & 0x7F000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSize(int width, int height) {
|
||||
Dimension oldSize = getPreferredSize();
|
||||
Dimension size = new Dimension(width, height);
|
||||
setMinimumSize(size);
|
||||
setPreferredSize(size);
|
||||
|
||||
if(oldSize.width != width || oldSize.height != height) {
|
||||
revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setImage(BufferedImage image) {
|
||||
setImage(image, 1);
|
||||
}
|
||||
|
||||
public void setImage(BufferedImage image, int width, int height) {
|
||||
setImage(image, 1, width, height);
|
||||
}
|
||||
|
||||
public void setImage(BufferedImage image, int scale) {
|
||||
setImage(image, scale, image == null ? 0 : image.getWidth(), image == null ? 0 : image.getHeight());
|
||||
}
|
||||
|
||||
public void setImage(BufferedImage image, int scale, int width, int height) {
|
||||
this.image = image;
|
||||
this.scale = scale;
|
||||
imageChanged();
|
||||
updateSize(width, height);
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
16
src/main/java/entralinked/gui/data/Country.java
Normal file
16
src/main/java/entralinked/gui/data/Country.java
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package entralinked.gui.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public record Country(
|
||||
@JsonProperty(required = true) int id,
|
||||
@JsonProperty(required = true) String name,
|
||||
List<Region> regions) {
|
||||
|
||||
@JsonProperty
|
||||
public boolean hasRegions() {
|
||||
return regions != null && !regions.isEmpty();
|
||||
}
|
||||
}
|
||||
185
src/main/java/entralinked/gui/data/DataManager.java
Normal file
185
src/main/java/entralinked/gui/data/DataManager.java
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
package entralinked.gui.data;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import entralinked.GameVersion;
|
||||
import entralinked.gui.ImageLoader;
|
||||
import entralinked.model.pkmn.PkmnGender;
|
||||
|
||||
/**
|
||||
* TODO name is too generic & model management is a bit confusing.
|
||||
*/
|
||||
public class DataManager {
|
||||
|
||||
private static final Map<Integer, String> abilities = new HashMap<>();
|
||||
private static final Map<Integer, String> items = new HashMap<>();
|
||||
private static final Map<Integer, String> moves = new HashMap<>();
|
||||
private static final Map<Integer, PkmnSpecies> species = new HashMap<>();
|
||||
private static final Map<Integer, Country> countries = new HashMap<>();
|
||||
private static final Map<String, DreamWorldArea> dreamWorldAreas = new HashMap<>();
|
||||
private static final Map<GameVersion, List<Encounter>> encounterCache = new HashMap<>();
|
||||
|
||||
public static void clearData() {
|
||||
abilities.clear();
|
||||
items.clear();
|
||||
moves.clear();
|
||||
species.clear();
|
||||
countries.clear();
|
||||
dreamWorldAreas.clear();
|
||||
encounterCache.clear();
|
||||
}
|
||||
|
||||
public static void loadData() throws IOException {
|
||||
clearData();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
abilities.putAll(mapper.readValue(DataManager.class.getResource("/data/abilities.json"), new TypeReference<Map<Integer, String>>(){}));
|
||||
items.putAll(mapper.readValue(DataManager.class.getResource("/data/items.json"), new TypeReference<Map<Integer, String>>(){}));
|
||||
moves.putAll(mapper.readValue(DataManager.class.getResource("/data/moves.json"), new TypeReference<Map<Integer, String>>(){}));
|
||||
species.putAll(mapper.readValue(DataManager.class.getResource("/data/species.json"), new TypeReference<Map<Integer, PkmnSpecies>>(){}));
|
||||
countries.putAll(mapper.readValue(DataManager.class.getResource("/data/countries.json"), new TypeReference<Map<Integer, Country>>(){}));
|
||||
dreamWorldAreas.putAll(mapper.readValue(DataManager.class.getResource("/data/legality.json"), new TypeReference<Map<String, DreamWorldArea>>(){}));
|
||||
}
|
||||
|
||||
public static BufferedImage getPokemonSprite(int species) {
|
||||
return getPokemonSprite(0, 0, false, false);
|
||||
}
|
||||
|
||||
public static BufferedImage getPokemonSprite(PkmnSpecies species, int form, PkmnGender gender, boolean shiny) {
|
||||
return getPokemonSprite(species, form, gender == PkmnGender.FEMALE, shiny);
|
||||
}
|
||||
|
||||
public static BufferedImage getPokemonSprite(int species, int form, PkmnGender gender, boolean shiny) {
|
||||
return getPokemonSprite(species, form, gender == PkmnGender.FEMALE, shiny);
|
||||
}
|
||||
|
||||
public static BufferedImage getPokemonSprite(int species, int form, boolean female, boolean shiny) {
|
||||
return getPokemonSprite(getSpecies(species), form, female, shiny);
|
||||
}
|
||||
|
||||
public static BufferedImage getPokemonSprite(PkmnSpecies species, int form, boolean female, boolean shiny) {
|
||||
String path = "/sprites/pokemon/normal/0.png";
|
||||
|
||||
if(species != null) {
|
||||
path = "/sprites/pokemon/%s/%s%s%s.png".formatted(
|
||||
shiny ? "shiny" : "normal",
|
||||
species.hasFemaleSprite() && female ? "female/" : "",
|
||||
species.id(), form == 0 ? "" : "-" + form);
|
||||
}
|
||||
|
||||
return ImageLoader.getImage(path);
|
||||
}
|
||||
|
||||
public static BufferedImage getItemSprite(int item) {
|
||||
return ImageLoader.getImage("/sprites/items/%s.png".formatted(item));
|
||||
}
|
||||
|
||||
public static String getAbilityName(int id) {
|
||||
return abilities.getOrDefault(id, "Unknown (#%s)".formatted(id));
|
||||
}
|
||||
|
||||
public static Set<Integer> getAbilityIds() {
|
||||
return Collections.unmodifiableSet(abilities.keySet());
|
||||
}
|
||||
|
||||
public static String getItemName(int id) {
|
||||
return items.getOrDefault(id, "Unknown (#%s)".formatted(id));
|
||||
}
|
||||
|
||||
public static Set<Integer> getItemIds() {
|
||||
return Collections.unmodifiableSet(items.keySet());
|
||||
}
|
||||
|
||||
public static String getMoveName(int id) {
|
||||
return moves.getOrDefault(id, "Unknown (#%s)".formatted(id));
|
||||
}
|
||||
|
||||
public static Set<Integer> getMoveIds() {
|
||||
return Collections.unmodifiableSet(moves.keySet());
|
||||
}
|
||||
|
||||
public static PkmnSpecies getSpecies(int id) {
|
||||
return species.get(id);
|
||||
}
|
||||
|
||||
public static Set<Integer> getSpeciesIds() {
|
||||
return Collections.unmodifiableSet(species.keySet());
|
||||
}
|
||||
|
||||
public static Collection<PkmnSpecies> getSpecies() {
|
||||
return Collections.unmodifiableCollection(species.values());
|
||||
}
|
||||
|
||||
public static Country getCountry(int id) {
|
||||
return countries.get(id);
|
||||
}
|
||||
|
||||
public static Collection<Country> getCountries() {
|
||||
return Collections.unmodifiableCollection(countries.values());
|
||||
}
|
||||
|
||||
// TODO functions might be computationally expensive
|
||||
|
||||
private static List<Encounter> getEncounters(GameVersion gameVersion) {
|
||||
return encounterCache.computeIfAbsent(gameVersion, version -> dreamWorldAreas.values().stream()
|
||||
.map(DreamWorldArea::encounters)
|
||||
.flatMap(List::stream)
|
||||
.filter(x -> x.versionMask() == 0 || version.checkMask(x.versionMask()))
|
||||
.toList());
|
||||
}
|
||||
|
||||
public static List<PkmnSpecies> getDownloadableSpecies(GameVersion gameVersion) {
|
||||
return species.values().stream()
|
||||
.filter(x -> x.downloadable() && (gameVersion.isVersion2() || x.id() <= 493))
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
public static List<PkmnSpecies> getSpeciesOptions(GameVersion gameVersion) {
|
||||
return getEncounters(gameVersion).stream()
|
||||
.map(x -> species.get(x.species()))
|
||||
.distinct()
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
public static List<PkmnGender> getGenderOptions(GameVersion gameVersion, PkmnSpecies species) {
|
||||
return getEncounters(gameVersion).stream()
|
||||
.filter(x -> x.species() == species.id())
|
||||
.map(x -> x.isGenderLocked() ? List.of(x.gender()) : species.getGenders())
|
||||
.flatMap(List::stream)
|
||||
.distinct()
|
||||
.sorted((a, b) -> Integer.compare(a.ordinal(), b.ordinal()))
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
public static List<Integer> getMoveOptions(GameVersion gameVersion, PkmnSpecies species, PkmnGender gender) {
|
||||
return getEncounters(gameVersion).stream()
|
||||
.filter(x -> x.species() == species.id() && (!x.isGenderLocked() || species.gender() == gender))
|
||||
.map(Encounter::moves)
|
||||
.flatMap(List::stream)
|
||||
.distinct()
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
public static List<Integer> getDownloadableItems(GameVersion gameVersion) {
|
||||
return items.keySet().stream().filter(x -> gameVersion.isVersion2() || x <= 626).toList();
|
||||
}
|
||||
|
||||
public static List<Integer> getItemOptions() {
|
||||
return dreamWorldAreas.values().stream()
|
||||
.map(DreamWorldArea::items)
|
||||
.flatMap(List::stream)
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
9
src/main/java/entralinked/gui/data/DreamWorldArea.java
Normal file
9
src/main/java/entralinked/gui/data/DreamWorldArea.java
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package entralinked.gui.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public record DreamWorldArea(
|
||||
@JsonProperty(required = true) List<Encounter> encounters,
|
||||
@JsonProperty(required = true) List<Integer> items) {}
|
||||
19
src/main/java/entralinked/gui/data/Encounter.java
Normal file
19
src/main/java/entralinked/gui/data/Encounter.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package entralinked.gui.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import entralinked.model.pkmn.PkmnGender;
|
||||
|
||||
public record Encounter(
|
||||
@JsonProperty(required = true) int species,
|
||||
@JsonProperty(required = true) List<Integer> moves,
|
||||
PkmnGender gender, int versionMask) {
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isGenderLocked() {
|
||||
return gender != null;
|
||||
}
|
||||
}
|
||||
7
src/main/java/entralinked/gui/data/PkmnForm.java
Normal file
7
src/main/java/entralinked/gui/data/PkmnForm.java
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package entralinked.gui.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public record PkmnForm(
|
||||
@JsonProperty(required = true) int id,
|
||||
@JsonProperty(required = true) String name) {}
|
||||
29
src/main/java/entralinked/gui/data/PkmnSpecies.java
Normal file
29
src/main/java/entralinked/gui/data/PkmnSpecies.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package entralinked.gui.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import entralinked.model.pkmn.PkmnGender;
|
||||
|
||||
public record PkmnSpecies(
|
||||
@JsonProperty(required = true) int id,
|
||||
@JsonProperty(required = true) String name,
|
||||
boolean downloadable, boolean hasFemaleSprite, PkmnGender gender, PkmnForm[] forms) {
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isSingleGender() {
|
||||
return gender != null;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean hasForms() {
|
||||
return forms != null && forms.length > 0;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<PkmnGender> getGenders() {
|
||||
return isSingleGender() ? List.of(gender) : List.of(PkmnGender.MALE, PkmnGender.FEMALE);
|
||||
}
|
||||
}
|
||||
7
src/main/java/entralinked/gui/data/Region.java
Normal file
7
src/main/java/entralinked/gui/data/Region.java
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package entralinked.gui.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public record Region(
|
||||
@JsonProperty(required = true) int id,
|
||||
@JsonProperty(required = true) String name) {}
|
||||
211
src/main/java/entralinked/gui/panels/DashboardPanel.java
Normal file
211
src/main/java/entralinked/gui/panels/DashboardPanel.java
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
package entralinked.gui.panels;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.CardLayout;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.GridLayout;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane;
|
||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane.TabAreaAlignment;
|
||||
import com.formdev.flatlaf.extras.components.FlatTextField;
|
||||
|
||||
import entralinked.Entralinked;
|
||||
import entralinked.GameVersion;
|
||||
import entralinked.gui.data.DataManager;
|
||||
import entralinked.model.player.Player;
|
||||
import entralinked.model.player.PlayerStatus;
|
||||
import entralinked.utility.GsidUtility;
|
||||
import entralinked.utility.SwingUtility;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class DashboardPanel extends JPanel {
|
||||
|
||||
public static final String LOGIN_CARD = "login";
|
||||
public static final String MAIN_CARD = "main";
|
||||
private final Entralinked entralinked;
|
||||
private final JLabel sessionLabel;
|
||||
private final FlatTabbedPane tabbedPane;
|
||||
private CardLayout layout;
|
||||
private SummaryPanel summaryPanel;
|
||||
private EncounterEditorPanel encounterPanel;
|
||||
private ItemEditorPanel itemPanel;
|
||||
private VisitorEditorPanel visitorPanel;
|
||||
private MiscPanel miscPanel;
|
||||
private Player player;
|
||||
private boolean initialized;
|
||||
|
||||
public DashboardPanel(Entralinked entralinked) {
|
||||
this.entralinked = entralinked;
|
||||
|
||||
// Create login labels
|
||||
JLabel loginTitle = new JLabel("Log in");
|
||||
loginTitle.putClientProperty(FlatClientProperties.STYLE, "font:bold +8");
|
||||
JLabel loginDescription = new JLabel("<html>Tuck in a Pokémon and enter your Game Sync ID to continue.<br/>"
|
||||
+ "Your Game Sync ID can be found in 'Game Sync Settings' in the game's main menu.</html>");
|
||||
loginDescription.putClientProperty(FlatClientProperties.STYLE, "[dark]foreground:darken(@foreground,20%)");
|
||||
|
||||
// Create GSID field
|
||||
FlatTextField gsidTextField = new FlatTextField();
|
||||
gsidTextField.setPlaceholderText("XXXXXXXXXX");
|
||||
|
||||
// Create login button
|
||||
JButton loginButton = new JButton("Log in");
|
||||
loginButton.addActionListener(event -> login(gsidTextField.getText()));
|
||||
|
||||
// Create browser dashboard button
|
||||
JLabel browserButton = SwingUtility.createButtonLabel("Browser dashboard (Legacy)", () -> {
|
||||
try {
|
||||
Desktop.getDesktop().browse(new URL("http://127.0.0.1/dashboard/profile.html").toURI());
|
||||
} catch(IOException | URISyntaxException e) {
|
||||
SwingUtility.showExceptionInfo(getRootPane(), "Failed to open URL.", e);
|
||||
}
|
||||
});
|
||||
|
||||
// Create login panel
|
||||
JPanel loginPanel = new JPanel(new MigLayout("wrap, align 50% 50%"));
|
||||
loginPanel.add(loginTitle);
|
||||
loginPanel.add(loginDescription);
|
||||
loginPanel.add(new JLabel("Game Sync ID"), "gapy 8");
|
||||
loginPanel.add(gsidTextField, "growx");
|
||||
loginPanel.add(loginButton, "growx");
|
||||
loginPanel.add(browserButton, "gapy 8, align 100%");
|
||||
|
||||
// Create save button
|
||||
JButton saveButton = new JButton("Save profile");
|
||||
saveButton.addActionListener(event -> {
|
||||
// Try to save data
|
||||
// TODO this is probably not thread-safe!!!
|
||||
try {
|
||||
encounterPanel.saveProfile(player);
|
||||
itemPanel.saveProfile(player);
|
||||
visitorPanel.saveProfile(player);
|
||||
miscPanel.saveProfile(player);
|
||||
player.setStatus(PlayerStatus.WAKE_READY);
|
||||
|
||||
if(!entralinked.getPlayerManager().savePlayer(player)) {
|
||||
JOptionPane.showMessageDialog(getRootPane(), "Failed to write player data to disk.", "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
JOptionPane.showMessageDialog(getRootPane(), "Profile data has been saved successfully!\n"
|
||||
+ "Use Game Sync to wake up your Pokémon and download your selected content.");
|
||||
} catch(Exception e) {
|
||||
SwingUtility.showExceptionInfo(getRootPane(), "Failed to save player data.", e);
|
||||
}
|
||||
});
|
||||
|
||||
// Create log out button
|
||||
JButton logoutButton = new JButton("Log out");
|
||||
logoutButton.addActionListener(event -> {
|
||||
gsidTextField.setText("");
|
||||
layout.show(this, LOGIN_CARD);
|
||||
});
|
||||
|
||||
// Create main footer button panel
|
||||
JPanel buttonPanel = new JPanel(new GridLayout(1, 2));
|
||||
buttonPanel.add(saveButton);
|
||||
buttonPanel.add(logoutButton);
|
||||
|
||||
// Create main footer panel
|
||||
sessionLabel = new JLabel("", JLabel.CENTER);
|
||||
JPanel footerPanel = new JPanel(new BorderLayout());
|
||||
footerPanel.add(sessionLabel);
|
||||
footerPanel.add(buttonPanel, BorderLayout.LINE_END);
|
||||
|
||||
// Create main panel
|
||||
tabbedPane = new FlatTabbedPane();
|
||||
tabbedPane.setTabPlacement(JTabbedPane.LEFT);
|
||||
tabbedPane.setTabAreaAlignment(TabAreaAlignment.center);
|
||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
mainPanel.add(tabbedPane);
|
||||
mainPanel.add(footerPanel, BorderLayout.PAGE_END);
|
||||
|
||||
// Create layout
|
||||
layout = new CardLayout();
|
||||
setLayout(layout);
|
||||
add(LOGIN_CARD, loginPanel);
|
||||
add(MAIN_CARD, mainPanel);
|
||||
}
|
||||
|
||||
private void login(String gameSyncId) {
|
||||
// Check if GSID is valid
|
||||
if(!GsidUtility.isValidGameSyncId(gameSyncId)) {
|
||||
JOptionPane.showMessageDialog(this, "Please enter a valid Game Sync ID.", "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = entralinked.getPlayerManager().getPlayer(gameSyncId);
|
||||
|
||||
// Check if player exists
|
||||
if(player == null) {
|
||||
JOptionPane.showMessageDialog(this, "This Game Sync ID does not exist.\n"
|
||||
+ "If you haven't already, please tuck in a Pokémon first.", "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
GameVersion version = player.getGameVersion();
|
||||
|
||||
// Check if necessary data is present
|
||||
if(player.getDreamerInfo() == null || version == null) {
|
||||
JOptionPane.showMessageDialog(this, "Please use Game Sync to tuck in a Pokémon first.", "Attention", JOptionPane.INFORMATION_MESSAGE);
|
||||
player = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to initialize dashboard
|
||||
if(!initialized && !initializeDashboard()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load player data
|
||||
sessionLabel.setText("Game Version: %s, Game Sync ID: %s".formatted(version.getDisplayName(), gameSyncId));
|
||||
tabbedPane.setEnabledAt(3, version.isVersion2());
|
||||
|
||||
try {
|
||||
summaryPanel.loadProfile(player);
|
||||
encounterPanel.loadProfile(player);
|
||||
itemPanel.loadProfile(player);
|
||||
visitorPanel.loadProfile(player);
|
||||
miscPanel.loadProfile(player);
|
||||
} catch(Exception e) {
|
||||
SwingUtility.showExceptionInfo(getRootPane(), "Failed to load player data.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
this.player = player;
|
||||
layout.show(this, MAIN_CARD);
|
||||
}
|
||||
|
||||
private boolean initializeDashboard() {
|
||||
try {
|
||||
DataManager.loadData();
|
||||
summaryPanel = new SummaryPanel();
|
||||
encounterPanel = new EncounterEditorPanel();
|
||||
itemPanel = new ItemEditorPanel();
|
||||
visitorPanel = new VisitorEditorPanel();
|
||||
miscPanel = new MiscPanel(entralinked);
|
||||
} catch(Exception e) {
|
||||
SwingUtility.showExceptionInfo(getRootPane(), "Failed to initialize dashboard.", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
tabbedPane.add("Summary", summaryPanel);
|
||||
tabbedPane.add("Entree Forest", encounterPanel);
|
||||
tabbedPane.add("Dream Remnants", itemPanel);
|
||||
tabbedPane.add("Join Avenue", visitorPanel);
|
||||
tabbedPane.add("Miscellaneous", miscPanel);
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
210
src/main/java/entralinked/gui/panels/EncounterEditorPanel.java
Normal file
210
src/main/java/entralinked/gui/panels/EncounterEditorPanel.java
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
package entralinked.gui.panels;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import entralinked.GameVersion;
|
||||
import entralinked.gui.data.DataManager;
|
||||
import entralinked.gui.data.PkmnForm;
|
||||
import entralinked.gui.data.PkmnSpecies;
|
||||
import entralinked.model.pkmn.PkmnGender;
|
||||
import entralinked.model.player.DreamAnimation;
|
||||
import entralinked.model.player.DreamEncounter;
|
||||
import entralinked.model.player.Player;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class EncounterEditorPanel extends TableEditorPanel {
|
||||
|
||||
public static final int ENCOUNTER_COUNT = 10;
|
||||
public static final int SPECIES_COLUMN = 1;
|
||||
public static final int MOVE_COLUMN = 2;
|
||||
public static final int FORM_COLUMN = 3;
|
||||
public static final int GENDER_COLUMN = 4;
|
||||
public static final int ANIMATION_COLUMN = 5;
|
||||
private static final String[] COLUMN_NAMES = { "Species", "Move", "Form", "Gender", "Animation" };
|
||||
private static final Class<?>[] COLUMN_TYPES = { PkmnSpecies.class, Integer.class, PkmnForm.class, PkmnGender.class, DreamAnimation.class };
|
||||
private GameVersion gameVersion;
|
||||
private boolean optionLock; // Used to prevent double option updates
|
||||
|
||||
public EncounterEditorPanel() {
|
||||
super(ENCOUNTER_COUNT, COLUMN_NAMES, COLUMN_TYPES);
|
||||
setTitle("Entree Forest");
|
||||
setDescription("""
|
||||
<html>
|
||||
Use the table below to configure Entree Forest encounters.<br/>
|
||||
Up to 10 different encounters can be configured at a time.<br/>
|
||||
Please be aware that Unova Pokémon are exclusive to B2W2.
|
||||
</html>
|
||||
""");
|
||||
table.setCellRenderers(SPECIES_COLUMN, PkmnSpecies.class, PkmnSpecies::name, "— — — — —");
|
||||
table.setCellRenderers(MOVE_COLUMN, Integer.class, DataManager::getMoveName, "None");
|
||||
table.setCellRenderers(FORM_COLUMN, PkmnForm.class, PkmnForm::name);
|
||||
table.setCellRenderers(GENDER_COLUMN, PkmnGender.class, PkmnGender::getDisplayName);
|
||||
table.setCellRenderers(ANIMATION_COLUMN, DreamAnimation.class, DreamAnimation::getDisplayName);
|
||||
initializeOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataChanged(int row, int column, Object oldValue, Object newValue) {
|
||||
if(optionLock) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(column == SPECIES_COLUMN) {
|
||||
if(newValue == null) {
|
||||
disableSecondaryOptions(row);
|
||||
return;
|
||||
}
|
||||
|
||||
optionLock = true;
|
||||
table.enableOption(row, GENDER_COLUMN);
|
||||
table.enableOption(row, MOVE_COLUMN);
|
||||
table.enableOption(row, FORM_COLUMN);
|
||||
table.enableOption(row, ANIMATION_COLUMN);
|
||||
PkmnSpecies species = (PkmnSpecies)newValue;
|
||||
updateGenderOptions(row, species);
|
||||
updateFormOptions(row, species);
|
||||
updateMoveOptions(row, species);
|
||||
optionLock = false;
|
||||
} else if(column == GENDER_COLUMN) {
|
||||
if(!isLegalMode()) {
|
||||
return; // Gender only affects move options if legal mode is enabled
|
||||
}
|
||||
|
||||
if(newValue == null) {
|
||||
table.disableOption(row, MOVE_COLUMN);
|
||||
return;
|
||||
}
|
||||
|
||||
updateMoveOptions(row, getSpecies(row));
|
||||
|
||||
if(oldValue == null) {
|
||||
table.enableOption(row, MOVE_COLUMN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void legalModeChanged() {
|
||||
for(int i = 0; i < ENCOUNTER_COUNT; i++) {
|
||||
updateSpeciesOptions(i);
|
||||
PkmnSpecies species = getSpecies(i);
|
||||
|
||||
if(species != null) {
|
||||
optionLock = true;
|
||||
updateGenderOptions(i, species);
|
||||
updateFormOptions(i, species);
|
||||
updateMoveOptions(i, species);
|
||||
optionLock = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeOptions(int row) {
|
||||
setOptions(row, ANIMATION_COLUMN, Arrays.asList(DreamAnimation.values()));
|
||||
disableSecondaryOptions(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomizeSelections(int row) {
|
||||
table.randomizeSelection(row, SPECIES_COLUMN, false);
|
||||
table.randomizeSelection(row, GENDER_COLUMN);
|
||||
table.randomizeSelection(row, FORM_COLUMN);
|
||||
table.randomizeSelection(row, MOVE_COLUMN);
|
||||
table.randomizeSelection(row, ANIMATION_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSelections(int row) {
|
||||
model.setValueAt(null, row, SPECIES_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getSelectionIcon(int row) {
|
||||
return row == -1 ? DataManager.getPokemonSprite(0) : DataManager.getPokemonSprite(getSpecies(row), getForm(row), getGender(row), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldUpdateSelectionIcon(int column) {
|
||||
return column == SPECIES_COLUMN || column == GENDER_COLUMN || column == FORM_COLUMN;
|
||||
}
|
||||
|
||||
private void updateSpeciesOptions(int row) {
|
||||
setOptions(row, SPECIES_COLUMN,
|
||||
isLegalMode() ? DataManager.getSpeciesOptions(gameVersion) : DataManager.getDownloadableSpecies(gameVersion),
|
||||
(a, b) -> a.name().compareTo(b.name()), true);
|
||||
}
|
||||
|
||||
private void updateMoveOptions(int row, PkmnSpecies species) {
|
||||
setOptions(row, MOVE_COLUMN,
|
||||
isLegalMode() ? DataManager.getMoveOptions(gameVersion, species, getGender(row)) : DataManager.getMoveIds(),
|
||||
(a, b) -> DataManager.getMoveName(a).compareTo(DataManager.getMoveName(b)), true);
|
||||
}
|
||||
|
||||
private void updateGenderOptions(int row, PkmnSpecies species) {
|
||||
setOptions(row, GENDER_COLUMN, isLegalMode() ? DataManager.getGenderOptions(gameVersion, species) : species.getGenders());
|
||||
}
|
||||
|
||||
private void updateFormOptions(int row, PkmnSpecies species) {
|
||||
setOptions(row, FORM_COLUMN, species.hasForms() ? Arrays.asList(species.forms()) : Collections.emptyList());
|
||||
}
|
||||
|
||||
public void loadProfile(Player player) {
|
||||
gameVersion = player.getGameVersion();
|
||||
table.clearSelection();
|
||||
clearSelections();
|
||||
legalModeToggle.setSelected(false);
|
||||
legalModeChanged();
|
||||
int row = 0;
|
||||
|
||||
for(DreamEncounter encounter : player.getEncounters()) {
|
||||
PkmnSpecies species = DataManager.getSpecies(encounter.species());
|
||||
model.setValueAt(species, row, SPECIES_COLUMN);
|
||||
model.setValueAt(encounter.move() == 0 ? null : encounter.move(), row, MOVE_COLUMN);
|
||||
model.setValueAt(encounter.gender(), row, GENDER_COLUMN);
|
||||
model.setValueAt(encounter.animation(), row, ANIMATION_COLUMN);
|
||||
|
||||
if(species.hasForms()) {
|
||||
model.setValueAt(species.forms()[encounter.form()], row, FORM_COLUMN);
|
||||
}
|
||||
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
public void saveProfile(Player player) {
|
||||
player.setEncounters(computeSelectionList(row ->
|
||||
new DreamEncounter(getSpecies(row).id(), getMove(row), getForm(row), getGender(row), getAnimation(row)),
|
||||
row -> getSpecies(row) != null));
|
||||
}
|
||||
|
||||
private void disableSecondaryOptions(int row) {
|
||||
table.disableOption(row, MOVE_COLUMN);
|
||||
table.disableOption(row, FORM_COLUMN);
|
||||
table.disableOption(row, GENDER_COLUMN);
|
||||
table.disableOption(row, ANIMATION_COLUMN);
|
||||
}
|
||||
|
||||
private PkmnSpecies getSpecies(int row) {
|
||||
return table.getValueAt(row, SPECIES_COLUMN, PkmnSpecies.class);
|
||||
}
|
||||
|
||||
private int getMove(int row) {
|
||||
return table.getValueAt(row, MOVE_COLUMN, Integer.class, 0);
|
||||
}
|
||||
|
||||
private int getForm(int row) {
|
||||
PkmnForm form = table.getValueAt(row, FORM_COLUMN, PkmnForm.class);
|
||||
return form == null ? 0 : form.id();
|
||||
}
|
||||
|
||||
private PkmnGender getGender(int row) {
|
||||
return table.getValueAt(row, GENDER_COLUMN, PkmnGender.class);
|
||||
}
|
||||
|
||||
private DreamAnimation getAnimation(int row) {
|
||||
return table.getValueAt(row, ANIMATION_COLUMN, DreamAnimation.class);
|
||||
}
|
||||
}
|
||||
132
src/main/java/entralinked/gui/panels/ItemEditorPanel.java
Normal file
132
src/main/java/entralinked/gui/panels/ItemEditorPanel.java
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
package entralinked.gui.panels;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import entralinked.GameVersion;
|
||||
import entralinked.gui.data.DataManager;
|
||||
import entralinked.model.player.DreamItem;
|
||||
import entralinked.model.player.Player;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class ItemEditorPanel extends TableEditorPanel {
|
||||
|
||||
public static final int ITEM_COUNT = 20;
|
||||
public static final int MAX_ITEM_QUANTITY = 20;
|
||||
public static final int ITEM_COLUMN = 1;
|
||||
public static final int QUANTITY_COLUMN = 2;
|
||||
private static final String[] COLUMN_NAMES = { "Item", "Quantity" };
|
||||
private static final Class<?>[] COLUMN_TYPES = { Integer.class, Integer.class };
|
||||
private GameVersion gameVersion;
|
||||
|
||||
public ItemEditorPanel() {
|
||||
super(ITEM_COUNT, COLUMN_NAMES, COLUMN_TYPES);
|
||||
setTitle("Dream Remnants");
|
||||
setDescription("""
|
||||
<html>
|
||||
Use the table below to select the items you want to receive.<br/>
|
||||
After waking up your Pokémon, talk to the boy near the<br/>
|
||||
Entree Forest entrance in the Entralink to receive your items.
|
||||
</html>
|
||||
""");
|
||||
table.setCellRenderers(ITEM_COLUMN, Integer.class, DataManager::getItemName, "— — — — —");
|
||||
initializeOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataChanged(int row, int column, Object oldValue, Object newValue) {
|
||||
if(column != ITEM_COLUMN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(newValue != null) {
|
||||
if(oldValue != null) {
|
||||
return; // Do nothing if new and old values are both non-null
|
||||
}
|
||||
|
||||
table.enableOption(row, QUANTITY_COLUMN);
|
||||
return;
|
||||
}
|
||||
|
||||
table.disableOption(row, QUANTITY_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void legalModeChanged() {
|
||||
for(int i = 0; i < ITEM_COUNT; i++) {
|
||||
updateItemOptions(i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeOptions(int row) {
|
||||
setOptions(row, QUANTITY_COLUMN, IntStream.rangeClosed(1, ITEM_COUNT).boxed().toList());
|
||||
table.disableOption(row, QUANTITY_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomizeSelections(int row) {
|
||||
table.randomizeSelection(row, ITEM_COLUMN, false);
|
||||
int quantity = 1;
|
||||
|
||||
while(quantity < MAX_ITEM_QUANTITY && Math.random() < 0.5) {
|
||||
quantity++;
|
||||
}
|
||||
|
||||
model.setValueAt(quantity, row, QUANTITY_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSelections(int row) {
|
||||
model.setValueAt(null, row, ITEM_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getSelectionIcon(int row) {
|
||||
int item = row == -1 ? 0 : getItem(row);
|
||||
return DataManager.getItemSprite(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldUpdateSelectionIcon(int column) {
|
||||
return column == ITEM_COLUMN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSelectionIconScale() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
public void loadProfile(Player player) {
|
||||
gameVersion = player.getGameVersion();
|
||||
table.clearSelection();
|
||||
clearSelections();
|
||||
legalModeToggle.setSelected(false);
|
||||
legalModeChanged();
|
||||
int row = 0;
|
||||
|
||||
for(DreamItem item : player.getItems()) {
|
||||
model.setValueAt(item.id(), row, ITEM_COLUMN);
|
||||
model.setValueAt(item.quantity(), row, QUANTITY_COLUMN);
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
public void saveProfile(Player player) {
|
||||
player.setItems(computeSelectionList(row -> new DreamItem(getItem(row), getQuantity(row)), row -> getItem(row) != 0));
|
||||
}
|
||||
|
||||
private void updateItemOptions(int row) {
|
||||
setOptions(row, ITEM_COLUMN,
|
||||
isLegalMode() ? DataManager.getItemOptions() : DataManager.getDownloadableItems(gameVersion),
|
||||
(a, b) -> DataManager.getItemName(a).compareTo(DataManager.getItemName(b)), true);
|
||||
}
|
||||
|
||||
private int getItem(int row) {
|
||||
return table.getValueAt(row, ITEM_COLUMN, Integer.class, 0);
|
||||
}
|
||||
|
||||
private int getQuantity(int row) {
|
||||
return table.getValueAt(row, QUANTITY_COLUMN, Integer.class, 0);
|
||||
}
|
||||
}
|
||||
380
src/main/java/entralinked/gui/panels/MiscPanel.java
Normal file
380
src/main/java/entralinked/gui/panels/MiscPanel.java
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
package entralinked.gui.panels;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
|
||||
import entralinked.Entralinked;
|
||||
import entralinked.GameVersion;
|
||||
import entralinked.gui.FileChooser;
|
||||
import entralinked.gui.ModelListCellRenderer;
|
||||
import entralinked.model.player.Player;
|
||||
import entralinked.utility.Crc16;
|
||||
import entralinked.utility.LEOutputStream;
|
||||
import entralinked.utility.SwingUtility;
|
||||
import entralinked.utility.TiledImageUtility;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class MiscPanel extends JPanel {
|
||||
|
||||
@FunctionalInterface
|
||||
private static interface SkinWriter {
|
||||
|
||||
public void writeSkin(OutputStream outputStream, BufferedImage image) throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal model for combo boxes.
|
||||
*/
|
||||
private static record DlcOption(String type, String name, String path, boolean custom) {
|
||||
|
||||
public DlcOption(String type, String name, String path) {
|
||||
this(type, name, path, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static final FileFilter IMAGE_FILE_FILTER = new FileNameExtensionFilter("Image Files (*.png)", "png");
|
||||
private static final FileFilter CGEAR_FILE_FILTER = new FileNameExtensionFilter("C-Gear Skin Files (*.bin, *.cgb, *.psk)", "bin", "cgb", "psk");
|
||||
private static final FileFilter ZUKAN_FILE_FILTER = new FileNameExtensionFilter("Pokédex Skin Files (*.bin, *.pds)", "bin", "pds");
|
||||
private static final byte[] NARC_HEADER = { 0x4E, 0x41, 0x52, 0x43, (byte)0xFE, (byte)0xFF, 0x00, 0x01 };
|
||||
private static final BufferedImage EMPTY_IMAGE = new BufferedImage(TiledImageUtility.SCREEN_WIDTH, TiledImageUtility.SCREEN_HEIGHT, BufferedImage.TYPE_INT_RGB);
|
||||
private static final Map<String, Image> skinCache = new HashMap<>();
|
||||
private final Entralinked entralinked;
|
||||
private final JComboBox<DlcOption> cgearComboBox;
|
||||
private final JComboBox<DlcOption> zukanComboBox;
|
||||
private final JComboBox<DlcOption> musicalComboBox;
|
||||
private final JPanel optionPanel;
|
||||
private final JSpinner levelSpinner;
|
||||
private Player player;
|
||||
private GameVersion gameVersion;
|
||||
private DlcOption customCGearSkin;
|
||||
private DlcOption customDexSkin;
|
||||
private DlcOption customMusical;
|
||||
|
||||
public MiscPanel(Entralinked entralinked) {
|
||||
this.entralinked = entralinked;
|
||||
setLayout(new MigLayout("align 50% 50%"));
|
||||
|
||||
// Create preview labels
|
||||
JLabel cgearPreviewLabel = new JLabel("", JLabel.CENTER);
|
||||
cgearPreviewLabel.setBorder(BorderFactory.createTitledBorder("C-Gear Skin Preview"));
|
||||
JLabel dexPreviewLabel = new JLabel("", JLabel.CENTER);
|
||||
dexPreviewLabel.setBorder(BorderFactory.createTitledBorder("Pokédex Skin Preview"));
|
||||
|
||||
// Create preview image panel
|
||||
JPanel previewPanel = new JPanel();
|
||||
previewPanel.add(cgearPreviewLabel);
|
||||
previewPanel.add(dexPreviewLabel);
|
||||
add(previewPanel, "spanx, align 50%, wrap");
|
||||
|
||||
// Create combo boxes
|
||||
ModelListCellRenderer<DlcOption> renderer = new ModelListCellRenderer<>(DlcOption.class, DlcOption::name, "Do not change");
|
||||
cgearComboBox = new JComboBox<>();
|
||||
cgearComboBox.setMinimumSize(cgearComboBox.getPreferredSize());
|
||||
cgearComboBox.setRenderer(renderer);
|
||||
cgearComboBox.addActionListener(event -> {
|
||||
cgearPreviewLabel.setIcon(new ImageIcon(getSkinImage((DlcOption)cgearComboBox.getSelectedItem())));
|
||||
});
|
||||
zukanComboBox = new JComboBox<>();
|
||||
zukanComboBox.setMinimumSize(zukanComboBox.getPreferredSize());
|
||||
zukanComboBox.setRenderer(renderer);
|
||||
zukanComboBox.addActionListener(event -> {
|
||||
dexPreviewLabel.setIcon(new ImageIcon(getSkinImage((DlcOption)zukanComboBox.getSelectedItem())));
|
||||
});
|
||||
musicalComboBox = new JComboBox<>();
|
||||
musicalComboBox.setMinimumSize(musicalComboBox.getPreferredSize());
|
||||
musicalComboBox.setRenderer(renderer);
|
||||
|
||||
// Create option panel
|
||||
optionPanel = new JPanel(new MigLayout());
|
||||
|
||||
// Create C-Gear skin selector
|
||||
createDlcOption("C-Gear Skin", cgearComboBox, () -> {
|
||||
FileChooser.showFileOpenDialog(getRootPane(), Arrays.asList(IMAGE_FILE_FILTER, CGEAR_FILE_FILTER), selection -> {
|
||||
File dst = player.getCGearSkinFile();
|
||||
File file = selection.file();
|
||||
FileFilter filter = selection.filter();
|
||||
|
||||
if(filter == IMAGE_FILE_FILTER) {
|
||||
if(!importSkinImage(file, dst, (stream, image) -> TiledImageUtility.writeCGearSkin(stream, image, !gameVersion.isVersion2()))) {
|
||||
return;
|
||||
}
|
||||
} else if(filter == CGEAR_FILE_FILTER) {
|
||||
if(!importSkinFile(file, dst, 9730)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
DlcOption option = new DlcOption(gameVersion.isVersion2() ? "CGEAR2" : "CGEAR", file.getName(), dst.getAbsolutePath(), true);
|
||||
updateCustomOption(cgearComboBox, customCGearSkin, option);
|
||||
customCGearSkin = option;
|
||||
player.setCustomCGearSkin(customCGearSkin.name());
|
||||
});
|
||||
});
|
||||
|
||||
// Create Pokédex skin selector
|
||||
createDlcOption("Pokédex Skin", zukanComboBox, () -> {
|
||||
FileChooser.showFileOpenDialog(getRootPane(), Arrays.asList(IMAGE_FILE_FILTER, ZUKAN_FILE_FILTER), selection -> {
|
||||
File dst = player.getDexSkinFile();
|
||||
File file = selection.file();
|
||||
FileFilter filter = selection.filter();
|
||||
|
||||
if(filter == IMAGE_FILE_FILTER) {
|
||||
if(!importSkinImage(file, dst, (stream, image) -> TiledImageUtility.writeDexSkin(stream, image, TiledImageUtility.generateBackgroundColors(image)))) {
|
||||
return;
|
||||
}
|
||||
} else if(filter == ZUKAN_FILE_FILTER) {
|
||||
if(!importSkinFile(file, dst, 25090)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
DlcOption option = new DlcOption("ZUKAN", file.getName(), dst.getAbsolutePath(), true);
|
||||
updateCustomOption(zukanComboBox, customDexSkin, option);
|
||||
customDexSkin = option;
|
||||
player.setCustomDexSkin(customDexSkin.name());
|
||||
});
|
||||
});
|
||||
|
||||
// Create musical show selector
|
||||
createDlcOption("Musical Show", musicalComboBox, () -> {
|
||||
SwingUtility.showIgnorableHint(getRootPane(), "Please exercise caution when importing custom musicals.\n"
|
||||
+ "Downloading invalid data might cause game crashes or other issues.", "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
FileChooser.showFileOpenDialog(getRootPane(), selection -> {
|
||||
File dst = player.getMusicalFile();
|
||||
File file = selection.file();
|
||||
|
||||
if(!importNarcFile(file, dst)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DlcOption option = new DlcOption("MUSICAL", file.getName(), dst.getAbsolutePath(), true);
|
||||
updateCustomOption(musicalComboBox, customMusical, option);
|
||||
customMusical = option;
|
||||
player.setCustomMusical(customMusical.name());
|
||||
});
|
||||
});
|
||||
|
||||
// Create level spinner
|
||||
levelSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 99, 1)) ;
|
||||
optionPanel.add(new JLabel("Level Gain"), "sizegroup label");
|
||||
optionPanel.add(levelSpinner, "sizegroup option");
|
||||
add(optionPanel, "spanx, align 50%");
|
||||
}
|
||||
|
||||
public void loadProfile(Player player) {
|
||||
this.player = player;
|
||||
gameVersion = player.getGameVersion();
|
||||
String cgearType = player.getGameVersion().isVersion2() ? "CGEAR2" : "CGEAR";
|
||||
customCGearSkin = player.getCustomCGearSkin() == null ? null : new DlcOption(cgearType, player.getCustomCGearSkin(), player.getCGearSkinFile().getAbsolutePath(), true);
|
||||
customDexSkin = player.getCustomDexSkin() == null ? null : new DlcOption("ZUKAN", player.getCustomDexSkin(), player.getDexSkinFile().getAbsolutePath(), true);
|
||||
customMusical = player.getCustomMusical() == null ? null : new DlcOption("MUSICAL", player.getCustomMusical(), player.getMusicalFile().getAbsolutePath(), true);
|
||||
updateDlcOptions(cgearComboBox, cgearType, player.getCGearSkin(), customCGearSkin);
|
||||
updateDlcOptions(zukanComboBox, "ZUKAN", player.getDexSkin(), customDexSkin);
|
||||
updateDlcOptions(musicalComboBox, "MUSICAL", player.getMusical(), customMusical);
|
||||
levelSpinner.setValue(player.getLevelsGained());
|
||||
}
|
||||
|
||||
public void saveProfile(Player player) {
|
||||
DlcOption cgearSkin = (DlcOption)cgearComboBox.getSelectedItem();
|
||||
DlcOption dexSkin = (DlcOption)zukanComboBox.getSelectedItem();
|
||||
DlcOption musical = (DlcOption)musicalComboBox.getSelectedItem();
|
||||
player.setCGearSkin(cgearSkin == null ? null : cgearSkin.custom() ? "custom" : cgearSkin.name());
|
||||
player.setDexSkin(dexSkin == null ? null : dexSkin.custom() ? "custom" : dexSkin.name());
|
||||
player.setMusical(musical == null ? null : musical.custom() ? "custom" : musical.name());
|
||||
player.setLevelsGained((int)levelSpinner.getValue());
|
||||
}
|
||||
|
||||
private void createDlcOption(String label, JComboBox<DlcOption> comboBox, Runnable importListener) {
|
||||
optionPanel.add(new JLabel(label), "sizegroup label");
|
||||
optionPanel.add(comboBox, "sizegroup option");
|
||||
JButton importButton = new JButton("Import");
|
||||
importButton.addActionListener(event -> importListener.run());
|
||||
optionPanel.add(importButton, "wrap");
|
||||
}
|
||||
|
||||
private void updateDlcOptions(JComboBox<DlcOption> comboBox, String type, String selectedOption, DlcOption customOption) {
|
||||
Vector<DlcOption> options = new Vector<>();
|
||||
|
||||
if(customOption != null) {
|
||||
options.add(customOption);
|
||||
}
|
||||
|
||||
entralinked.getDlcList().getDlcList("IRAO", type).forEach(dlc -> options.add(new DlcOption(type, dlc.name(), dlc.path())));
|
||||
DlcOption selection = selectedOption == null ? null : selectedOption.equals("custom") ? customOption : options.stream().filter(x -> selectedOption.equals(x.name())).findFirst().orElse(null);
|
||||
options.add(0, null); // "Do not change" option
|
||||
comboBox.setModel(new DefaultComboBoxModel<DlcOption>(options));
|
||||
comboBox.setSelectedItem(selection);
|
||||
}
|
||||
|
||||
private void updateCustomOption(JComboBox<DlcOption> comboBox, DlcOption oldValue, DlcOption newValue) {
|
||||
DefaultComboBoxModel<DlcOption> model = (DefaultComboBoxModel<DlcOption>)comboBox.getModel();
|
||||
|
||||
if(oldValue != null) {
|
||||
model.removeElement(oldValue);
|
||||
skinCache.remove(oldValue.path());
|
||||
}
|
||||
|
||||
model.insertElementAt(newValue, 1);
|
||||
model.setSelectedItem(newValue);
|
||||
}
|
||||
|
||||
private static Image getSkinImage(DlcOption option) {
|
||||
return option == null ? EMPTY_IMAGE : skinCache.computeIfAbsent(option.path(), path -> {
|
||||
try(FileInputStream inputStream = new FileInputStream(path)) {
|
||||
return switch(option.type()) {
|
||||
case "CGEAR" -> TiledImageUtility.readCGearSkin(inputStream, true);
|
||||
case "CGEAR2" -> TiledImageUtility.readCGearSkin(inputStream, false);
|
||||
case "ZUKAN" -> TiledImageUtility.readDexSkin(inputStream, true);
|
||||
default -> throw new IllegalArgumentException("Invalid type: " + option.type());
|
||||
};
|
||||
} catch(Exception e) {
|
||||
return EMPTY_IMAGE; // TODO show feedback
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean importSkinFile(File src, File dst, int expectedSize) {
|
||||
int sizeWithoutChecksum = expectedSize - 2;
|
||||
int length = (int)src.length();
|
||||
|
||||
// Check content length
|
||||
if(length != expectedSize && length != sizeWithoutChecksum) {
|
||||
JOptionPane.showMessageDialog(getRootPane(), "Invalid content length, expected either %s or %s bytes."
|
||||
.formatted(sizeWithoutChecksum, expectedSize), "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] bytes = Files.readAllBytes(src.toPath());
|
||||
boolean writeChecksum = true;
|
||||
|
||||
// Validate checksum
|
||||
if(length == expectedSize) {
|
||||
int checksum = Crc16.calc(bytes, 0, sizeWithoutChecksum);
|
||||
int checksumInFile = (bytes[bytes.length - 2] & 0xFF) | ((bytes[bytes.length - 1] & 0xFF) << 8);
|
||||
|
||||
if(checksum != checksumInFile) {
|
||||
JOptionPane.showMessageDialog(getRootPane(), "File checksum doesn't match.", "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
|
||||
writeChecksum = false;
|
||||
}
|
||||
|
||||
// Write to destination & append checksum if necessary
|
||||
try(LEOutputStream outputStream = new LEOutputStream(new FileOutputStream(dst))) {
|
||||
outputStream.write(bytes);
|
||||
|
||||
if(writeChecksum) {
|
||||
outputStream.writeShort(Crc16.calc(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace(); // TODO show feedback
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean importSkinImage(File src, File dst, SkinWriter writer) {
|
||||
try {
|
||||
BufferedImage image = ImageIO.read(src);
|
||||
int width = TiledImageUtility.SCREEN_WIDTH;
|
||||
int height = TiledImageUtility.SCREEN_HEIGHT;
|
||||
|
||||
if(image.getWidth() != width || image.getHeight() != height) {
|
||||
JOptionPane.showMessageDialog(getRootPane(), "Image size must be %sx%s pixels.".formatted(width, height), "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
writer.writeSkin(byteStream, image);
|
||||
byte[] bytes = byteStream.toByteArray();
|
||||
|
||||
try(LEOutputStream outputStream = new LEOutputStream(new FileOutputStream(dst))) {
|
||||
outputStream.write(bytes);
|
||||
outputStream.writeShort(Crc16.calc(bytes));
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch(IllegalArgumentException e) {
|
||||
JOptionPane.showMessageDialog(getRootPane(), e.getMessage(), "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace(); // TODO show feedback
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean importNarcFile(File src, File dst) {
|
||||
try {
|
||||
byte[] bytes = Files.readAllBytes(src.toPath());
|
||||
int offset = 0;
|
||||
|
||||
// Check narc header
|
||||
if(!Arrays.equals(bytes, 0, NARC_HEADER.length, NARC_HEADER, 0, NARC_HEADER.length)) {
|
||||
if(bytes.length < 16 || !Arrays.equals(bytes, 16, 16 + NARC_HEADER.length, NARC_HEADER, 0, NARC_HEADER.length)) {
|
||||
JOptionPane.showMessageDialog(getRootPane(), "Invalid or unsupported file.", "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
|
||||
offset = 16;
|
||||
}
|
||||
|
||||
// TODO utility function
|
||||
int length = ((bytes[offset + NARC_HEADER.length + 3] & 0xFF) << 24)
|
||||
| ((bytes[offset + NARC_HEADER.length + 2] & 0xFF) << 16)
|
||||
| ((bytes[offset + NARC_HEADER.length + 1] & 0xFF) << 8)
|
||||
| (bytes[offset + NARC_HEADER.length] & 0xFF);
|
||||
|
||||
if(offset + length >= bytes.length) {
|
||||
JOptionPane.showMessageDialog(getRootPane(), "File data is malformed or corrupt.", "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write to destination
|
||||
try(LEOutputStream outputStream = new LEOutputStream(new FileOutputStream(dst))) {
|
||||
outputStream.write(bytes, offset, length);
|
||||
outputStream.writeShort(Crc16.calc(bytes, offset, length));
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace(); // TODO show feedback
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
119
src/main/java/entralinked/gui/panels/SummaryPanel.java
Normal file
119
src/main/java/entralinked/gui/panels/SummaryPanel.java
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package entralinked.gui.panels;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
|
||||
import entralinked.gui.component.PropertyDisplay;
|
||||
import entralinked.gui.component.ShadowedSprite;
|
||||
import entralinked.gui.data.DataManager;
|
||||
import entralinked.model.pkmn.PkmnInfo;
|
||||
import entralinked.model.player.Player;
|
||||
import entralinked.utility.SwingUtility;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class SummaryPanel extends JPanel {
|
||||
|
||||
public static final String KEY_NAME = "name";
|
||||
public static final String KEY_OT = "trainer";
|
||||
public static final String KEY_ID = "tid";
|
||||
public static final String KEY_SID = "sid";
|
||||
public static final String KEY_PID = "pid";
|
||||
public static final String KEY_ABILITY = "ability";
|
||||
public static final String KEY_NATURE = "nature";
|
||||
public static final String KEY_GENDER = "gender";
|
||||
public static final String KEY_LEVEL = "level";
|
||||
public static final String KEY_ITEM = "item";
|
||||
private static final String[] FLAVOR_TEXT = {
|
||||
"*pokemon* is trying to grow berries in a nearby garden.",
|
||||
"*pokemon* is itching to earn some Dream Points.",
|
||||
"*pokemon* is looking at the Tree of Dreams from afar.",
|
||||
"*pokemon* is playing a fun minigame (and you're not allowed to join, ha!)",
|
||||
"*pokemon* is checking out the Friend Board.",
|
||||
"*pokemon* is dreaming about when they first met *trainer*.",
|
||||
"*pokemon* left a berry at the Tree of Dreams.",
|
||||
"*pokemon* is trying to come up with more of these."
|
||||
}; // TODO I think it would be fun to grab various bits of data from the save file and use them here
|
||||
private final ShadowedSprite icon;
|
||||
private final JLabel flavorTextLabel;
|
||||
private final PropertyDisplay infoDisplay;
|
||||
private Player player;
|
||||
|
||||
public SummaryPanel() {
|
||||
setLayout(new MigLayout("align 50% 50%, gapy 0, insets 0"));
|
||||
|
||||
// Create info labels
|
||||
JLabel titleLabel = new JLabel("Summary");
|
||||
titleLabel.putClientProperty(FlatClientProperties.STYLE, "font:bold +8");
|
||||
flavorTextLabel = new JLabel();
|
||||
flavorTextLabel.putClientProperty(FlatClientProperties.STYLE, "[dark]foreground:darken(@foreground,20%)");
|
||||
JLabel subLabel = new JLabel("Tucked-in Pokémon info:");
|
||||
subLabel.putClientProperty(FlatClientProperties.STYLE, "[dark]foreground:darken(@foreground,20%)");
|
||||
|
||||
// Create header panel
|
||||
JPanel headerPanel = new JPanel(new MigLayout("fillx, insets 0"));
|
||||
headerPanel.add(titleLabel, "wrap");
|
||||
headerPanel.add(flavorTextLabel, "wrap");
|
||||
headerPanel.add(subLabel, "wrap");
|
||||
add(headerPanel, "spanx");
|
||||
|
||||
// Create icon label
|
||||
icon = new ShadowedSprite();
|
||||
add(icon, "gapright 4");
|
||||
|
||||
// Create property display
|
||||
infoDisplay = new PropertyDisplay();
|
||||
infoDisplay.addProperty(0, 0, KEY_NAME, "Name");
|
||||
infoDisplay.addProperty(0, 1, KEY_OT, "OT");
|
||||
infoDisplay.addProperty(0, 2, KEY_ID, "ID No.");
|
||||
infoDisplay.addProperty(0, 3, KEY_SID, "SID No.", true);
|
||||
infoDisplay.addProperty(0, 4, KEY_PID, "PID", true);
|
||||
infoDisplay.addProperty(1, 0, KEY_ABILITY, "Ability");
|
||||
infoDisplay.addProperty(1, 1, KEY_NATURE, "Nature");
|
||||
infoDisplay.addProperty(1, 2, KEY_GENDER, "Gender");
|
||||
infoDisplay.addProperty(1, 3, KEY_LEVEL, "Level");
|
||||
infoDisplay.addProperty(1, 4, KEY_ITEM, "Held Item");
|
||||
add(infoDisplay, "wrap");
|
||||
|
||||
// Create save file location button
|
||||
JLabel openSaveButton = SwingUtility.createButtonLabel("Open data directory", () -> {
|
||||
SwingUtility.showIgnorableHint(getRootPane(), "The game sends incomplete/corrupt save files to the server.\n"
|
||||
+ "Please do not rely on Game Sync to back up your save files.", "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
|
||||
try {
|
||||
Desktop.getDesktop().open(player.getDataDirectory());
|
||||
} catch(IOException e) {
|
||||
SwingUtility.showExceptionInfo(getRootPane(), "Couldn't open data directory.", e);
|
||||
}
|
||||
});
|
||||
add(openSaveButton, "spanx, align 100%, gapy 8");
|
||||
}
|
||||
|
||||
public void loadProfile(Player player) {
|
||||
this.player = player;
|
||||
PkmnInfo info = player.getDreamerInfo();
|
||||
icon.setImage(DataManager.getPokemonSprite(info.species(), info.form(), info.gender(), info.isShiny()));
|
||||
infoDisplay.setValue(KEY_NAME, info.nickname());
|
||||
infoDisplay.setValue(KEY_OT, info.trainerName());
|
||||
infoDisplay.setValue(KEY_ID, "%05d".formatted(info.trainerId()));
|
||||
infoDisplay.setValue(KEY_SID, "%05d".formatted(info.trainerSecretId()));
|
||||
infoDisplay.setValue(KEY_PID, "%08X".formatted(info.personality()));
|
||||
infoDisplay.setValue(KEY_ABILITY, DataManager.getAbilityName(info.ability()));
|
||||
infoDisplay.setValue(KEY_NATURE, info.nature().getDisplayName());
|
||||
infoDisplay.setValue(KEY_GENDER, info.gender().getDisplayName());
|
||||
infoDisplay.setValue(KEY_LEVEL, info.level());
|
||||
infoDisplay.setValue(KEY_ITEM, info.heldItem() == 0 ? "None" : DataManager.getItemName(info.heldItem()));
|
||||
SwingUtility.setTextFieldToggle(infoDisplay.getValueField(KEY_SID), false);
|
||||
SwingUtility.setTextFieldToggle(infoDisplay.getValueField(KEY_PID), false);
|
||||
String flavorText = FLAVOR_TEXT[(int)(Math.random() * FLAVOR_TEXT.length)]
|
||||
.replace("*pokemon*", info.nickname())
|
||||
.replace("*trainer*", info.trainerName());
|
||||
flavorTextLabel.setText(flavorText);
|
||||
}
|
||||
}
|
||||
251
src/main/java/entralinked/gui/panels/TableEditorPanel.java
Normal file
251
src/main/java/entralinked/gui/panels/TableEditorPanel.java
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
package entralinked.gui.panels;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
|
||||
import entralinked.gui.component.ConfigTable;
|
||||
import entralinked.gui.component.ShadowedSprite;
|
||||
import entralinked.utility.SwingUtility;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class TableEditorPanel extends JPanel {
|
||||
|
||||
protected final Map<Integer, Object> oldValues = new HashMap<>();
|
||||
protected final int rowCount;
|
||||
protected final ConfigTable table;
|
||||
protected final TableModel model;
|
||||
protected final JLabel titleLabel;
|
||||
protected final JLabel descriptionLabel;
|
||||
protected final ShadowedSprite selectionIcon;
|
||||
protected JButton randomizeButton;
|
||||
protected JButton clearAllButton;
|
||||
protected JCheckBox legalModeToggle;
|
||||
|
||||
public TableEditorPanel(int rowCount, String[] columnNames, Class<?>[] columnTypes) {
|
||||
this(rowCount, true, columnNames, columnTypes);
|
||||
}
|
||||
|
||||
public TableEditorPanel(int rowCount, boolean incluceLegalMode, String[] columnNames, Class<?>[] columnTypes) {
|
||||
if(columnNames.length != columnTypes.length) {
|
||||
throw new IllegalArgumentException("Length mismatch between column names and column types");
|
||||
}
|
||||
|
||||
this.rowCount = rowCount;
|
||||
String[] columns = new String[columnNames.length + 1];
|
||||
columns[0] = "No.";
|
||||
System.arraycopy(columnNames, 0, columns, 1, columnNames.length);
|
||||
|
||||
// Create labels
|
||||
titleLabel = new JLabel();
|
||||
titleLabel.putClientProperty(FlatClientProperties.STYLE, "font:bold +8");
|
||||
descriptionLabel = new JLabel();
|
||||
descriptionLabel.putClientProperty(FlatClientProperties.STYLE, "[dark]foreground:darken(@foreground,20%)");
|
||||
selectionIcon = new ShadowedSprite();
|
||||
|
||||
// Create buttons & toggles
|
||||
randomizeButton = new JButton("Random");
|
||||
randomizeButton.addActionListener(event -> randomizeSelections());
|
||||
clearAllButton = new JButton("Clear all");
|
||||
clearAllButton.addActionListener(event -> clearSelections());
|
||||
legalModeToggle = new JCheckBox("Legal mode");
|
||||
legalModeToggle.addActionListener(event -> {
|
||||
boolean selected = legalModeToggle.isSelected();
|
||||
|
||||
// Since we're clearing illegal settings, ask for confirmation first.
|
||||
if(selected && !SwingUtility.showIgnorableConfirmDialog(getRootPane(), "Enable legal mode? Illegal selections will be cleared.", "Attention")) {
|
||||
legalModeToggle.setSelected(false);
|
||||
return;
|
||||
}
|
||||
|
||||
legalModeChanged();
|
||||
});
|
||||
|
||||
// Create layout
|
||||
setLayout(new MigLayout("align 50% 50%, gapy 0, insets 0 8 0 8"));
|
||||
|
||||
// Create header panels
|
||||
JPanel labelPanel = new JPanel(new MigLayout("insets 0, fill"));
|
||||
labelPanel.add(titleLabel, "wrap");
|
||||
labelPanel.add(descriptionLabel, "wrap");
|
||||
|
||||
JPanel headerPanel = new JPanel(new MigLayout("insets n n 0 n", "[][grow]"));
|
||||
headerPanel.add(labelPanel);
|
||||
headerPanel.add(selectionIcon, "align 100%");
|
||||
add(headerPanel, "wrap, grow");
|
||||
|
||||
// Create table
|
||||
model = new DefaultTableModel(columns, rowCount);
|
||||
model.addTableModelListener(event -> {
|
||||
int column = event.getColumn();
|
||||
|
||||
if(column == 0) {
|
||||
return; // Ignore number column
|
||||
}
|
||||
|
||||
if(shouldUpdateSelectionIcon(column)) {
|
||||
updateSelectionIcon();
|
||||
}
|
||||
|
||||
for(int i = event.getFirstRow(); i <= event.getLastRow(); i++) {
|
||||
int index = i * rowCount + column;
|
||||
Object oldValue = oldValues.get(index);
|
||||
Object newValue = model.getValueAt(i, column);
|
||||
Class<?> type = columnTypes[column - 1];
|
||||
|
||||
// Set to null if types don't match
|
||||
if(oldValue != null && !type.isAssignableFrom(oldValue.getClass())) {
|
||||
oldValue = null;
|
||||
}
|
||||
|
||||
if(newValue != null && !type.isAssignableFrom(newValue.getClass())) {
|
||||
newValue = null;
|
||||
}
|
||||
|
||||
// Fire data changed if new value is different from old value
|
||||
if(newValue != oldValue) {
|
||||
dataChanged(i, column, oldValue, newValue);
|
||||
oldValues.put(index, model.getValueAt(i, column)); // We use getValueAt again because dataChanged might have updated the value
|
||||
}
|
||||
}
|
||||
});
|
||||
table = new ConfigTable();
|
||||
table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
|
||||
table.getTableHeader().setReorderingAllowed(false);
|
||||
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
table.setModel(model);
|
||||
table.getSelectionModel().addListSelectionListener(event -> updateSelectionIcon());
|
||||
int width = Math.max(table.getPreferredSize().width, table.getPreferredScrollableViewportSize().width);
|
||||
table.setPreferredScrollableViewportSize(new Dimension(width, table.getRowHeight() * rowCount));
|
||||
table.getColumnModel().getColumn(0).setResizable(false);
|
||||
table.getColumnModel().getColumn(0).setMinWidth(32);
|
||||
table.getColumnModel().getColumn(0).setMaxWidth(32);
|
||||
add(new JScrollPane(table), "spanx, grow");
|
||||
updateSelectionIcon();
|
||||
|
||||
// Initialize rows
|
||||
for(int i = 0; i < rowCount; i++) {
|
||||
model.setValueAt(i + 1, i, 0);
|
||||
}
|
||||
|
||||
// Create footer panel
|
||||
JPanel footerPanel = new JPanel(new MigLayout("insets 0", "0[]"));
|
||||
footerPanel.add(randomizeButton);
|
||||
footerPanel.add(clearAllButton);
|
||||
|
||||
if(incluceLegalMode) {
|
||||
footerPanel.add(legalModeToggle);
|
||||
}
|
||||
|
||||
add(footerPanel, "spanx, gapy 4");
|
||||
}
|
||||
|
||||
public abstract void initializeOptions(int row);
|
||||
public abstract void randomizeSelections(int row);
|
||||
public abstract void clearSelections(int row);
|
||||
public abstract Image getSelectionIcon(int row);
|
||||
|
||||
public void dataChanged(int row, int column, Object oldValue, Object newValue) {
|
||||
// Override
|
||||
}
|
||||
|
||||
public void legalModeChanged() {
|
||||
// Override
|
||||
}
|
||||
|
||||
public boolean shouldUpdateSelectionIcon(int column) {
|
||||
return true; // Override
|
||||
}
|
||||
|
||||
public int getSelectionIconScale() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void initializeOptions() {
|
||||
for(int i = 0; i < rowCount; i++) {
|
||||
initializeOptions(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void randomizeSelections() {
|
||||
for(int i = 0; i < rowCount; i++) {
|
||||
randomizeSelections(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearSelections() {
|
||||
for(int i = 0; i < rowCount; i++) {
|
||||
clearSelections(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTitle(String text) {
|
||||
titleLabel.setText(text);
|
||||
}
|
||||
|
||||
public void setDescription(String text) {
|
||||
descriptionLabel.setText(text);
|
||||
}
|
||||
|
||||
public boolean isLegalMode() {
|
||||
return legalModeToggle.isSelected();
|
||||
}
|
||||
|
||||
private void updateSelectionIcon() {
|
||||
BufferedImage image = (BufferedImage)getSelectionIcon(table.getSelectedRow());
|
||||
selectionIcon.setImage(image, getSelectionIconScale(), 96, 96);
|
||||
}
|
||||
|
||||
protected <T> void setOptions(int row, int column, Collection<T> options) {
|
||||
setOptions(row, column, options, null);
|
||||
}
|
||||
|
||||
protected <T> void setOptions(int row, int column, Collection<T> options, Comparator<T> sorter) {
|
||||
setOptions(row, column, options, sorter, false);
|
||||
}
|
||||
|
||||
protected <T> void setOptions(int row, int column, Collection<T> options, boolean includeNullOption) {
|
||||
setOptions(row, column, options, null, includeNullOption);
|
||||
}
|
||||
|
||||
protected <T> void setOptions(int row, int column, Collection<T> options, Comparator<T> sorter, boolean includeNullOption) {
|
||||
List<T> list = new ArrayList<>(options);
|
||||
|
||||
if(sorter != null) {
|
||||
list.sort(sorter);
|
||||
}
|
||||
|
||||
table.setOptionsAt(row, column, list, includeNullOption);
|
||||
}
|
||||
|
||||
protected <T> List<T> computeSelectionList(Function<Integer, T> supplier, Function<Integer, Boolean> rowFilter) {
|
||||
List<T> list = new ArrayList<>();
|
||||
|
||||
for(int i = 0; i < rowCount; i++) {
|
||||
if(rowFilter.apply(i)) {
|
||||
list.add(supplier.apply(i));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
272
src/main/java/entralinked/gui/panels/VisitorEditorPanel.java
Normal file
272
src/main/java/entralinked/gui/panels/VisitorEditorPanel.java
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
package entralinked.gui.panels;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.Toolkit;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.swing.InputVerifier;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import entralinked.GameVersion;
|
||||
import entralinked.gui.ImageLoader;
|
||||
import entralinked.gui.InputVerifierCellEditor;
|
||||
import entralinked.gui.data.Country;
|
||||
import entralinked.gui.data.DataManager;
|
||||
import entralinked.gui.data.PkmnSpecies;
|
||||
import entralinked.gui.data.Region;
|
||||
import entralinked.model.avenue.AvenueShopType;
|
||||
import entralinked.model.avenue.AvenueVisitor;
|
||||
import entralinked.model.avenue.AvenueVisitorType;
|
||||
import entralinked.model.player.Player;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class VisitorEditorPanel extends TableEditorPanel {
|
||||
|
||||
public static final int VISITOR_COUNT = 12;
|
||||
public static final int TYPE_COLUMN = 1;
|
||||
public static final int NAME_COLUMN = 2;
|
||||
public static final int SHOP_COLUMN = 3;
|
||||
public static final int GAME_COLUMN = 4;
|
||||
public static final int COUNTRY_COLUMN = 5;
|
||||
public static final int REGION_COLUMN = 6;
|
||||
public static final int PHRASE_COLUMN = 7;
|
||||
public static final int DREAMER_COLUMN = 8;
|
||||
private static final String[] COLUMN_NAMES = { "Type", "Name", "Shop", "Game", "Country", "Region", "Phrase", "Pokémon" };
|
||||
private static final Class<?>[] COLUMN_TYPES = { AvenueVisitorType.class, String.class, AvenueShopType.class, GameVersion.class, Country.class, Region.class, Integer.class, PkmnSpecies.class };
|
||||
|
||||
// Name tables for randomization
|
||||
// TODO more languages would be neat but isn't really necessary
|
||||
private static final String[] ENG_NAMES_M = { "Aleron", "Alpi", "Amando", "Anselmi", "Anton", "Arhippa", "Arpo", "Artos", "Assar", "Atilio", "Axel", "Azzo", "Jasper", "Jaylen", "Jephew", "Jimbo", "Joakim", "Joelle", "Jonas", "Jule", "Julian", "Julio", "Justan" };
|
||||
private static final String[] ENG_NAMES_F = { "Agata", "Ainikki", "Alena", "Alibena", "Alwyn", "Anelma", "Anneli", "Annetta", "Antonie", "Armina", "Assunta", "Asta", "Jaclyn", "Jane", "Janette", "Jannis", "Jeanne", "Jenna", "Jess", "Joan", "Jocelyn", "Josie", "Judith", "Julie" };
|
||||
private static final String[] JAP_NAMES_M = { "アートス", "アーポ", "アクセル", "アッサール", "アッツォ", "アッラン", "アティリオ", "アマンド", "アルピ", "アルヒッパ", "アンセルミ", "アントン", "ジェヒュー", "ジェリー", "ジェローム", "ジャコブ", "ジャスパー", "ジャレッド", "ジャン", "ジュール", "ジョエル", "ジョシュア", "ジョナス" };
|
||||
private static final String[] JAP_NAMES_F = { "アーム", "アイニッキ", "アガタ", "アスタ", "アッスンタ", "アネルマ", "アラベッラ", "アリビーナ", "アレーナ", "アントニナ", "アンネッタ", "アンネリ", "ジェーン", "ジェシー", "ジェナ", "ジャサント", "ジャニス", "ジャンヌ", "ジュディス", "ジュリー", "ジョアン", "ジョイス", "ジョスリン", "ジョゼ" };
|
||||
|
||||
public VisitorEditorPanel() {
|
||||
super(VISITOR_COUNT, false, COLUMN_NAMES, COLUMN_TYPES);
|
||||
setTitle("Join Avenue");
|
||||
setDescription("""
|
||||
<html>
|
||||
Use the table below to configure Join Avenue visitors.<br/>
|
||||
Please be aware that new visitors will not appear if you have<br/>
|
||||
already reached the max amount of concurrent visitors.
|
||||
</html>
|
||||
""");
|
||||
table.setCellRenderers(TYPE_COLUMN, AvenueVisitorType.class, AvenueVisitorType::getDisplayName, "— — — — —");
|
||||
table.setCellRenderers(SHOP_COLUMN, AvenueShopType.class, AvenueShopType::getDisplayName);
|
||||
table.setCellRenderers(GAME_COLUMN, GameVersion.class, GameVersion::getDisplayName);
|
||||
table.setCellRenderers(COUNTRY_COLUMN, Country.class, Country::name);
|
||||
table.setCellRenderers(REGION_COLUMN, Region.class, Region::name);
|
||||
table.setCellRenderers(DREAMER_COLUMN, PkmnSpecies.class, PkmnSpecies::name);
|
||||
initializeOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataChanged(int row, int column, Object oldValue, Object newValue) {
|
||||
if(column == TYPE_COLUMN) {
|
||||
if(newValue != null) {
|
||||
if(oldValue != null) {
|
||||
return; // Do nothing if new and old values are both non-null
|
||||
}
|
||||
|
||||
table.enableOption(row, SHOP_COLUMN);
|
||||
table.enableOption(row, GAME_COLUMN, GameVersion.BLACK_2_ENGLISH); // Updates country -> region
|
||||
table.enableOption(row, PHRASE_COLUMN);
|
||||
table.enableOption(row, DREAMER_COLUMN);
|
||||
table.enableOption(row, NAME_COLUMN, findRandomName(row));
|
||||
model.setValueAt(findRandomName(row), row, NAME_COLUMN);
|
||||
return;
|
||||
}
|
||||
|
||||
disableSecondaryOptions(row);
|
||||
} else if(column == GAME_COLUMN) {
|
||||
if(newValue == null) {
|
||||
table.disableOption(row, COUNTRY_COLUMN);
|
||||
return;
|
||||
}
|
||||
|
||||
GameVersion newVersion = (GameVersion)newValue;
|
||||
boolean shouldUpdate = oldValue == null; // Update by default if previous value was null
|
||||
|
||||
// Check if language code changed from or to Japanese
|
||||
if(oldValue != null) {
|
||||
GameVersion oldVersion = (GameVersion)oldValue;
|
||||
shouldUpdate |= (oldVersion.getLanguageCode() == 1 && newVersion.getLanguageCode() != 1)
|
||||
|| (oldVersion.getLanguageCode() != 1 && newVersion.getLanguageCode() == 1);
|
||||
}
|
||||
|
||||
if(shouldUpdate) {
|
||||
// Japanese language visitors seem to only be able to appear if their set country is Japan
|
||||
setOptions(row, COUNTRY_COLUMN, newVersion.getLanguageCode() == 1 ? Arrays.asList(DataManager.getCountry(105)) : DataManager.getCountries());
|
||||
table.enableOption(row, COUNTRY_COLUMN);
|
||||
}
|
||||
} else if(column == COUNTRY_COLUMN) {
|
||||
if(newValue == null) {
|
||||
table.disableOption(row, REGION_COLUMN);
|
||||
return;
|
||||
}
|
||||
|
||||
Country country = (Country)newValue;
|
||||
setOptions(row, REGION_COLUMN, country.hasRegions() ? country.regions() : Collections.emptyList(), (a, b) -> a.name().compareTo(b.name()));
|
||||
table.enableOption(row, REGION_COLUMN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeOptions(int row) {
|
||||
setOptions(row, TYPE_COLUMN, Arrays.asList(AvenueVisitorType.values()), true);
|
||||
setOptions(row, SHOP_COLUMN, Arrays.asList(AvenueShopType.values()));
|
||||
setOptions(row, GAME_COLUMN, Arrays.asList(GameVersion.values()), (a, b) -> Integer.compare(a.getLanguageCode(), b.getLanguageCode()));
|
||||
setOptions(row, PHRASE_COLUMN, IntStream.rangeClosed(0, 7).boxed().toList());
|
||||
setOptions(row, DREAMER_COLUMN, DataManager.getSpecies(), (a, b) -> a.name().compareTo(b.name()));
|
||||
InputVerifier nameVerifier = new InputVerifier() {
|
||||
@Override
|
||||
public boolean verify(JComponent input) {
|
||||
String text = ((JTextField)input).getText();
|
||||
String error = checkName(text, row);
|
||||
|
||||
if(error != null) {
|
||||
// TODO show hint
|
||||
Toolkit.getDefaultToolkit().beep();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
table.setCellEditor(row, NAME_COLUMN, new InputVerifierCellEditor(nameVerifier));
|
||||
disableSecondaryOptions(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomizeSelections(int row) {
|
||||
table.randomizeSelection(row, TYPE_COLUMN, false);
|
||||
table.randomizeSelection(row, SHOP_COLUMN);
|
||||
table.randomizeSelection(row, GAME_COLUMN);
|
||||
table.randomizeSelection(row, COUNTRY_COLUMN);
|
||||
table.randomizeSelection(row, REGION_COLUMN);
|
||||
table.randomizeSelection(row, PHRASE_COLUMN);
|
||||
table.randomizeSelection(row, DREAMER_COLUMN);
|
||||
model.setValueAt(findRandomName(row), row, NAME_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSelections(int row) {
|
||||
model.setValueAt(null, row, TYPE_COLUMN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getSelectionIcon(int row) {
|
||||
String name = row == -1 || getType(row) == null ? "none" : getType(row).name().toLowerCase();
|
||||
return ImageLoader.getImage("/sprites/trainers/%s.png".formatted(name));
|
||||
}
|
||||
|
||||
public void loadProfile(Player player) {
|
||||
table.clearSelection();
|
||||
clearSelections();
|
||||
int row = 0;
|
||||
|
||||
for(AvenueVisitor visitor : player.getAvenueVisitors()) {
|
||||
model.setValueAt(visitor.type(), row, TYPE_COLUMN);
|
||||
model.setValueAt(visitor.name(), row, NAME_COLUMN);
|
||||
model.setValueAt(visitor.shopType(), row, SHOP_COLUMN);
|
||||
model.setValueAt(visitor.gameVersion(), row, GAME_COLUMN);
|
||||
Country country = DataManager.getCountry(visitor.countryCode());
|
||||
model.setValueAt(country, row, COUNTRY_COLUMN);
|
||||
|
||||
if(country.hasRegions()) {
|
||||
model.setValueAt(country.regions().get(visitor.stateProvinceCode() - 1), row, REGION_COLUMN);
|
||||
}
|
||||
|
||||
model.setValueAt(visitor.personality(), row, PHRASE_COLUMN);
|
||||
model.setValueAt(DataManager.getSpecies(visitor.dreamerSpecies()), row, DREAMER_COLUMN);
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
public void saveProfile(Player player) {
|
||||
player.setAvenueVisitors(computeSelectionList(i -> new AvenueVisitor(
|
||||
getName(i), getType(i), getShop(i), getGameVersion(i), getCountry(i).id(), getRegion(i), getPhrase(i), getDreamer(i).id()),
|
||||
i -> getType(i) != null));
|
||||
}
|
||||
|
||||
private String findRandomName(int row) {
|
||||
boolean japanese = getGameVersion(row).getLanguageCode() == 1;
|
||||
String[] table = null;
|
||||
|
||||
if(getType(row).isFemale()) {
|
||||
table = japanese ? JAP_NAMES_F : ENG_NAMES_F;
|
||||
} else {
|
||||
table = japanese ? JAP_NAMES_M : ENG_NAMES_M;
|
||||
}
|
||||
|
||||
List<String> existingNames = computeSelectionList(this::getName, i -> getType(row) != null);
|
||||
List<String> options = Stream.of(table).filter(x -> !existingNames.contains(x)).toList();
|
||||
return options.get((int)(Math.random() * options.size())); // Just make sure there are enough name options in the tables...
|
||||
}
|
||||
|
||||
private String checkName(String name, int ignoreRow) {
|
||||
if(name.isBlank()) {
|
||||
return "Visitor name can't be blank.";
|
||||
}
|
||||
|
||||
if(name.length() > 7) {
|
||||
return "Visitor name can't exceed 7 characters.";
|
||||
}
|
||||
|
||||
for(int i = 0; i < VISITOR_COUNT; i++) {
|
||||
if(i != ignoreRow && name.equals(getName(i))) {
|
||||
return "Visitors can't have the same name.";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void disableSecondaryOptions(int row) {
|
||||
table.disableOption(row, NAME_COLUMN);
|
||||
table.disableOption(row, SHOP_COLUMN);
|
||||
table.disableOption(row, GAME_COLUMN);
|
||||
// table.disableOption(row, COUNTRY_COLUMN);
|
||||
// table.disableOption(row, REGION_COLUMN);
|
||||
table.disableOption(row, PHRASE_COLUMN);
|
||||
table.disableOption(row, DREAMER_COLUMN);
|
||||
}
|
||||
|
||||
private AvenueVisitorType getType(int row) {
|
||||
return table.getValueAt(row, TYPE_COLUMN, AvenueVisitorType.class, null);
|
||||
}
|
||||
|
||||
private String getName(int row) {
|
||||
return table.getValueAt(row, NAME_COLUMN, String.class, "");
|
||||
}
|
||||
|
||||
private AvenueShopType getShop(int row) {
|
||||
return table.getValueAt(row, SHOP_COLUMN, AvenueShopType.class, null);
|
||||
}
|
||||
|
||||
private GameVersion getGameVersion(int row) {
|
||||
return table.getValueAt(row, GAME_COLUMN, GameVersion.class, null);
|
||||
}
|
||||
|
||||
private Country getCountry(int row) {
|
||||
return table.getValueAt(row, COUNTRY_COLUMN, Country.class, null);
|
||||
}
|
||||
|
||||
private int getRegion(int row) {
|
||||
Region region = table.getValueAt(row, REGION_COLUMN, Region.class, null);
|
||||
return region == null ? 0 : region.id();
|
||||
}
|
||||
|
||||
private int getPhrase(int row) {
|
||||
return table.getValueAt(row, PHRASE_COLUMN, Integer.class, 0);
|
||||
}
|
||||
|
||||
private PkmnSpecies getDreamer(int row) {
|
||||
return table.getValueAt(row, DREAMER_COLUMN, PkmnSpecies.class, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package entralinked.gui;
|
||||
package entralinked.gui.view;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
|
|
@ -13,14 +13,15 @@ import java.net.URL;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.JTextPane;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.text.AttributeSet;
|
||||
|
|
@ -34,8 +35,10 @@ import javax.swing.text.StyleContext;
|
|||
import org.apache.logging.log4j.Level;
|
||||
|
||||
import com.formdev.flatlaf.intellijthemes.FlatOneDarkIJTheme;
|
||||
import com.formdev.flatlaf.util.ColorFunctions;
|
||||
|
||||
import entralinked.Entralinked;
|
||||
import entralinked.gui.panels.DashboardPanel;
|
||||
import entralinked.utility.ConsumerAppender;
|
||||
import entralinked.utility.SwingUtility;
|
||||
|
||||
|
|
@ -49,29 +52,21 @@ public class MainView {
|
|||
public static final Color TEXT_COLOR_ERROR = Color.RED.darker();
|
||||
private final StyleContext styleContext = StyleContext.getDefaultStyleContext();
|
||||
private final AttributeSet fontAttribute = styleContext.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.FontFamily, "Consolas");
|
||||
private final JButton dashboardButton;
|
||||
private final JLabel statusLabel;
|
||||
|
||||
public MainView(Entralinked entralinked) {
|
||||
// Set look and feel
|
||||
FlatOneDarkIJTheme.setup();
|
||||
UIManager.getDefaults().put("Component.focusedBorderColor", UIManager.get("Component.borderColor"));
|
||||
|
||||
// Create dashboard button
|
||||
dashboardButton = new JButton("Open User Dashboard");
|
||||
dashboardButton.setEnabled(false);
|
||||
dashboardButton.setFocusable(false);
|
||||
dashboardButton.addActionListener(event -> {
|
||||
openUrl("http://127.0.0.1/dashboard/profile.html");
|
||||
});
|
||||
UIManager.put("Table.alternateRowColor", ColorFunctions.lighten(UIManager.getColor("Table.background"), 0.05F));
|
||||
|
||||
// Create status label
|
||||
statusLabel = new JLabel("Entralinked is starting...", JLabel.CENTER);
|
||||
statusLabel = new JLabel("Servers are starting, please wait a bit...", JLabel.CENTER);
|
||||
|
||||
// Create footer panel
|
||||
JPanel footerPanel = new JPanel(new BorderLayout());
|
||||
footerPanel.add(statusLabel, BorderLayout.CENTER);
|
||||
footerPanel.add(dashboardButton, BorderLayout.LINE_END);
|
||||
footerPanel.setBorder(BorderFactory.createEmptyBorder(2, 0, 5, 0));
|
||||
|
||||
// Create console output
|
||||
JTextPane consoleOutputPane = new JTextPane() {
|
||||
|
|
@ -85,7 +80,7 @@ public class MainView {
|
|||
return getUI().getPreferredSize(this);
|
||||
};
|
||||
};
|
||||
consoleOutputPane.setFont(new Font("Consola", Font.PLAIN, 12));
|
||||
consoleOutputPane.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||
consoleOutputPane.setEditable(false);
|
||||
((DefaultCaret)consoleOutputPane.getCaret()).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
|
||||
|
||||
|
|
@ -109,7 +104,12 @@ public class MainView {
|
|||
// Create main panel
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.add(scrollPane, BorderLayout.CENTER);
|
||||
panel.add(footerPanel, BorderLayout.PAGE_END);
|
||||
panel.add(footerPanel, BorderLayout.PAGE_END);
|
||||
|
||||
// Create tabbed pane
|
||||
JTabbedPane tabbedPane = new JTabbedPane();
|
||||
tabbedPane.addTab("Console", panel);
|
||||
tabbedPane.addTab("Dashboard", new DashboardPanel(entralinked));
|
||||
|
||||
// Create window
|
||||
JFrame frame = new JFrame("Entralinked");
|
||||
|
|
@ -118,7 +118,13 @@ public class MainView {
|
|||
JMenuBar menuBar = new JMenuBar();
|
||||
JMenu helpMenu = new JMenu("Help");
|
||||
helpMenu.add(SwingUtility.createAction("Update PID (Error 60000)", () -> new PidToolDialog(entralinked, frame)));
|
||||
helpMenu.add(SwingUtility.createAction("GitHub", () -> openUrl("https://github.com/kuroppoi/entralinked")));
|
||||
helpMenu.add(SwingUtility.createAction("GitHub", () -> {
|
||||
try {
|
||||
Desktop.getDesktop().browse(new URL("https://github.com/kuroppoi/entralinked").toURI());
|
||||
} catch(IOException | URISyntaxException e) {
|
||||
SwingUtility.showExceptionInfo(frame, "Failed to open URL.", e);
|
||||
}
|
||||
}));
|
||||
menuBar.add(helpMenu);
|
||||
|
||||
// Set window properties
|
||||
|
|
@ -126,8 +132,7 @@ public class MainView {
|
|||
@Override
|
||||
public void windowClosing(WindowEvent event) {
|
||||
// Update status
|
||||
dashboardButton.setEnabled(false);
|
||||
statusLabel.setText("Entralinked is shutting down ...");
|
||||
statusLabel.setText("Servers are shutting down, please wait a bit...");
|
||||
|
||||
// Run asynchronously so it doesn't just awkwardly freeze
|
||||
// Still scuffed but better than nothing I guess
|
||||
|
|
@ -142,31 +147,16 @@ public class MainView {
|
|||
new ImageIcon(getClass().getResource("/icon-32x.png")).getImage(),
|
||||
new ImageIcon(getClass().getResource("/icon-16x.png")).getImage()));
|
||||
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||
frame.setMinimumSize(new Dimension(512, 288));
|
||||
frame.setJMenuBar(menuBar);
|
||||
frame.add(panel);
|
||||
frame.add(tabbedPane);
|
||||
frame.getContentPane().setPreferredSize(new Dimension(733, 463));
|
||||
frame.pack();
|
||||
frame.setMinimumSize(frame.getSize());
|
||||
frame.setLocationRelativeTo(null);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
public void setDashboardButtonEnabled(boolean enabled) {
|
||||
dashboardButton.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public void setStatusLabelText(String text) {
|
||||
statusLabel.setText(text);
|
||||
}
|
||||
|
||||
private void openUrl(String url) {
|
||||
Desktop desktop = Desktop.getDesktop();
|
||||
|
||||
if(desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
|
||||
try {
|
||||
desktop.browse(new URL(url).toURI());
|
||||
} catch(IOException | URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package entralinked.gui;
|
||||
package entralinked.gui.view;
|
||||
|
||||
import java.awt.GridBagLayout;
|
||||
import java.util.regex.Pattern;
|
||||
|
|
@ -60,7 +60,8 @@ public class PidToolDialog {
|
|||
|
||||
// Make sure user exists
|
||||
if(user == null) {
|
||||
JOptionPane.showMessageDialog(dialog, "This Wi-Fi Connection ID does not exist.", "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
JOptionPane.showMessageDialog(dialog, "This Wi-Fi Connection ID does not exist.\n"
|
||||
+ "If you haven't attempted to connect yet, please do that first.", "Attention", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -5,11 +5,21 @@ import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
|
|||
public enum AvenueShopType {
|
||||
|
||||
@JsonEnumDefaultValue
|
||||
RAFFLE,
|
||||
FLORIST,
|
||||
SALON,
|
||||
ANTIQUE,
|
||||
DOJO,
|
||||
CAFE,
|
||||
MARKET
|
||||
RAFFLE("Raffle Shop"),
|
||||
FLORIST("Flower Shop"),
|
||||
SALON("Beauty Salon"),
|
||||
ANTIQUE("Antique Shop"),
|
||||
DOJO("Dojo"),
|
||||
CAFE("Café"),
|
||||
MARKET("Market");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private AvenueShopType(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,47 +6,53 @@ public enum AvenueVisitorType {
|
|||
|
||||
// 0
|
||||
@JsonEnumDefaultValue
|
||||
YOUNGSTER(0),
|
||||
LASS(0, true),
|
||||
YOUNGSTER("Youngster", 0),
|
||||
LASS("Lass", 0, true),
|
||||
|
||||
// 1
|
||||
ACE_TRAINER_MALE(1),
|
||||
ACE_TRAINER_FEMALE(1, true),
|
||||
ACE_TRAINER_MALE("Ace Trainer♂", 1),
|
||||
ACE_TRAINER_FEMALE("Ace Trainer♀", 1, true),
|
||||
|
||||
// 2
|
||||
RANGER_MALE(2),
|
||||
RANGER_FEMALE(2, true),
|
||||
RANGER_MALE("Pokémon Ranger♂", 2),
|
||||
RANGER_FEMALE("Pokémon Ranger♀", 2, true),
|
||||
|
||||
// 3
|
||||
BREEDER_MALE(3),
|
||||
BREEDER_FEMALE(3, true),
|
||||
BREEDER_MALE("Pokémon Breeder♂", 3),
|
||||
BREEDER_FEMALE("Pokémon Breeder♀", 3, true),
|
||||
|
||||
// 4
|
||||
SCIENTIST_MALE(4),
|
||||
SCIENTIST_FEMALE(4, true),
|
||||
SCIENTIST_MALE("Scientist♂", 4),
|
||||
SCIENTIST_FEMALE("Scientist♀", 4, true),
|
||||
|
||||
// 5
|
||||
HIKER(5),
|
||||
PARASOL_LADY(5, true),
|
||||
HIKER("Hiker♂", 5),
|
||||
PARASOL_LADY("Parasol Lady", 5, true),
|
||||
|
||||
// 6
|
||||
ROUGHNECK(6),
|
||||
NURSE(6, true),
|
||||
ROUGHNECK("Roughneck", 6),
|
||||
NURSE("Nurse", 6, true),
|
||||
|
||||
// 7
|
||||
PRESCHOOLER_MALE(7),
|
||||
PRESCHOOLER_FEMALE(7, true);
|
||||
PRESCHOOLER_MALE("Preschooler♂", 7),
|
||||
PRESCHOOLER_FEMALE("Preschooler♀", 7, true);
|
||||
|
||||
private final String displayName;
|
||||
private final int clientId;
|
||||
private final boolean female;
|
||||
|
||||
private AvenueVisitorType(int clientId, boolean female) {
|
||||
private AvenueVisitorType(String displayName, int clientId, boolean female) {
|
||||
this.displayName = displayName;
|
||||
this.clientId = clientId;
|
||||
this.female = female;
|
||||
}
|
||||
|
||||
private AvenueVisitorType(int clientId) {
|
||||
this(clientId, false);
|
||||
private AvenueVisitorType(String displayName, int clientId) {
|
||||
this(displayName, clientId, false);
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public int getClientId() {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,17 @@ import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
|
|||
public enum PkmnGender {
|
||||
|
||||
@JsonEnumDefaultValue
|
||||
MALE,
|
||||
FEMALE,
|
||||
GENDERLESS;
|
||||
MALE("Male"),
|
||||
FEMALE("Female"),
|
||||
GENDERLESS("Genderless");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private PkmnGender(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,33 +5,44 @@ import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
|
|||
public enum PkmnNature {
|
||||
|
||||
@JsonEnumDefaultValue
|
||||
HARDY,
|
||||
LONELY,
|
||||
BRAVE,
|
||||
ADAMANT,
|
||||
NAUGHTY,
|
||||
BOLD,
|
||||
DOCILE,
|
||||
RELAXED,
|
||||
IMPISH,
|
||||
LAX,
|
||||
TIMID,
|
||||
HASTY,
|
||||
SERIOUS,
|
||||
JOLLY,
|
||||
NAIVE,
|
||||
MODEST,
|
||||
MILD,
|
||||
QUIET,
|
||||
BASHFUL,
|
||||
RASH,
|
||||
CALM,
|
||||
GENTLE,
|
||||
SASSY,
|
||||
CAREFUL,
|
||||
QUIRKY;
|
||||
HARDY("Hardy"),
|
||||
LONELY("Lonely"),
|
||||
BRAVE("Brave"),
|
||||
ADAMANT("Adamant"),
|
||||
NAUGHTY("Naughty"),
|
||||
BOLD("Bold"),
|
||||
DOCILE("Docile"),
|
||||
RELAXED("Relaxed"),
|
||||
IMPISH("Impish"),
|
||||
LAX("Lax"),
|
||||
TIMID("Timid"),
|
||||
HASTY("Hasty"),
|
||||
SERIOUS("Serious"),
|
||||
JOLLY("Jolly"),
|
||||
NAIVE("Naive"),
|
||||
MODEST("Modest"),
|
||||
MILD("Mild"),
|
||||
QUIET("Quiet"),
|
||||
BASHFUL("Bashful"),
|
||||
RASH("Rash"),
|
||||
CALM("Calm"),
|
||||
GENTLE("Gentle"),
|
||||
SASSY("Sassy"),
|
||||
CAREFUL("Careful"),
|
||||
QUIRKY("Quirky");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private PkmnNature(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public static PkmnNature valueOf(int index) {
|
||||
return index >= 0 && index < values().length ? values()[index] : null;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,44 +8,54 @@ public enum DreamAnimation {
|
|||
* Look around, but stay in the same position.
|
||||
*/
|
||||
@JsonEnumDefaultValue
|
||||
LOOK_AROUND,
|
||||
LOOK_AROUND("Look around"),
|
||||
|
||||
/**
|
||||
* Walk around, but never change direction without moving a step in that direction.
|
||||
*/
|
||||
WALK_AROUND,
|
||||
WALK_AROUND("Walk around"),
|
||||
|
||||
/**
|
||||
* Walk around and occasionally change direction without moving.
|
||||
*/
|
||||
WALK_LOOK_AROUND,
|
||||
WALK_LOOK_AROUND("Walk and look around"),
|
||||
|
||||
/**
|
||||
* Only walk up and down.
|
||||
*/
|
||||
WALK_VERTICALLY,
|
||||
WALK_VERTICALLY("Walk up and down"),
|
||||
|
||||
/**
|
||||
* Only walk left and right.
|
||||
*/
|
||||
WALK_HORIZONTALLY,
|
||||
WALK_HORIZONTALLY("Walk left and right"),
|
||||
|
||||
/**
|
||||
* Only walk left and right, and occasionally change direction without moving.
|
||||
*/
|
||||
WALK_LOOK_HORIZONTALLY,
|
||||
WALK_LOOK_HORIZONTALLY("Walk left and right and look around"),
|
||||
|
||||
/**
|
||||
* Continuously spin right.
|
||||
*/
|
||||
SPIN_RIGHT,
|
||||
SPIN_RIGHT("Spin right"),
|
||||
|
||||
/**
|
||||
* Continuously spin left.
|
||||
*/
|
||||
SPIN_LEFT;
|
||||
SPIN_LEFT("Spin left");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private DreamAnimation(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public static DreamAnimation valueOf(int index) {
|
||||
return index >= 0 && index < values().length ? values()[index] : null;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ public class Player {
|
|||
private String musical;
|
||||
private String customCGearSkin;
|
||||
private String customDexSkin;
|
||||
private String customMusical;
|
||||
private File dataDirectory;
|
||||
|
||||
public Player(String gameSyncId) {
|
||||
|
|
@ -152,6 +153,14 @@ public class Player {
|
|||
return customDexSkin;
|
||||
}
|
||||
|
||||
public void setCustomMusical(String customMusical) {
|
||||
this.customMusical = customMusical;
|
||||
}
|
||||
|
||||
public String getCustomMusical() {
|
||||
return customMusical;
|
||||
}
|
||||
|
||||
// IO stuff
|
||||
|
||||
public void setDataDirectory(File dataDirectory) {
|
||||
|
|
@ -177,4 +186,8 @@ public class Player {
|
|||
public File getDexSkinFile() {
|
||||
return new File(dataDirectory, "zukan.bin");
|
||||
}
|
||||
|
||||
public File getMusicalFile() {
|
||||
return new File(dataDirectory, "musical.bin");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,16 +23,17 @@ public record PlayerDto(
|
|||
String musical,
|
||||
String customCGearSkin,
|
||||
String customDexSkin,
|
||||
String customMusical,
|
||||
int levelsGained,
|
||||
@JsonDeserialize(contentAs = DreamEncounter.class) Collection<DreamEncounter> encounters,
|
||||
@JsonDeserialize(contentAs = DreamItem.class) Collection<DreamItem> items,
|
||||
@JsonDeserialize(contentAs = AvenueVisitor.class) Collection<AvenueVisitor> avenueVisitors) {
|
||||
|
||||
public PlayerDto(Player player) {
|
||||
this(player.getGameSyncId(), player.getGameVersion(), player.getStatus(), player.getDreamerInfo(),
|
||||
this(player.getGameSyncId(), player.getGameVersion(), player.getStatus(), player.getDreamerInfo(),
|
||||
player.getCGearSkin(), player.getDexSkin(), player.getMusical(), player.getCustomCGearSkin(),
|
||||
player.getCustomDexSkin(), player.getLevelsGained(), player.getEncounters(), player.getItems(),
|
||||
player.getAvenueVisitors());
|
||||
player.getCustomDexSkin(), player.getCustomMusical(), player.getLevelsGained(), player.getEncounters(),
|
||||
player.getItems(), player.getAvenueVisitors());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -48,6 +49,7 @@ public record PlayerDto(
|
|||
player.setMusical(musical);
|
||||
player.setCustomCGearSkin(customCGearSkin);
|
||||
player.setCustomDexSkin(customDexSkin);
|
||||
player.setCustomMusical(customMusical);
|
||||
player.setLevelsGained(levelsGained);
|
||||
player.setEncounters(encounters == null ? Collections.emptyList() : encounters);
|
||||
player.setItems(items == null ? Collections.emptyList() : items);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import javax.imageio.ImageIO;
|
|||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bouncycastle.util.Arrays;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
|
|
@ -35,7 +34,6 @@ import entralinked.model.player.Player;
|
|||
import entralinked.model.player.PlayerManager;
|
||||
import entralinked.model.player.PlayerStatus;
|
||||
import entralinked.network.http.HttpHandler;
|
||||
import entralinked.utility.ColorUtility;
|
||||
import entralinked.utility.Crc16;
|
||||
import entralinked.utility.GsidUtility;
|
||||
import entralinked.utility.LEOutputStream;
|
||||
|
|
@ -49,6 +47,8 @@ import io.javalin.json.JavalinJackson;
|
|||
|
||||
/**
|
||||
* HTTP handler for requests made to the user dashboard.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public class DashboardHandler implements HttpHandler {
|
||||
|
||||
|
|
@ -310,29 +310,8 @@ public class DashboardHandler implements HttpHandler {
|
|||
previewImage = TiledImageUtility.readCGearSkin(new ByteArrayInputStream(skinBytes), offsetIndices);
|
||||
break;
|
||||
case "ZUKAN":
|
||||
// Generate some background colors based roughly on what's in the image
|
||||
int[] backgroundColors = Arrays.copyOf(TiledImageUtility.DEFAULT_DEX_BACKGROUND_COLORS, 64);
|
||||
int backgroundColor = image.getRGB(0, 0);
|
||||
int dexColor = image.getRGB(0, 134);
|
||||
int buttonColor = image.getRGB(128, 134);
|
||||
|
||||
// Background colors (58, 59, 60)
|
||||
for(int i = 0; i < 3; i++) {
|
||||
backgroundColors[i + 58] = ColorUtility.multiplyColor(backgroundColor, 0.7 + i * 0.15);
|
||||
}
|
||||
|
||||
// Pokédex colors (48, 49, 50, 51, 52)
|
||||
for(int i = 0; i < 5; i++) {
|
||||
backgroundColors[i + 48] = ColorUtility.multiplyColor(dexColor, 1.15 - i * 0.15);
|
||||
}
|
||||
|
||||
// Pokédex button colors (53, 54, 55, 56)
|
||||
for(int i = 0; i < 4; i++) {
|
||||
backgroundColors[i + 53] = ColorUtility.multiplyColor(buttonColor, 0.55 + i * 0.15);
|
||||
}
|
||||
|
||||
// Process skin data
|
||||
TiledImageUtility.writeDexSkin(byteOutputStream, image, backgroundColors);
|
||||
TiledImageUtility.writeDexSkin(byteOutputStream, image, TiledImageUtility.generateBackgroundColors(image));
|
||||
skinBytes = byteOutputStream.toByteArray();
|
||||
previewImage = TiledImageUtility.readDexSkin(new ByteArrayInputStream(skinBytes), true);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import entralinked.model.player.DreamEncounter;
|
|||
import entralinked.model.player.DreamItem;
|
||||
import entralinked.model.player.Player;
|
||||
|
||||
@Deprecated
|
||||
public record DashboardProfileMessage(
|
||||
String gameVersion,
|
||||
String dreamerSprite,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import entralinked.model.avenue.AvenueVisitor;
|
|||
import entralinked.model.player.DreamEncounter;
|
||||
import entralinked.model.player.DreamItem;
|
||||
|
||||
@Deprecated
|
||||
public record DashboardProfileUpdateRequest(
|
||||
@JsonProperty(required = true) @JsonDeserialize(contentAs = DreamEncounter.class) List<DreamEncounter> encounters,
|
||||
@JsonProperty(required = true) @JsonDeserialize(contentAs = DreamItem.class) List<DreamItem> items,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package entralinked.network.http.dashboard;
|
||||
|
||||
@Deprecated
|
||||
public record DashboardStatusMessage(String message, boolean error) {
|
||||
|
||||
public DashboardStatusMessage(String message) {
|
||||
|
|
|
|||
|
|
@ -220,8 +220,10 @@ public class PglHandler implements HttpHandler {
|
|||
String cgearType = player.getGameVersion().isVersion2() ? "CGEAR2" : "CGEAR";
|
||||
String cgearSkin = player.getCGearSkin();
|
||||
String dexSkin = player.getDexSkin();
|
||||
String musical = player.getMusical();
|
||||
int cgearSkinIndex = 0;
|
||||
int dexSkinIndex = 0;
|
||||
int musicalIndex = 0;
|
||||
|
||||
// Create or remove custom C-Gear skin DLC override
|
||||
if("custom".equals(cgearSkin)) {
|
||||
|
|
@ -243,6 +245,17 @@ public class PglHandler implements HttpHandler {
|
|||
user.removeDlcOverride("ZUKAN");
|
||||
}
|
||||
|
||||
// Create or remove custom musical DLC override
|
||||
if("custom".equals(musical)) {
|
||||
musicalIndex = 1;
|
||||
File file = player.getMusicalFile();
|
||||
user.setDlcOverride("MUSICAL", new Dlc(file.getAbsolutePath(),
|
||||
"custom", "IRAO", "MUSICAL", musicalIndex, (int)file.length(), 0, true));
|
||||
} else {
|
||||
musicalIndex = dlcList.getDlcIndex("IRAO", "MUSICAL", musical);
|
||||
user.removeDlcOverride("MUSICAL");
|
||||
}
|
||||
|
||||
// When waking up a Pokémon, these 4 bytes are written to 0x1D304 in the save file.
|
||||
// If the bytes in the game's save file match the new bytes, they will be set to 0x00000000
|
||||
// and no content will be downloaded.
|
||||
|
|
@ -264,7 +277,7 @@ public class PglHandler implements HttpHandler {
|
|||
// Write misc stuff and DLC information
|
||||
outputStream.writeShort(player.getLevelsGained());
|
||||
outputStream.write(0); // Unknown
|
||||
outputStream.write(dlcList.getDlcIndex("IRAO", "MUSICAL", player.getMusical()));
|
||||
outputStream.write(musicalIndex);
|
||||
outputStream.write(cgearSkinIndex);
|
||||
outputStream.write(dexSkinIndex);
|
||||
outputStream.write(decorList.isEmpty() ? 0 : 1); // Seems to be a flag for indicating whether or not decor data is present
|
||||
|
|
@ -329,7 +342,7 @@ public class PglHandler implements HttpHandler {
|
|||
outputStream.writeInt(1); // [20] Ignores if 0
|
||||
outputStream.write(visitor.countryCode());
|
||||
outputStream.write(visitor.stateProvinceCode());
|
||||
outputStream.write(0); // [26] Ignores if 1
|
||||
outputStream.write(visitor.gameVersion().getLanguageCode()); // 99% sure this is the lang code because 1 seems to be ignored ONLY if country code isn't Japan (plus it's right above the rom code)
|
||||
outputStream.write(visitor.gameVersion().getRomCode()); // Affects shop stock
|
||||
outputStream.write(visitor.type().isFemale() ? 1 : 0);
|
||||
outputStream.write(0); // [29] Does.. something
|
||||
|
|
|
|||
|
|
@ -1,14 +1,41 @@
|
|||
package entralinked.utility;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
public class SwingUtility {
|
||||
|
||||
private static final Set<String> ignoredMessages = new HashSet<>();
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static Action createAction(String name, Icon icon, Runnable handler) {
|
||||
AbstractAction action = new AbstractAction(name, icon) {
|
||||
|
|
@ -55,4 +82,113 @@ public class SwingUtility {
|
|||
constraints.ipady = paddingY;
|
||||
return constraints;
|
||||
}
|
||||
|
||||
public static void setTextFieldToggle(JTextField textField, boolean selected) {
|
||||
for(Component component : textField.getComponents()) {
|
||||
if(component instanceof JToggleButton) {
|
||||
JToggleButton button = (JToggleButton)component;
|
||||
|
||||
if(button.isSelected() != selected) {
|
||||
button.doClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO find a good unicode font maybe?
|
||||
public static Font findSupportingFont(String text, Font def) {
|
||||
GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
|
||||
for(Font font : graphicsEnvironment.getAllFonts()) {
|
||||
if(font.canDisplayUpTo(text) == -1) {
|
||||
return new Font(font.getName(), def.getStyle(), def.getSize());
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
public static JLabel createButtonLabel(String text, Runnable actionHandler) {
|
||||
JLabel label = new JLabel("<html><u>%s</u></html>".formatted(text));
|
||||
label.putClientProperty(FlatClientProperties.STYLE, "font: -1");
|
||||
label.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent event) {
|
||||
if(SwingUtilities.isLeftMouseButton(event)) {
|
||||
actionHandler.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
return label;
|
||||
}
|
||||
|
||||
public static void showIgnorableHint(Component parentComponent, String message, String title, int messageType) {
|
||||
synchronized(ignoredMessages) {
|
||||
// Do nothing if message has been ignored
|
||||
if(ignoredMessages.contains(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create ignore checkbox
|
||||
JCheckBox checkBox = new JCheckBox("Don't show this again");
|
||||
JOptionPane.showMessageDialog(parentComponent, createIgnorableDialogPanel(message, checkBox), title, messageType);
|
||||
|
||||
// Add to ignore list if checkbox is selected
|
||||
if(checkBox.isSelected()) {
|
||||
ignoredMessages.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean showIgnorableConfirmDialog(Component parentComponent, String message, String title) {
|
||||
synchronized(ignoredMessages) {
|
||||
// Do nothing if message has been ignored
|
||||
if(ignoredMessages.contains(message)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
JCheckBox checkBox = new JCheckBox("Don't ask this again");
|
||||
int result = JOptionPane.showConfirmDialog(parentComponent, createIgnorableDialogPanel(message, checkBox), title, JOptionPane.YES_NO_OPTION);
|
||||
|
||||
// Add to ignore list if checkbox is selected
|
||||
if(checkBox.isSelected()) {
|
||||
ignoredMessages.add(message);
|
||||
}
|
||||
|
||||
return result == JOptionPane.YES_OPTION;
|
||||
}
|
||||
}
|
||||
|
||||
private static JPanel createIgnorableDialogPanel(String message, JCheckBox checkBox) {
|
||||
JPanel panel = new JPanel(new MigLayout("insets 0"));
|
||||
panel.add(new JLabel("<html>%s</html>".formatted(message.replace("\n", "<br/>"))), "wrap"); // TODO no idea how JOptionPane does line breaks
|
||||
panel.add(checkBox, "gapy 8");
|
||||
return panel;
|
||||
}
|
||||
|
||||
public static void showExceptionInfo(Component parentComponent, String message, Throwable throwable) {
|
||||
// Create stacktrace string
|
||||
StringWriter writer = new StringWriter();
|
||||
throwable.printStackTrace(new PrintWriter(writer));
|
||||
|
||||
// Create text area
|
||||
JTextArea area = new JTextArea(writer.toString());
|
||||
area.setBorder(BorderFactory.createEmptyBorder(8, 8, 0, 0));
|
||||
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||
area.setEditable(false);
|
||||
|
||||
// Create scroll pane
|
||||
int height = Math.min(200, area.getFontMetrics(area.getFont()).getHeight() * area.getLineCount() + 10);
|
||||
JScrollPane scrollPane = new JScrollPane(area);
|
||||
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
scrollPane.setPreferredSize(new Dimension(600, height));
|
||||
scrollPane.setMaximumSize(scrollPane.getPreferredSize());
|
||||
|
||||
// Create dialog
|
||||
String label = String.format("<html><b>%s</b><br>Exception details:<br><br></html>", message);
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.add(new JLabel(label), BorderLayout.PAGE_START);
|
||||
panel.add(scrollPane);
|
||||
JOptionPane.showMessageDialog(parentComponent, panel, "An error has occured", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import java.awt.image.BufferedImage;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
|
@ -363,6 +364,33 @@ public class TiledImageUtility {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates Pokédex skin background colors for the specified image.
|
||||
*/
|
||||
public static int[] generateBackgroundColors(BufferedImage image) {
|
||||
int[] backgroundColors = Arrays.copyOf(DEFAULT_DEX_BACKGROUND_COLORS, 64);
|
||||
int backgroundColor = image.getRGB(0, 0);
|
||||
int dexColor = image.getRGB(0, 134);
|
||||
int buttonColor = image.getRGB(128, 134);
|
||||
|
||||
// Background colors (58, 59, 60)
|
||||
for(int i = 0; i < 3; i++) {
|
||||
backgroundColors[i + 58] = ColorUtility.multiplyColor(backgroundColor, 0.7 + i * 0.15);
|
||||
}
|
||||
|
||||
// Pokédex colors (48, 49, 50, 51, 52)
|
||||
for(int i = 0; i < 5; i++) {
|
||||
backgroundColors[i + 48] = ColorUtility.multiplyColor(dexColor, 1.15 - i * 0.15);
|
||||
}
|
||||
|
||||
// Pokédex button colors (53, 54, 55, 56)
|
||||
for(int i = 0; i < 4; i++) {
|
||||
backgroundColors[i + 53] = ColorUtility.multiplyColor(buttonColor, 0.55 + i * 0.15);
|
||||
}
|
||||
|
||||
return backgroundColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The index of the element in the array, or {@code -1} if no such element exists.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -266,6 +266,30 @@
|
|||
<option value="WHITE_ENGLISH">White Version</option>
|
||||
<option value="BLACK_2_ENGLISH">Black Version 2</option>
|
||||
<option value="WHITE_2_ENGLISH">White Version 2</option>
|
||||
<option value="BLACK_FRENCH">Version Noire</option>
|
||||
<option value="WHITE_FRENCH">Version Blanche</option>
|
||||
<option value="BLACK_2_FRENCH">Version Noire 2</option>
|
||||
<option value="WHITE_2_FRENCH">Version Blanche 2</option>
|
||||
<option value="BLACK_ITALIAN">Versione Nera</option>
|
||||
<option value="WHITE_ITALIAN">Versione Bianca</option>
|
||||
<option value="BLACK_2_ITALIAN">Versione Nera 2</option>
|
||||
<option value="WHITE_2_ITALIAN">Versione Bianca 2</option>
|
||||
<option value="BLACK_GERMAN">Schwarze Edition</option>
|
||||
<option value="WHITE_GERMAN">Weisse Edition</option>
|
||||
<option value="BLACK_2_GERMAN">Schwarze Edition 2</option>
|
||||
<option value="WHITE_2_GERMAN">Weisse Edition 2</option>
|
||||
<option value="BLACK_SPANISH">Edicion Negra</option>
|
||||
<option value="WHITE_SPANISH">Edicion Blanca</option>
|
||||
<option value="BLACK_2_SPANISH">Edicion Negra 2</option>
|
||||
<option value="WHITE_2_SPANISH">Edicion Blanca 2</option>
|
||||
<option value="BLACK_JAPANESE">ブラック</option>
|
||||
<option value="WHITE_JAPANESE">ホワイト</option>
|
||||
<option value="BLACK_2_JAPANESE">ブラック2</option>
|
||||
<option value="WHITE_2_JAPANESE">ホワイト2</option>
|
||||
<option value="BLACK_KOREAN">블랙</option>
|
||||
<option value="WHITE_KOREAN">화이트</option>
|
||||
<option value="BLACK_2_KOREAN">블랙2</option>
|
||||
<option value="WHITE_2_KOREAN">화이트2</option>
|
||||
</select>
|
||||
<label for="visitor-form-region">Country</label>
|
||||
<select id="visitor-form-region" name="region" value="1">
|
||||
|
|
|
|||
166
src/main/resources/data/abilities.json
Normal file
166
src/main/resources/data/abilities.json
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
{
|
||||
"1": "Stench",
|
||||
"2": "Drizzle",
|
||||
"3": "Speed Boost",
|
||||
"4": "Battle Armor",
|
||||
"5": "Sturdy",
|
||||
"6": "Damp",
|
||||
"7": "Limber",
|
||||
"8": "Sand Veil",
|
||||
"9": "Static",
|
||||
"10": "Volt Absorb",
|
||||
"11": "Water Absorb",
|
||||
"12": "Oblivious",
|
||||
"13": "Cloud Nine",
|
||||
"14": "Compound Eyes",
|
||||
"15": "Insomnia",
|
||||
"16": "Color Change",
|
||||
"17": "Immunity",
|
||||
"18": "Flash Fire",
|
||||
"19": "Shield Dust",
|
||||
"20": "Own Tempo",
|
||||
"21": "Suction Cups",
|
||||
"22": "Intimidate",
|
||||
"23": "Shadow Tag",
|
||||
"24": "Rough Skin",
|
||||
"25": "Wonder Guard",
|
||||
"26": "Levitate",
|
||||
"27": "Effect Spore",
|
||||
"28": "Synchronize",
|
||||
"29": "Clear Body",
|
||||
"30": "Natural Cure",
|
||||
"31": "Lightning Rod",
|
||||
"32": "Serene Grace",
|
||||
"33": "Swift Swim",
|
||||
"34": "Chlorophyll",
|
||||
"35": "Illuminate",
|
||||
"36": "Trace",
|
||||
"37": "Huge Power",
|
||||
"38": "Poison Point",
|
||||
"39": "Inner Focus",
|
||||
"40": "Magma Armor",
|
||||
"41": "Water Veil",
|
||||
"42": "Magnet Pull",
|
||||
"43": "Soundproof",
|
||||
"44": "Rain Dish",
|
||||
"45": "Sand Stream",
|
||||
"46": "Pressure",
|
||||
"47": "Thick Fat",
|
||||
"48": "Early Bird",
|
||||
"49": "Flame Body",
|
||||
"50": "Run Away",
|
||||
"51": "Keen Eye",
|
||||
"52": "Hyper Cutter",
|
||||
"53": "Pickup",
|
||||
"54": "Truant",
|
||||
"55": "Hustle",
|
||||
"56": "Cute Charm",
|
||||
"57": "Plus",
|
||||
"58": "Minus",
|
||||
"59": "Forecast",
|
||||
"60": "Sticky Hold",
|
||||
"61": "Shed Skin",
|
||||
"62": "Guts",
|
||||
"63": "Marvel Scale",
|
||||
"64": "Liquid Ooze",
|
||||
"65": "Overgrow",
|
||||
"66": "Blaze",
|
||||
"67": "Torrent",
|
||||
"68": "Swarm",
|
||||
"69": "Rock Head",
|
||||
"70": "Drought",
|
||||
"71": "Arena Trap",
|
||||
"72": "Vital Spirit",
|
||||
"73": "White Smoke",
|
||||
"74": "Pure Power",
|
||||
"75": "Shell Armor",
|
||||
"76": "Air Lock",
|
||||
"77": "Tangled Feet",
|
||||
"78": "Motor Drive",
|
||||
"79": "Rivalry",
|
||||
"80": "Steadfast",
|
||||
"81": "Snow Cloak",
|
||||
"82": "Gluttony",
|
||||
"83": "Anger Point",
|
||||
"84": "Unburden",
|
||||
"85": "Heatproof",
|
||||
"86": "Simple",
|
||||
"87": "Dry Skin",
|
||||
"88": "Download",
|
||||
"89": "Iron Fist",
|
||||
"90": "Poison Heal",
|
||||
"91": "Adaptability",
|
||||
"92": "Skill Link",
|
||||
"93": "Hydration",
|
||||
"94": "Solar Power",
|
||||
"95": "Quick Feet",
|
||||
"96": "Normalize",
|
||||
"97": "Sniper",
|
||||
"98": "Magic Guard",
|
||||
"99": "No Guard",
|
||||
"100": "Stall",
|
||||
"101": "Technician",
|
||||
"102": "Leaf Guard",
|
||||
"103": "Klutz",
|
||||
"104": "Mold Breaker",
|
||||
"105": "Super Luck",
|
||||
"106": "Aftermath",
|
||||
"107": "Anticipation",
|
||||
"108": "Forewarn",
|
||||
"109": "Unaware",
|
||||
"110": "Tinted Lens",
|
||||
"111": "Filter",
|
||||
"112": "Slow Start",
|
||||
"113": "Scrappy",
|
||||
"114": "Storm Drain",
|
||||
"115": "Ice Body",
|
||||
"116": "Solid Rock",
|
||||
"117": "Snow Warning",
|
||||
"118": "Honey Gather",
|
||||
"119": "Frisk",
|
||||
"120": "Reckless",
|
||||
"121": "Multitype",
|
||||
"122": "Flower Gift",
|
||||
"123": "Bad Dreams",
|
||||
"124": "Pickpocket",
|
||||
"125": "Sheer Force",
|
||||
"126": "Contrary",
|
||||
"127": "Unnerve",
|
||||
"128": "Defiant",
|
||||
"129": "Defeatist",
|
||||
"130": "Cursed Body",
|
||||
"131": "Healer",
|
||||
"132": "Friend Guard",
|
||||
"133": "Weak Armor",
|
||||
"134": "Heavy Metal",
|
||||
"135": "Light Metal",
|
||||
"136": "Multiscale",
|
||||
"137": "Toxic Boost",
|
||||
"138": "Flare Boost",
|
||||
"139": "Harvest",
|
||||
"140": "Telepathy",
|
||||
"141": "Moody",
|
||||
"142": "Overcoat",
|
||||
"143": "Poison Touch",
|
||||
"144": "Regenerator",
|
||||
"145": "Big Pecks",
|
||||
"146": "Sand Rush",
|
||||
"147": "Wonder Skin",
|
||||
"148": "Analytic",
|
||||
"149": "Illusion",
|
||||
"150": "Imposter",
|
||||
"151": "Infiltrator",
|
||||
"152": "Mummy",
|
||||
"153": "Moxie",
|
||||
"154": "Justified",
|
||||
"155": "Rattled",
|
||||
"156": "Magic Bounce",
|
||||
"157": "Sap Sipper",
|
||||
"158": "Prankster",
|
||||
"159": "Sand Force",
|
||||
"160": "Iron Barbs",
|
||||
"161": "Zen Mode",
|
||||
"162": "Victory Star",
|
||||
"163": "Turboblaze",
|
||||
"164": "Teravolt"
|
||||
}
|
||||
2166
src/main/resources/data/countries.json
Normal file
2166
src/main/resources/data/countries.json
Normal file
File diff suppressed because it is too large
Load Diff
621
src/main/resources/data/items.json
Normal file
621
src/main/resources/data/items.json
Normal file
|
|
@ -0,0 +1,621 @@
|
|||
{
|
||||
"1": "Master Ball",
|
||||
"2": "Ultra Ball",
|
||||
"3": "Great Ball",
|
||||
"4": "Poké Ball",
|
||||
"5": "Safari Ball",
|
||||
"6": "Net Ball",
|
||||
"7": "Dive Ball",
|
||||
"8": "Nest Ball",
|
||||
"9": "Repeat Ball",
|
||||
"10": "Timer Ball",
|
||||
"11": "Luxury Ball",
|
||||
"12": "Premier Ball",
|
||||
"13": "Dusk Ball",
|
||||
"14": "Heal Ball",
|
||||
"15": "Quick Ball",
|
||||
"16": "Cherish Ball",
|
||||
"17": "Potion",
|
||||
"18": "Antidote",
|
||||
"19": "Burn Heal",
|
||||
"20": "Ice Heal",
|
||||
"21": "Awakening",
|
||||
"22": "Paralyze Heal",
|
||||
"23": "Full Restore",
|
||||
"24": "Max Potion",
|
||||
"25": "Hyper Potion",
|
||||
"26": "Super Potion",
|
||||
"27": "Full Heal",
|
||||
"28": "Revive",
|
||||
"29": "Max Revive",
|
||||
"30": "Fresh Water",
|
||||
"31": "Soda Pop",
|
||||
"32": "Lemonade",
|
||||
"33": "Moomoo Milk",
|
||||
"34": "Energy Powder",
|
||||
"35": "Energy Root",
|
||||
"36": "Heal Powder",
|
||||
"37": "Revival Herb",
|
||||
"38": "Ether",
|
||||
"39": "Max Ether",
|
||||
"40": "Elixir",
|
||||
"41": "Max Elixir",
|
||||
"42": "Lava Cookie",
|
||||
"43": "Berry Juice",
|
||||
"44": "Sacred Ash",
|
||||
"45": "HP Up",
|
||||
"46": "Protein",
|
||||
"47": "Iron",
|
||||
"48": "Carbos",
|
||||
"49": "Calcium",
|
||||
"50": "Rare Candy",
|
||||
"51": "PP Up",
|
||||
"52": "Zinc",
|
||||
"53": "PP Max",
|
||||
"54": "Old Gateau",
|
||||
"55": "Guard Spec.",
|
||||
"56": "Dire Hit",
|
||||
"57": "X Attack",
|
||||
"58": "X Defense",
|
||||
"59": "X Speed",
|
||||
"60": "X Accuracy",
|
||||
"61": "X Sp. Atk",
|
||||
"62": "X Sp. Def",
|
||||
"63": "Poké Doll",
|
||||
"64": "Fluffy Tail",
|
||||
"65": "Blue Flute",
|
||||
"66": "Yellow Flute",
|
||||
"67": "Red Flute",
|
||||
"68": "Black Flute",
|
||||
"69": "White Flute",
|
||||
"70": "Shoal Salt",
|
||||
"71": "Shoal Shell",
|
||||
"72": "Red Shard",
|
||||
"73": "Blue Shard",
|
||||
"74": "Yellow Shard",
|
||||
"75": "Green Shard",
|
||||
"76": "Super Repel",
|
||||
"77": "Max Repel",
|
||||
"78": "Escape Rope",
|
||||
"79": "Repel",
|
||||
"80": "Sun Stone",
|
||||
"81": "Moon Stone",
|
||||
"82": "Fire Stone",
|
||||
"83": "Thunder Stone",
|
||||
"84": "Water Stone",
|
||||
"85": "Leaf Stone",
|
||||
"86": "Tiny Mushroom",
|
||||
"87": "Big Mushroom",
|
||||
"88": "Pearl",
|
||||
"89": "Big Pearl",
|
||||
"90": "Stardust",
|
||||
"91": "Star Piece",
|
||||
"92": "Nugget",
|
||||
"93": "Heart Scale",
|
||||
"94": "Honey",
|
||||
"95": "Growth Mulch",
|
||||
"96": "Damp Mulch",
|
||||
"97": "Stable Mulch",
|
||||
"98": "Gooey Mulch",
|
||||
"99": "Root Fossil",
|
||||
"100": "Claw Fossil",
|
||||
"101": "Helix Fossil",
|
||||
"102": "Dome Fossil",
|
||||
"103": "Old Amber",
|
||||
"104": "Armor Fossil",
|
||||
"105": "Skull Fossil",
|
||||
"106": "Rare Bone",
|
||||
"107": "Shiny Stone",
|
||||
"108": "Dusk Stone",
|
||||
"109": "Dawn Stone",
|
||||
"110": "Oval Stone",
|
||||
"111": "Odd Keystone",
|
||||
"112": "Griseous Orb",
|
||||
"116": "Douse Drive",
|
||||
"117": "Shock Drive",
|
||||
"118": "Burn Drive",
|
||||
"119": "Chill Drive",
|
||||
"134": "Sweet Heart",
|
||||
"135": "Adamant Orb",
|
||||
"136": "Lustrous Orb",
|
||||
"137": "Greet Mail",
|
||||
"138": "Favored Mail",
|
||||
"139": "RSVP Mail",
|
||||
"140": "Thanks Mail",
|
||||
"141": "Inquiry Mail",
|
||||
"142": "Like Mail",
|
||||
"143": "Reply Mail",
|
||||
"144": "Bridge Mail S",
|
||||
"145": "Bridge Mail D",
|
||||
"146": "Bridge Mail T",
|
||||
"147": "Bridge Mail V",
|
||||
"148": "Bridge Mail M",
|
||||
"149": "Cheri Berry",
|
||||
"150": "Chesto Berry",
|
||||
"151": "Pecha Berry",
|
||||
"152": "Rawst Berry",
|
||||
"153": "Aspear Berry",
|
||||
"154": "Leppa Berry",
|
||||
"155": "Oran Berry",
|
||||
"156": "Persim Berry",
|
||||
"157": "Lum Berry",
|
||||
"158": "Sitrus Berry",
|
||||
"159": "Figy Berry",
|
||||
"160": "Wiki Berry",
|
||||
"161": "Mago Berry",
|
||||
"162": "Aguav Berry",
|
||||
"163": "Iapapa Berry",
|
||||
"164": "Razz Berry",
|
||||
"165": "Bluk Berry",
|
||||
"166": "Nanab Berry",
|
||||
"167": "Wepear Berry",
|
||||
"168": "Pinap Berry",
|
||||
"169": "Pomeg Berry",
|
||||
"170": "Kelpsy Berry",
|
||||
"171": "Qualot Berry",
|
||||
"172": "Hondew Berry",
|
||||
"173": "Grepa Berry",
|
||||
"174": "Tamato Berry",
|
||||
"175": "Cornn Berry",
|
||||
"176": "Magost Berry",
|
||||
"177": "Rabuta Berry",
|
||||
"178": "Nomel Berry",
|
||||
"179": "Spelon Berry",
|
||||
"180": "Pamtre Berry",
|
||||
"181": "Watmel Berry",
|
||||
"182": "Durin Berry",
|
||||
"183": "Belue Berry",
|
||||
"184": "Occa Berry",
|
||||
"185": "Passho Berry",
|
||||
"186": "Wacan Berry",
|
||||
"187": "Rindo Berry",
|
||||
"188": "Yache Berry",
|
||||
"189": "Chople Berry",
|
||||
"190": "Kebia Berry",
|
||||
"191": "Shuca Berry",
|
||||
"192": "Coba Berry",
|
||||
"193": "Payapa Berry",
|
||||
"194": "Tanga Berry",
|
||||
"195": "Charti Berry",
|
||||
"196": "Kasib Berry",
|
||||
"197": "Haban Berry",
|
||||
"198": "Colbur Berry",
|
||||
"199": "Babiri Berry",
|
||||
"200": "Chilan Berry",
|
||||
"201": "Liechi Berry",
|
||||
"202": "Ganlon Berry",
|
||||
"203": "Salac Berry",
|
||||
"204": "Petaya Berry",
|
||||
"205": "Apicot Berry",
|
||||
"206": "Lansat Berry",
|
||||
"207": "Starf Berry",
|
||||
"208": "Enigma Berry",
|
||||
"209": "Micle Berry",
|
||||
"210": "Custap Berry",
|
||||
"211": "Jaboca Berry",
|
||||
"212": "Rowap Berry",
|
||||
"213": "Bright Powder",
|
||||
"214": "White Herb",
|
||||
"215": "Macho Brace",
|
||||
"216": "Exp. Share",
|
||||
"217": "Quick Claw",
|
||||
"218": "Soothe Bell",
|
||||
"219": "Mental Herb",
|
||||
"220": "Choice Band",
|
||||
"221": "King's Rock",
|
||||
"222": "Silver Powder",
|
||||
"223": "Amulet Coin",
|
||||
"224": "Cleanse Tag",
|
||||
"225": "Soul Dew",
|
||||
"226": "Deep Sea Tooth",
|
||||
"227": "Deep Sea Scale",
|
||||
"228": "Smoke Ball",
|
||||
"229": "Everstone",
|
||||
"230": "Focus Band",
|
||||
"231": "Lucky Egg",
|
||||
"232": "Scope Lens",
|
||||
"233": "Metal Coat",
|
||||
"234": "Leftovers",
|
||||
"235": "Dragon Scale",
|
||||
"236": "Light Ball",
|
||||
"237": "Soft Sand",
|
||||
"238": "Hard Stone",
|
||||
"239": "Miracle Seed",
|
||||
"240": "Black Glasses",
|
||||
"241": "Black Belt",
|
||||
"242": "Magnet",
|
||||
"243": "Mystic Water",
|
||||
"244": "Sharp Beak",
|
||||
"245": "Poison Barb",
|
||||
"246": "Never-Melt Ice",
|
||||
"247": "Spell Tag",
|
||||
"248": "Twisted Spoon",
|
||||
"249": "Charcoal",
|
||||
"250": "Dragon Fang",
|
||||
"251": "Silk Scarf",
|
||||
"252": "Up-Grade",
|
||||
"253": "Shell Bell",
|
||||
"254": "Sea Incense",
|
||||
"255": "Lax Incense",
|
||||
"256": "Lucky Punch",
|
||||
"257": "Metal Powder",
|
||||
"258": "Thick Club",
|
||||
"259": "Stick",
|
||||
"260": "Red Scarf",
|
||||
"261": "Blue Scarf",
|
||||
"262": "Pink Scarf",
|
||||
"263": "Green Scarf",
|
||||
"264": "Yellow Scarf",
|
||||
"265": "Wide Lens",
|
||||
"266": "Muscle Band",
|
||||
"267": "Wise Glasses",
|
||||
"268": "Expert Belt",
|
||||
"269": "Light Clay",
|
||||
"270": "Life Orb",
|
||||
"271": "Power Herb",
|
||||
"272": "Toxic Orb",
|
||||
"273": "Flame Orb",
|
||||
"274": "Quick Powder",
|
||||
"275": "Focus Sash",
|
||||
"276": "Zoom Lens",
|
||||
"277": "Metronome",
|
||||
"278": "Iron Ball",
|
||||
"279": "Lagging Tail",
|
||||
"280": "Destiny Knot",
|
||||
"281": "Black Sludge",
|
||||
"282": "Icy Rock",
|
||||
"283": "Smooth Rock",
|
||||
"284": "Heat Rock",
|
||||
"285": "Damp Rock",
|
||||
"286": "Grip Claw",
|
||||
"287": "Choice Scarf",
|
||||
"288": "Sticky Barb",
|
||||
"289": "Power Bracer",
|
||||
"290": "Power Belt",
|
||||
"291": "Power Lens",
|
||||
"292": "Power Band",
|
||||
"293": "Power Anklet",
|
||||
"294": "Power Weight",
|
||||
"295": "Shed Shell",
|
||||
"296": "Big Root",
|
||||
"297": "Choice Specs",
|
||||
"298": "Flame Plate",
|
||||
"299": "Splash Plate",
|
||||
"300": "Zap Plate",
|
||||
"301": "Meadow Plate",
|
||||
"302": "Icicle Plate",
|
||||
"303": "Fist Plate",
|
||||
"304": "Toxic Plate",
|
||||
"305": "Earth Plate",
|
||||
"306": "Sky Plate",
|
||||
"307": "Mind Plate",
|
||||
"308": "Insect Plate",
|
||||
"309": "Stone Plate",
|
||||
"310": "Spooky Plate",
|
||||
"311": "Draco Plate",
|
||||
"312": "Dread Plate",
|
||||
"313": "Iron Plate",
|
||||
"314": "Odd Incense",
|
||||
"315": "Rock Incense",
|
||||
"316": "Full Incense",
|
||||
"317": "Wave Incense",
|
||||
"318": "Rose Incense",
|
||||
"319": "Luck Incense",
|
||||
"320": "Pure Incense",
|
||||
"321": "Protector",
|
||||
"322": "Electirizer",
|
||||
"323": "Magmarizer",
|
||||
"324": "Dubious Disc",
|
||||
"325": "Reaper Cloth",
|
||||
"326": "Razor Claw",
|
||||
"327": "Razor Fang",
|
||||
"328": "TM01",
|
||||
"329": "TM02",
|
||||
"330": "TM03",
|
||||
"331": "TM04",
|
||||
"332": "TM05",
|
||||
"333": "TM06",
|
||||
"334": "TM07",
|
||||
"335": "TM08",
|
||||
"336": "TM09",
|
||||
"337": "TM10",
|
||||
"338": "TM11",
|
||||
"339": "TM12",
|
||||
"340": "TM13",
|
||||
"341": "TM14",
|
||||
"342": "TM15",
|
||||
"343": "TM16",
|
||||
"344": "TM17",
|
||||
"345": "TM18",
|
||||
"346": "TM19",
|
||||
"347": "TM20",
|
||||
"348": "TM21",
|
||||
"349": "TM22",
|
||||
"350": "TM23",
|
||||
"351": "TM24",
|
||||
"352": "TM25",
|
||||
"353": "TM26",
|
||||
"354": "TM27",
|
||||
"355": "TM28",
|
||||
"356": "TM29",
|
||||
"357": "TM30",
|
||||
"358": "TM31",
|
||||
"359": "TM32",
|
||||
"360": "TM33",
|
||||
"361": "TM34",
|
||||
"362": "TM35",
|
||||
"363": "TM36",
|
||||
"364": "TM37",
|
||||
"365": "TM38",
|
||||
"366": "TM39",
|
||||
"367": "TM40",
|
||||
"368": "TM41",
|
||||
"369": "TM42",
|
||||
"370": "TM43",
|
||||
"371": "TM44",
|
||||
"372": "TM45",
|
||||
"373": "TM46",
|
||||
"374": "TM47",
|
||||
"375": "TM48",
|
||||
"376": "TM49",
|
||||
"377": "TM50",
|
||||
"378": "TM51",
|
||||
"379": "TM52",
|
||||
"380": "TM53",
|
||||
"381": "TM54",
|
||||
"382": "TM55",
|
||||
"383": "TM56",
|
||||
"384": "TM57",
|
||||
"385": "TM58",
|
||||
"386": "TM59",
|
||||
"387": "TM60",
|
||||
"388": "TM61",
|
||||
"389": "TM62",
|
||||
"390": "TM63",
|
||||
"391": "TM64",
|
||||
"392": "TM65",
|
||||
"393": "TM66",
|
||||
"394": "TM67",
|
||||
"395": "TM68",
|
||||
"396": "TM69",
|
||||
"397": "TM70",
|
||||
"398": "TM71",
|
||||
"399": "TM72",
|
||||
"400": "TM73",
|
||||
"401": "TM74",
|
||||
"402": "TM75",
|
||||
"403": "TM76",
|
||||
"404": "TM77",
|
||||
"405": "TM78",
|
||||
"406": "TM79",
|
||||
"407": "TM80",
|
||||
"408": "TM81",
|
||||
"409": "TM82",
|
||||
"410": "TM83",
|
||||
"411": "TM84",
|
||||
"412": "TM85",
|
||||
"413": "TM86",
|
||||
"414": "TM87",
|
||||
"415": "TM88",
|
||||
"416": "TM89",
|
||||
"417": "TM90",
|
||||
"418": "TM91",
|
||||
"419": "TM92",
|
||||
"420": "HM01",
|
||||
"421": "HM02",
|
||||
"422": "HM03",
|
||||
"423": "HM04",
|
||||
"424": "HM05",
|
||||
"425": "HM06",
|
||||
"428": "Explorer Kit",
|
||||
"429": "Loot Sack",
|
||||
"430": "Rule Book",
|
||||
"431": "Poké Radar",
|
||||
"432": "Point Card",
|
||||
"433": "Journal",
|
||||
"434": "Seal Case",
|
||||
"435": "Fashion Case",
|
||||
"436": "Seal Bag",
|
||||
"437": "Pal Pad",
|
||||
"438": "Works Key",
|
||||
"439": "Old Charm",
|
||||
"440": "Galactic Key",
|
||||
"441": "Red Chain",
|
||||
"442": "Town Map",
|
||||
"443": "Vs. Seeker",
|
||||
"444": "Coin Case",
|
||||
"445": "Old Rod",
|
||||
"446": "Good Rod",
|
||||
"447": "Super Rod",
|
||||
"448": "Sprayduck",
|
||||
"449": "Poffin Case",
|
||||
"450": "Bike",
|
||||
"451": "Suite Key",
|
||||
"452": "Oak's Letter",
|
||||
"453": "Lunar Wing",
|
||||
"454": "Member Card",
|
||||
"455": "Azure Flute",
|
||||
"456": "S.S. Ticket",
|
||||
"457": "Contest Pass",
|
||||
"458": "Magma Stone",
|
||||
"459": "Parcel",
|
||||
"460": "Coupon 1",
|
||||
"461": "Coupon 2",
|
||||
"462": "Coupon 3",
|
||||
"463": "Storage Key",
|
||||
"464": "Secret Potion",
|
||||
"465": "Vs. Recorder",
|
||||
"466": "Gracidea",
|
||||
"467": "Secret Key",
|
||||
"468": "Apricorn Box",
|
||||
"469": "Unown Report",
|
||||
"470": "Berry Pots",
|
||||
"471": "Dowsing Machine",
|
||||
"472": "Blue Card",
|
||||
"473": "Slowpoke Tail",
|
||||
"474": "Clear Bell",
|
||||
"475": "Card Key",
|
||||
"476": "Basement Key",
|
||||
"477": "Squirt Bottle",
|
||||
"478": "Red Scale",
|
||||
"479": "Lost Item",
|
||||
"480": "Pass",
|
||||
"481": "Machine Part",
|
||||
"482": "Silver Wing",
|
||||
"483": "Rainbow Wing",
|
||||
"484": "Mystery Egg",
|
||||
"485": "Red Apricorn",
|
||||
"486": "Blue Apricorn",
|
||||
"487": "Yellow Apricorn",
|
||||
"488": "Green Apricorn",
|
||||
"489": "Pink Apricorn",
|
||||
"490": "White Apricorn",
|
||||
"491": "Black Apricorn",
|
||||
"492": "Fast Ball",
|
||||
"493": "Level Ball",
|
||||
"494": "Lure Ball",
|
||||
"495": "Heavy Ball",
|
||||
"496": "Love Ball",
|
||||
"497": "Friend Ball",
|
||||
"498": "Moon Ball",
|
||||
"499": "Sport Ball",
|
||||
"500": "Park Ball",
|
||||
"501": "Photo Album",
|
||||
"502": "GB Sounds",
|
||||
"503": "Tidal Bell",
|
||||
"504": "RageCandyBar",
|
||||
"505": "Data Card 01",
|
||||
"506": "Data Card 02",
|
||||
"507": "Data Card 03",
|
||||
"508": "Data Card 04",
|
||||
"509": "Data Card 05",
|
||||
"510": "Data Card 06",
|
||||
"511": "Data Card 07",
|
||||
"512": "Data Card 08",
|
||||
"513": "Data Card 09",
|
||||
"514": "Data Card 10",
|
||||
"515": "Data Card 11",
|
||||
"516": "Data Card 12",
|
||||
"517": "Data Card 13",
|
||||
"518": "Data Card 14",
|
||||
"519": "Data Card 15",
|
||||
"520": "Data Card 16",
|
||||
"521": "Data Card 17",
|
||||
"522": "Data Card 18",
|
||||
"523": "Data Card 19",
|
||||
"524": "Data Card 20",
|
||||
"525": "Data Card 21",
|
||||
"526": "Data Card 22",
|
||||
"527": "Data Card 23",
|
||||
"528": "Data Card 24",
|
||||
"529": "Data Card 25",
|
||||
"530": "Data Card 26",
|
||||
"531": "Data Card 27",
|
||||
"532": "Jade Orb",
|
||||
"533": "Lock Capsule",
|
||||
"534": "Red Orb",
|
||||
"535": "Blue Orb",
|
||||
"536": "Enigma Stone",
|
||||
"537": "Prism Scale",
|
||||
"538": "Eviolite",
|
||||
"539": "Float Stone",
|
||||
"540": "Rocky Helmet",
|
||||
"541": "Air Balloon",
|
||||
"542": "Red Card",
|
||||
"543": "Ring Target",
|
||||
"544": "Binding Band",
|
||||
"545": "Absorb Bulb",
|
||||
"546": "Cell Battery",
|
||||
"547": "Eject Button",
|
||||
"548": "Fire Gem",
|
||||
"549": "Water Gem",
|
||||
"550": "Electric Gem",
|
||||
"551": "Grass Gem",
|
||||
"552": "Ice Gem",
|
||||
"553": "Fighting Gem",
|
||||
"554": "Poison Gem",
|
||||
"555": "Ground Gem",
|
||||
"556": "Flying Gem",
|
||||
"557": "Psychic Gem",
|
||||
"558": "Bug Gem",
|
||||
"559": "Rock Gem",
|
||||
"560": "Ghost Gem",
|
||||
"561": "Dragon Gem",
|
||||
"562": "Dark Gem",
|
||||
"563": "Steel Gem",
|
||||
"564": "Normal Gem",
|
||||
"565": "Health Wing",
|
||||
"566": "Muscle Wing",
|
||||
"567": "Resist Wing",
|
||||
"568": "Genius Wing",
|
||||
"569": "Clever Wing",
|
||||
"570": "Swift Wing",
|
||||
"571": "Pretty Wing",
|
||||
"572": "Cover Fossil",
|
||||
"573": "Plume Fossil",
|
||||
"574": "Liberty Pass",
|
||||
"575": "Pass Orb",
|
||||
"576": "Dream Ball",
|
||||
"577": "Poké Toy",
|
||||
"578": "Prop Case",
|
||||
"579": "Dragon Skull",
|
||||
"580": "Balm Mushroom",
|
||||
"581": "Big Nugget",
|
||||
"582": "Pearl String",
|
||||
"583": "Comet Shard",
|
||||
"584": "Relic Copper",
|
||||
"585": "Relic Silver",
|
||||
"586": "Relic Gold",
|
||||
"587": "Relic Vase",
|
||||
"588": "Relic Band",
|
||||
"589": "Relic Statue",
|
||||
"590": "Relic Crown",
|
||||
"591": "Casteliacone",
|
||||
"592": "Dire Hit 2",
|
||||
"593": "X Speed 2",
|
||||
"594": "X Sp. Atk 2",
|
||||
"595": "X Sp. Def 2",
|
||||
"596": "X Defense 2",
|
||||
"597": "X Attack 2",
|
||||
"598": "X Accuracy 2",
|
||||
"599": "X Speed 3",
|
||||
"600": "X Sp. Atk 3",
|
||||
"601": "X Sp. Def 3",
|
||||
"602": "X Defense 3",
|
||||
"603": "X Attack 3",
|
||||
"604": "X Accuracy 3",
|
||||
"605": "X Speed 6",
|
||||
"606": "X Sp. Atk 6",
|
||||
"607": "X Sp. Def 6",
|
||||
"608": "X Defense 6",
|
||||
"609": "X Attack 6",
|
||||
"610": "X Accuracy 6",
|
||||
"611": "Ability Urge",
|
||||
"612": "Item Drop",
|
||||
"613": "Item Urge",
|
||||
"614": "Reset Urge",
|
||||
"615": "Dire Hit 3",
|
||||
"616": "Light Stone",
|
||||
"617": "Dark Stone",
|
||||
"618": "TM93",
|
||||
"619": "TM94",
|
||||
"620": "TM95",
|
||||
"621": "Xtransceiver",
|
||||
"622": "God Stone",
|
||||
"623": "Gram 1",
|
||||
"624": "Gram 2",
|
||||
"625": "Gram 3",
|
||||
"626": "Xtransceiver",
|
||||
"627": "Medal Box",
|
||||
"628": "DNA Splicers",
|
||||
"629": "DNA Splicers",
|
||||
"630": "Permit",
|
||||
"631": "Oval Charm",
|
||||
"632": "Shiny Charm",
|
||||
"633": "Plasma Card",
|
||||
"634": "Grubby Hanky",
|
||||
"635": "Colress Machine",
|
||||
"636": "Dropped Item",
|
||||
"637": "Dropped Item",
|
||||
"638": "Reveal Glass"
|
||||
}
|
||||
2375
src/main/resources/data/legality.json
Normal file
2375
src/main/resources/data/legality.json
Normal file
File diff suppressed because it is too large
Load Diff
561
src/main/resources/data/moves.json
Normal file
561
src/main/resources/data/moves.json
Normal file
|
|
@ -0,0 +1,561 @@
|
|||
{
|
||||
"1": "Pound",
|
||||
"2": "Karate Chop",
|
||||
"3": "Double Slap",
|
||||
"4": "Comet Punch",
|
||||
"5": "Mega Punch",
|
||||
"6": "Pay Day",
|
||||
"7": "Fire Punch",
|
||||
"8": "Ice Punch",
|
||||
"9": "Thunder Punch",
|
||||
"10": "Scratch",
|
||||
"11": "Vice Grip",
|
||||
"12": "Guillotine",
|
||||
"13": "Razor Wind",
|
||||
"14": "Swords Dance",
|
||||
"15": "Cut",
|
||||
"16": "Gust",
|
||||
"17": "Wing Attack",
|
||||
"18": "Whirlwind",
|
||||
"19": "Fly",
|
||||
"20": "Bind",
|
||||
"21": "Slam",
|
||||
"22": "Vine Whip",
|
||||
"23": "Stomp",
|
||||
"24": "Double Kick",
|
||||
"25": "Mega Kick",
|
||||
"26": "Jump Kick",
|
||||
"27": "Rolling Kick",
|
||||
"28": "Sand Attack",
|
||||
"29": "Headbutt",
|
||||
"30": "Horn Attack",
|
||||
"31": "Fury Attack",
|
||||
"32": "Horn Drill",
|
||||
"33": "Tackle",
|
||||
"34": "Body Slam",
|
||||
"35": "Wrap",
|
||||
"36": "Take Down",
|
||||
"37": "Thrash",
|
||||
"38": "Double-Edge",
|
||||
"39": "Tail Whip",
|
||||
"40": "Poison Sting",
|
||||
"41": "Twineedle",
|
||||
"42": "Pin Missile",
|
||||
"43": "Leer",
|
||||
"44": "Bite",
|
||||
"45": "Growl",
|
||||
"46": "Roar",
|
||||
"47": "Sing",
|
||||
"48": "Supersonic",
|
||||
"49": "Sonic Boom",
|
||||
"50": "Disable",
|
||||
"51": "Acid",
|
||||
"52": "Ember",
|
||||
"53": "Flamethrower",
|
||||
"54": "Mist",
|
||||
"55": "Water Gun",
|
||||
"56": "Hydro Pump",
|
||||
"57": "Surf",
|
||||
"58": "Ice Beam",
|
||||
"59": "Blizzard",
|
||||
"60": "Psybeam",
|
||||
"61": "Bubble Beam",
|
||||
"62": "Aurora Beam",
|
||||
"63": "Hyper Beam",
|
||||
"64": "Peck",
|
||||
"65": "Drill Peck",
|
||||
"66": "Submission",
|
||||
"67": "Low Kick",
|
||||
"68": "Counter",
|
||||
"69": "Seismic Toss",
|
||||
"70": "Strength",
|
||||
"71": "Absorb",
|
||||
"72": "Mega Drain",
|
||||
"73": "Leech Seed",
|
||||
"74": "Growth",
|
||||
"75": "Razor Leaf",
|
||||
"76": "Solar Beam",
|
||||
"77": "Poison Powder",
|
||||
"78": "Stun Spore",
|
||||
"79": "Sleep Powder",
|
||||
"80": "Petal Dance",
|
||||
"81": "String Shot",
|
||||
"82": "Dragon Rage",
|
||||
"83": "Fire Spin",
|
||||
"84": "Thunder Shock",
|
||||
"85": "Thunderbolt",
|
||||
"86": "Thunder Wave",
|
||||
"87": "Thunder",
|
||||
"88": "Rock Throw",
|
||||
"89": "Earthquake",
|
||||
"90": "Fissure",
|
||||
"91": "Dig",
|
||||
"92": "Toxic",
|
||||
"93": "Confusion",
|
||||
"94": "Psychic",
|
||||
"95": "Hypnosis",
|
||||
"96": "Meditate",
|
||||
"97": "Agility",
|
||||
"98": "Quick Attack",
|
||||
"99": "Rage",
|
||||
"100": "Teleport",
|
||||
"101": "Night Shade",
|
||||
"102": "Mimic",
|
||||
"103": "Screech",
|
||||
"104": "Double Team",
|
||||
"105": "Recover",
|
||||
"106": "Harden",
|
||||
"107": "Minimize",
|
||||
"108": "Smokescreen",
|
||||
"109": "Confuse Ray",
|
||||
"110": "Withdraw",
|
||||
"111": "Defense Curl",
|
||||
"112": "Barrier",
|
||||
"113": "Light Screen",
|
||||
"114": "Haze",
|
||||
"115": "Reflect",
|
||||
"116": "Focus Energy",
|
||||
"117": "Bide",
|
||||
"118": "Metronome",
|
||||
"119": "Mirror Move",
|
||||
"120": "Self-Destruct",
|
||||
"121": "Egg Bomb",
|
||||
"122": "Lick",
|
||||
"123": "Smog",
|
||||
"124": "Sludge",
|
||||
"125": "Bone Club",
|
||||
"126": "Fire Blast",
|
||||
"127": "Waterfall",
|
||||
"128": "Clamp",
|
||||
"129": "Swift",
|
||||
"130": "Skull Bash",
|
||||
"131": "Spike Cannon",
|
||||
"132": "Constrict",
|
||||
"133": "Amnesia",
|
||||
"134": "Kinesis",
|
||||
"135": "Soft-Boiled",
|
||||
"136": "High Jump Kick",
|
||||
"137": "Glare",
|
||||
"138": "Dream Eater",
|
||||
"139": "Poison Gas",
|
||||
"140": "Barrage",
|
||||
"141": "Leech Life",
|
||||
"142": "Lovely Kiss",
|
||||
"143": "Sky Attack",
|
||||
"144": "Transform",
|
||||
"145": "Bubble",
|
||||
"146": "Dizzy Punch",
|
||||
"147": "Spore",
|
||||
"148": "Flash",
|
||||
"149": "Psywave",
|
||||
"150": "Splash",
|
||||
"151": "Acid Armor",
|
||||
"152": "Crabhammer",
|
||||
"153": "Explosion",
|
||||
"154": "Fury Swipes",
|
||||
"155": "Bonemerang",
|
||||
"156": "Rest",
|
||||
"157": "Rock Slide",
|
||||
"158": "Hyper Fang",
|
||||
"159": "Sharpen",
|
||||
"160": "Conversion",
|
||||
"161": "Tri Attack",
|
||||
"162": "Super Fang",
|
||||
"163": "Slash",
|
||||
"164": "Substitute",
|
||||
"165": "Struggle",
|
||||
"166": "Sketch",
|
||||
"167": "Triple Kick",
|
||||
"168": "Thief",
|
||||
"169": "Spider Web",
|
||||
"170": "Mind Reader",
|
||||
"171": "Nightmare",
|
||||
"172": "Flame Wheel",
|
||||
"173": "Snore",
|
||||
"174": "Curse",
|
||||
"175": "Flail",
|
||||
"176": "Conversion 2",
|
||||
"177": "Aeroblast",
|
||||
"178": "Cotton Spore",
|
||||
"179": "Reversal",
|
||||
"180": "Spite",
|
||||
"181": "Powder Snow",
|
||||
"182": "Protect",
|
||||
"183": "Mach Punch",
|
||||
"184": "Scary Face",
|
||||
"185": "Feint Attack",
|
||||
"186": "Sweet Kiss",
|
||||
"187": "Belly Drum",
|
||||
"188": "Sludge Bomb",
|
||||
"189": "Mud-Slap",
|
||||
"190": "Octazooka",
|
||||
"191": "Spikes",
|
||||
"192": "Zap Cannon",
|
||||
"193": "Foresight",
|
||||
"194": "Destiny Bond",
|
||||
"195": "Perish Song",
|
||||
"196": "Icy Wind",
|
||||
"197": "Detect",
|
||||
"198": "Bone Rush",
|
||||
"199": "Lock-On",
|
||||
"200": "Outrage",
|
||||
"201": "Sandstorm",
|
||||
"202": "Giga Drain",
|
||||
"203": "Endure",
|
||||
"204": "Charm",
|
||||
"205": "Rollout",
|
||||
"206": "False Swipe",
|
||||
"207": "Swagger",
|
||||
"208": "Milk Drink",
|
||||
"209": "Spark",
|
||||
"210": "Fury Cutter",
|
||||
"211": "Steel Wing",
|
||||
"212": "Mean Look",
|
||||
"213": "Attract",
|
||||
"214": "Sleep Talk",
|
||||
"215": "Heal Bell",
|
||||
"216": "Return",
|
||||
"217": "Present",
|
||||
"218": "Frustration",
|
||||
"219": "Safeguard",
|
||||
"220": "Pain Split",
|
||||
"221": "Sacred Fire",
|
||||
"222": "Magnitude",
|
||||
"223": "Dynamic Punch",
|
||||
"224": "Megahorn",
|
||||
"225": "Dragon Breath",
|
||||
"226": "Baton Pass",
|
||||
"227": "Encore",
|
||||
"228": "Pursuit",
|
||||
"229": "Rapid Spin",
|
||||
"230": "Sweet Scent",
|
||||
"231": "Iron Tail",
|
||||
"232": "Metal Claw",
|
||||
"233": "Vital Throw",
|
||||
"234": "Morning Sun",
|
||||
"235": "Synthesis",
|
||||
"236": "Moonlight",
|
||||
"237": "Hidden Power",
|
||||
"238": "Cross Chop",
|
||||
"239": "Twister",
|
||||
"240": "Rain Dance",
|
||||
"241": "Sunny Day",
|
||||
"242": "Crunch",
|
||||
"243": "Mirror Coat",
|
||||
"244": "Psych Up",
|
||||
"245": "Extreme Speed",
|
||||
"246": "Ancient Power",
|
||||
"247": "Shadow Ball",
|
||||
"248": "Future Sight",
|
||||
"249": "Rock Smash",
|
||||
"250": "Whirlpool",
|
||||
"251": "Beat Up",
|
||||
"252": "Fake Out",
|
||||
"253": "Uproar",
|
||||
"254": "Stockpile",
|
||||
"255": "Spit Up",
|
||||
"256": "Swallow",
|
||||
"257": "Heat Wave",
|
||||
"258": "Hail",
|
||||
"259": "Torment",
|
||||
"260": "Flatter",
|
||||
"261": "Will-O-Wisp",
|
||||
"262": "Memento",
|
||||
"263": "Facade",
|
||||
"264": "Focus Punch",
|
||||
"265": "Smelling Salts",
|
||||
"266": "Follow Me",
|
||||
"267": "Nature Power",
|
||||
"268": "Charge",
|
||||
"269": "Taunt",
|
||||
"270": "Helping Hand",
|
||||
"271": "Trick",
|
||||
"272": "Role Play",
|
||||
"273": "Wish",
|
||||
"274": "Assist",
|
||||
"275": "Ingrain",
|
||||
"276": "Superpower",
|
||||
"277": "Magic Coat",
|
||||
"278": "Recycle",
|
||||
"279": "Revenge",
|
||||
"280": "Brick Break",
|
||||
"281": "Yawn",
|
||||
"282": "Knock Off",
|
||||
"283": "Endeavor",
|
||||
"284": "Eruption",
|
||||
"285": "Skill Swap",
|
||||
"286": "Imprison",
|
||||
"287": "Refresh",
|
||||
"288": "Grudge",
|
||||
"289": "Snatch",
|
||||
"290": "Secret Power",
|
||||
"291": "Dive",
|
||||
"292": "Arm Thrust",
|
||||
"293": "Camouflage",
|
||||
"294": "Tail Glow",
|
||||
"295": "Luster Purge",
|
||||
"296": "Mist Ball",
|
||||
"297": "Feather Dance",
|
||||
"298": "Teeter Dance",
|
||||
"299": "Blaze Kick",
|
||||
"300": "Mud Sport",
|
||||
"301": "Ice Ball",
|
||||
"302": "Needle Arm",
|
||||
"303": "Slack Off",
|
||||
"304": "Hyper Voice",
|
||||
"305": "Poison Fang",
|
||||
"306": "Crush Claw",
|
||||
"307": "Blast Burn",
|
||||
"308": "Hydro Cannon",
|
||||
"309": "Meteor Mash",
|
||||
"310": "Astonish",
|
||||
"311": "Weather Ball",
|
||||
"312": "Aromatherapy",
|
||||
"313": "Fake Tears",
|
||||
"314": "Air Cutter",
|
||||
"315": "Overheat",
|
||||
"316": "Odor Sleuth",
|
||||
"317": "Rock Tomb",
|
||||
"318": "Silver Wind",
|
||||
"319": "Metal Sound",
|
||||
"320": "Grass Whistle",
|
||||
"321": "Tickle",
|
||||
"322": "Cosmic Power",
|
||||
"323": "Water Spout",
|
||||
"324": "Signal Beam",
|
||||
"325": "Shadow Punch",
|
||||
"326": "Extrasensory",
|
||||
"327": "Sky Uppercut",
|
||||
"328": "Sand Tomb",
|
||||
"329": "Sheer Cold",
|
||||
"330": "Muddy Water",
|
||||
"331": "Bullet Seed",
|
||||
"332": "Aerial Ace",
|
||||
"333": "Icicle Spear",
|
||||
"334": "Iron Defense",
|
||||
"335": "Block",
|
||||
"336": "Howl",
|
||||
"337": "Dragon Claw",
|
||||
"338": "Frenzy Plant",
|
||||
"339": "Bulk Up",
|
||||
"340": "Bounce",
|
||||
"341": "Mud Shot",
|
||||
"342": "Poison Tail",
|
||||
"343": "Covet",
|
||||
"344": "Volt Tackle",
|
||||
"345": "Magical Leaf",
|
||||
"346": "Water Sport",
|
||||
"347": "Calm Mind",
|
||||
"348": "Leaf Blade",
|
||||
"349": "Dragon Dance",
|
||||
"350": "Rock Blast",
|
||||
"351": "Shock Wave",
|
||||
"352": "Water Pulse",
|
||||
"353": "Doom Desire",
|
||||
"354": "Psycho Boost",
|
||||
"355": "Roost",
|
||||
"356": "Gravity",
|
||||
"357": "Miracle Eye",
|
||||
"358": "Wake-Up Slap",
|
||||
"359": "Hammer Arm",
|
||||
"360": "Gyro Ball",
|
||||
"361": "Healing Wish",
|
||||
"362": "Brine",
|
||||
"363": "Natural Gift",
|
||||
"364": "Feint",
|
||||
"365": "Pluck",
|
||||
"366": "Tailwind",
|
||||
"367": "Acupressure",
|
||||
"368": "Metal Burst",
|
||||
"369": "U-turn",
|
||||
"370": "Close Combat",
|
||||
"371": "Payback",
|
||||
"372": "Assurance",
|
||||
"373": "Embargo",
|
||||
"374": "Fling",
|
||||
"375": "Psycho Shift",
|
||||
"376": "Trump Card",
|
||||
"377": "Heal Block",
|
||||
"378": "Wring Out",
|
||||
"379": "Power Trick",
|
||||
"380": "Gastro Acid",
|
||||
"381": "Lucky Chant",
|
||||
"382": "Me First",
|
||||
"383": "Copycat",
|
||||
"384": "Power Swap",
|
||||
"385": "Guard Swap",
|
||||
"386": "Punishment",
|
||||
"387": "Last Resort",
|
||||
"388": "Worry Seed",
|
||||
"389": "Sucker Punch",
|
||||
"390": "Toxic Spikes",
|
||||
"391": "Heart Swap",
|
||||
"392": "Aqua Ring",
|
||||
"393": "Magnet Rise",
|
||||
"394": "Flare Blitz",
|
||||
"395": "Force Palm",
|
||||
"396": "Aura Sphere",
|
||||
"397": "Rock Polish",
|
||||
"398": "Poison Jab",
|
||||
"399": "Dark Pulse",
|
||||
"400": "Night Slash",
|
||||
"401": "Aqua Tail",
|
||||
"402": "Seed Bomb",
|
||||
"403": "Air Slash",
|
||||
"404": "X-Scissor",
|
||||
"405": "Bug Buzz",
|
||||
"406": "Dragon Pulse",
|
||||
"407": "Dragon Rush",
|
||||
"408": "Power Gem",
|
||||
"409": "Drain Punch",
|
||||
"410": "Vacuum Wave",
|
||||
"411": "Focus Blast",
|
||||
"412": "Energy Ball",
|
||||
"413": "Brave Bird",
|
||||
"414": "Earth Power",
|
||||
"415": "Switcheroo",
|
||||
"416": "Giga Impact",
|
||||
"417": "Nasty Plot",
|
||||
"418": "Bullet Punch",
|
||||
"419": "Avalanche",
|
||||
"420": "Ice Shard",
|
||||
"421": "Shadow Claw",
|
||||
"422": "Thunder Fang",
|
||||
"423": "Ice Fang",
|
||||
"424": "Fire Fang",
|
||||
"425": "Shadow Sneak",
|
||||
"426": "Mud Bomb",
|
||||
"427": "Psycho Cut",
|
||||
"428": "Zen Headbutt",
|
||||
"429": "Mirror Shot",
|
||||
"430": "Flash Cannon",
|
||||
"431": "Rock Climb",
|
||||
"432": "Defog",
|
||||
"433": "Trick Room",
|
||||
"434": "Draco Meteor",
|
||||
"435": "Discharge",
|
||||
"436": "Lava Plume",
|
||||
"437": "Leaf Storm",
|
||||
"438": "Power Whip",
|
||||
"439": "Rock Wrecker",
|
||||
"440": "Cross Poison",
|
||||
"441": "Gunk Shot",
|
||||
"442": "Iron Head",
|
||||
"443": "Magnet Bomb",
|
||||
"444": "Stone Edge",
|
||||
"445": "Captivate",
|
||||
"446": "Stealth Rock",
|
||||
"447": "Grass Knot",
|
||||
"448": "Chatter",
|
||||
"449": "Judgment",
|
||||
"450": "Bug Bite",
|
||||
"451": "Charge Beam",
|
||||
"452": "Wood Hammer",
|
||||
"453": "Aqua Jet",
|
||||
"454": "Attack Order",
|
||||
"455": "Defend Order",
|
||||
"456": "Heal Order",
|
||||
"457": "Head Smash",
|
||||
"458": "Double Hit",
|
||||
"459": "Roar of Time",
|
||||
"460": "Spacial Rend",
|
||||
"461": "Lunar Dance",
|
||||
"462": "Crush Grip",
|
||||
"463": "Magma Storm",
|
||||
"464": "Dark Void",
|
||||
"465": "Seed Flare",
|
||||
"466": "Ominous Wind",
|
||||
"467": "Shadow Force",
|
||||
"468": "Hone Claws",
|
||||
"469": "Wide Guard",
|
||||
"470": "Guard Split",
|
||||
"471": "Power Split",
|
||||
"472": "Wonder Room",
|
||||
"473": "Psyshock",
|
||||
"474": "Venoshock",
|
||||
"475": "Autotomize",
|
||||
"476": "Rage Powder",
|
||||
"477": "Telekinesis",
|
||||
"478": "Magic Room",
|
||||
"479": "Smack Down",
|
||||
"480": "Storm Throw",
|
||||
"481": "Flame Burst",
|
||||
"482": "Sludge Wave",
|
||||
"483": "Quiver Dance",
|
||||
"484": "Heavy Slam",
|
||||
"485": "Synchronoise",
|
||||
"486": "Electro Ball",
|
||||
"487": "Soak",
|
||||
"488": "Flame Charge",
|
||||
"489": "Coil",
|
||||
"490": "Low Sweep",
|
||||
"491": "Acid Spray",
|
||||
"492": "Foul Play",
|
||||
"493": "Simple Beam",
|
||||
"494": "Entrainment",
|
||||
"495": "After You",
|
||||
"496": "Round",
|
||||
"497": "Echoed Voice",
|
||||
"498": "Chip Away",
|
||||
"499": "Clear Smog",
|
||||
"500": "Stored Power",
|
||||
"501": "Quick Guard",
|
||||
"502": "Ally Switch",
|
||||
"503": "Scald",
|
||||
"504": "Shell Smash",
|
||||
"505": "Heal Pulse",
|
||||
"506": "Hex",
|
||||
"507": "Sky Drop",
|
||||
"508": "Shift Gear",
|
||||
"509": "Circle Throw",
|
||||
"510": "Incinerate",
|
||||
"511": "Quash",
|
||||
"512": "Acrobatics",
|
||||
"513": "Reflect Type",
|
||||
"514": "Retaliate",
|
||||
"515": "Final Gambit",
|
||||
"516": "Bestow",
|
||||
"517": "Inferno",
|
||||
"518": "Water Pledge",
|
||||
"519": "Fire Pledge",
|
||||
"520": "Grass Pledge",
|
||||
"521": "Volt Switch",
|
||||
"522": "Struggle Bug",
|
||||
"523": "Bulldoze",
|
||||
"524": "Frost Breath",
|
||||
"525": "Dragon Tail",
|
||||
"526": "Work Up",
|
||||
"527": "Electroweb",
|
||||
"528": "Wild Charge",
|
||||
"529": "Drill Run",
|
||||
"530": "Dual Chop",
|
||||
"531": "Heart Stamp",
|
||||
"532": "Horn Leech",
|
||||
"533": "Sacred Sword",
|
||||
"534": "Razor Shell",
|
||||
"535": "Heat Crash",
|
||||
"536": "Leaf Tornado",
|
||||
"537": "Steamroller",
|
||||
"538": "Cotton Guard",
|
||||
"539": "Night Daze",
|
||||
"540": "Psystrike",
|
||||
"541": "Tail Slap",
|
||||
"542": "Hurricane",
|
||||
"543": "Head Charge",
|
||||
"544": "Gear Grind",
|
||||
"545": "Searing Shot",
|
||||
"546": "Techno Blast",
|
||||
"547": "Relic Song",
|
||||
"548": "Secret Sword",
|
||||
"549": "Glaciate",
|
||||
"550": "Bolt Strike",
|
||||
"551": "Blue Flare",
|
||||
"552": "Fiery Dance",
|
||||
"553": "Freeze Shock",
|
||||
"554": "Ice Burn",
|
||||
"555": "Snarl",
|
||||
"556": "Icicle Crash",
|
||||
"557": "V-create",
|
||||
"558": "Fusion Flare",
|
||||
"559": "Fusion Bolt"
|
||||
}
|
||||
3772
src/main/resources/data/species.json
Normal file
3772
src/main/resources/data/species.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user