Initial Commit

This commit is contained in:
2025-08-03 22:26:35 +02:00
commit 33673a05e8
33 changed files with 16758 additions and 0 deletions
@@ -0,0 +1,11 @@
package speiger.src.data;
import java.awt.Point;
import java.util.Map;
import java.util.UUID;
public record Collectable(String name, UUID id, CollectableType type, Point position, EndPoint end, Map<String, TaggedImage> images) {
public boolean hasEndpoint() {
return end != null;
}
}
@@ -0,0 +1,8 @@
package speiger.src.data;
public enum CollectableType {
PSWITCH,
PANEL,
MEDAL,
CHUCKS
}
@@ -0,0 +1,7 @@
package speiger.src.data;
import java.awt.Point;
public record EndPoint(Point location, boolean isFlying) {
}
@@ -0,0 +1,342 @@
package speiger.src.data;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.imageio.ImageIO;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonWriter;
public class Registry {
public static final Registry INSTANCE = new Registry();
ExecutorService service = Executors.newFixedThreadPool(4);
ExecutorCompletionService<DownloadedImage> queue = new ExecutorCompletionService<>(service);
Map<UUID, Collectable> collectables = new LinkedHashMap<>();
Set<UUID> completed = new HashSet<>();
Object syncObj = new Object();
public void load() {
parseRegistry("/assets/data/medals.json", CollectableType.MEDAL);
parseRegistry("/assets/data/panels.json", CollectableType.PANEL);
parseRegistry("/assets/data/pswitches.json", CollectableType.PSWITCH);
parseRegistry("/assets/data/chucks.json", CollectableType.CHUCKS);
dumpData();
loadCompleted();
}
private void dumpData() {
Path path = getOrigin().resolve("data");
if(Files.exists(path.resolve("images.zip"))) return;
try(InputStream stream = Registry.class.getResourceAsStream("/assets/images/images.zip")) {
if(Files.notExists(path)) Files.createDirectories(path);
Files.copy(stream, path.resolve("images.zip"));
}
catch(Exception e) {
e.printStackTrace();
}
}
public void importSave(List<UUID> ids) {
completed.clear();
completed.addAll(ids);
save();
}
private void loadCompleted() {
completed.clear();
Path path = getOrigin().resolve("data/save.json");
if(Files.notExists(path)) return;
try(BufferedReader reader = Files.newBufferedReader(path)) {
for(JsonElement element : JsonParser.parseReader(reader).getAsJsonObject().getAsJsonArray("completed")) {
completed.add(UUID.fromString(element.getAsString()));
}
}
catch(Exception e) {
e.printStackTrace();
}
}
public void save() {
JsonArray array = new JsonArray();
completed.forEach(T -> array.add(T.toString()));
Path path = getOrigin().resolve("data");
if(Files.notExists(path)) {
try {
Files.createDirectories(path);
}
catch(Exception e) {
e.printStackTrace();
}
}
try(JsonWriter writer = new JsonWriter(Files.newBufferedWriter(path.resolve("save.json")))) {
writer.setIndent("\t");
JsonObject obj = new JsonObject();
obj.add("completed", array);
Streams.write(obj, writer);
}
catch(Exception e) {
e.printStackTrace();
}
}
public void processQueue() {
List<DownloadedImage> images = new ArrayList<>();
Future<DownloadedImage> data = null;
while((data = queue.poll()) != null) {
try {
images.add(data.get());
}
catch(Exception e) {
e.printStackTrace();
}
}
synchronized(syncObj) {
try(FileSystem system = FileSystems.newFileSystem(getOrigin().resolve("data/images.zip"), Map.of("create", "true"))) {
for(DownloadedImage image : images) {
if(image.id() == null) continue;
Path path = system.getPath(image.id().toString().replace("-", "_")+".jpg");
try(OutputStream stream = Files.newOutputStream(path)) {
ImageIO.write(image.image(), "jpg", new BufferedOutputStream(stream));
}
catch(Exception e) {
e.printStackTrace();
}
}
}
catch(Exception e) {
e.printStackTrace();
}
}
}
private void parseRegistry(String path, CollectableType type) {
try(InputStreamReader reader = new InputStreamReader(Registry.class.getResourceAsStream(path))) {
for(JsonElement element : JsonParser.parseReader(reader).getAsJsonObject().getAsJsonArray("entries")) {
try {
Collectable entry = parse(element.getAsJsonObject(), type);
collectables.put(entry.id(), entry);
}
catch(Exception e) {
e.printStackTrace();
}
}
}
catch(Exception e) {
e.printStackTrace();
}
}
public BufferedImage getImage(TaggedImage image) {
Path path = getOrigin().resolve("data/images.zip");
if(Files.exists(path)) {
synchronized(syncObj) {
try(FileSystem system = FileSystems.newFileSystem(path)) {
Path resolve = system.getPath(image.fileId().toString().replace("-", "_"));
if(Files.exists(resolve)) { return ImageIO.read(new ByteArrayInputStream(Files.readAllBytes(resolve))); }
}
catch(Exception e) {
e.printStackTrace();
}
}
}
try {
return queue.submit(new DownloadTask(image)).get().image();
}
catch(Exception e) {
e.printStackTrace();
}
return null;
}
public Collectable get(UUID id) {
return collectables.get(id);
}
public int completed(CollectableType type) {
return (int)collectables.values().stream().filter(T -> completed.contains(T.id())).filter(T -> T.type() == type).count();
}
public int total(CollectableType type) {
return (int)collectables.values().stream().filter(T -> T.type() == type).count();
}
public void markComplete(UUID id) {
completed.add(id);
save();
}
public void unmarkComplete(UUID id) {
completed.remove(id);
save();
}
public boolean isCompleted(UUID id) {
return completed.contains(id);
}
public List<Collectable> collectables() {
return new ArrayList<>(collectables.values());
}
public void downloadMissingIcons(Consumer<int[]> progressTracker, AtomicBoolean cancel, Runnable completion) {
ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
ExecutorCompletionService<DownloadedImage> queue = new ExecutorCompletionService<>(service);
synchronized(syncObj) {
Map<String, String> done = Map.of();
if(Files.notExists(getOrigin().resolve("data/images.zip"))) {
done = Map.of("create", "true");
}
try(FileSystem system = FileSystems.newFileSystem(getOrigin().resolve("data/images.zip"), done)) {
int toDo = 0;
for(Collectable collect : Registry.INSTANCE.collectables()) {
for(TaggedImage image : collect.images().values()) {
Path path = system.getPath(image.fileId().toString().replace("-", "_")+".jpg");
if(Files.exists(path)) continue;
queue.submit(new DownloadTask(image, cancel));
toDo++;
}
}
int total = toDo;
progressTracker.accept(new int[] {0, total});
service.shutdown();
while(toDo > 0) {
Future<DownloadedImage> future = null;
while((future = queue.poll()) != null) {
DownloadedImage image = future.get();
toDo--;
if(image.id() == null) continue;
Path path = system.getPath(image.id().toString().replace("-", "_")+".jpg");
try(OutputStream stream = Files.newOutputStream(path)) {
ImageIO.write(image.image(), "jpg", new BufferedOutputStream(stream));
}
catch(Exception e) {
e.printStackTrace();
}
progressTracker.accept(new int[] {(total - toDo), total});
Thread.sleep(Duration.ofMillis(50));
}
}
}
catch(Exception e) {
e.printStackTrace();
}
}
completion.run();
}
private static Collectable parse(JsonObject obj, CollectableType type) {
String name = obj.get("name").getAsString();
UUID id = UUID.fromString(obj.get("id").getAsString());
Point pos = parsePosition(obj.getAsJsonObject("position"));
EndPoint point = parseEnd(obj.getAsJsonObject("end"));
Map<String, TaggedImage> images = new HashMap<>();
for(JsonElement el : obj.getAsJsonArray("images")) {
TaggedImage image = parseImage(el.getAsJsonObject());
images.put(image.tag(), image);
}
return new Collectable(name, id, type, pos, point, images);
}
private static TaggedImage parseImage(JsonObject obj) {
return new TaggedImage(UUID.fromString(obj.get("id").getAsString()), obj.get("url").getAsString(), obj.get("type").getAsString());
}
private static EndPoint parseEnd(JsonObject obj) {
return obj == null ? null : new EndPoint(parsePosition(obj), obj.get("flying").getAsBoolean());
}
private static Point parsePosition(JsonObject obj) {
return new Point(obj.get("x").getAsInt(), obj.get("y").getAsInt());
}
public static Path getOrigin() {
try {
Path data = Path.of(Registry.class.getProtectionDomain().getCodeSource().getLocation().toURI());
if(!Files.isDirectory(data)) return data.getParent();
}
catch(URISyntaxException e) {
e.printStackTrace();
}
return Path.of("");
}
private static class DownloadTask implements Callable<DownloadedImage> {
TaggedImage image;
AtomicBoolean cancel;
public DownloadTask(TaggedImage image) {
this.image = image;
}
public DownloadTask(TaggedImage image, AtomicBoolean cancel) {
this.image = image;
this.cancel = cancel;
}
@Override
public DownloadedImage call() throws Exception {
if(cancel != null && cancel.get()) {
return new DownloadedImage(null, null);
}
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
WritableByteChannel output = Channels.newChannel(stream);
ReadableByteChannel input = Channels.newChannel(new URI(image.url().replace(" ", "%20")).toURL().openStream());
ByteBuffer buffer = ByteBuffer.allocate(8192);
while(input.read(buffer) > 0) {
output.write(buffer.flip());
buffer.flip();
}
input.close();
output.close();
return new DownloadedImage(this.image.fileId(), ImageIO.read(new ByteArrayInputStream(stream.toByteArray())));
}
catch(Exception e) {
e.printStackTrace();
}
return new DownloadedImage(null, null);
}
}
private record DownloadedImage(UUID id, BufferedImage image) {}
}
@@ -0,0 +1,7 @@
package speiger.src.data;
import java.util.UUID;
public record TaggedImage(UUID fileId, String url, String tag) {
}
@@ -0,0 +1,68 @@
package speiger.src.ui;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.border.EmptyBorder;
import speiger.src.data.Registry;
public class DownloadTask extends JDialog {
private static final long serialVersionUID = 142921231931795121L;
public DownloadTask(JFrame owner) {
super(owner, owner != null);
setTitle("Downloading Images");
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setBounds(100, 100, 326, 153);
setLocationRelativeTo(owner);
getContentPane().setLayout(new BorderLayout());
JPanel content = new JPanel(null);
content.setBorder(new EmptyBorder(5, 5, 5, 5));
JLabel label = new JLabel("Downloading location Images");
label.setBounds(10, 11, 210, 14);
content.add(label);
JProgressBar progress = new JProgressBar();
progress.setStringPainted(true);
progress.setString("0 / 0");
progress.setBounds(10, 38, 284, 25);
content.add(progress);
getContentPane().add(content, BorderLayout.CENTER);
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
getContentPane().add(buttonPane, BorderLayout.SOUTH);
AtomicBoolean task = new AtomicBoolean();
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(T -> {
task.set(true);
dispose();
});
buttonPane.add(cancelButton);
Thread.ofPlatform().start(() -> {
Registry.INSTANCE.downloadMissingIcons(T -> {
EventQueue.invokeLater(() -> {
progress.setString(T[0]+" / "+T[1]);
progress.setMaximum(T[1]);
progress.setValue(T[0]);
repaint();
});
}, task, () -> {
EventQueue.invokeLater(this::dispose);
});
});
setVisible(true);
}
}
@@ -0,0 +1,84 @@
package speiger.src.ui;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Panel;
import java.awt.event.ComponentAdapter;
import java.awt.event.MouseAdapter;
import java.awt.image.BufferedImage;
import java.util.Map;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import speiger.src.data.Registry;
import speiger.src.data.TaggedImage;
public class LocationDisplay extends JDialog {
private static final long serialVersionUID = 3803744617604239360L;
public LocationDisplay(JFrame owner, String name, Map<String, TaggedImage> images) {
setTitle(name+" Images");
setBounds(0, 0, 700, 700);
setResizable(true);
setLocationRelativeTo(owner);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP);
getContentPane().add(tabbedPane, BorderLayout.CENTER);
images.forEach((K, V) -> {
JPanel panel = new JPanel(new BorderLayout());
tabbedPane.addTab(firstLetterUppercase(K), panel);
Thread.ofPlatform().name("Image Loader", 1).start(() -> {
BufferedImage image = Registry.INSTANCE.getImage(V);
if(image == null) {
EventQueue.invokeLater(() -> tabbedPane.remove(panel));
return;
}
EventQueue.invokeLater(() -> {
panel.add(new ImagePanel(image, this), BorderLayout.CENTER);
repaint();
});
});
});
setVisible(true);
addComponentListener(new ComponentAdapter() {
public void componentResized(java.awt.event.ComponentEvent e) {
repaint();
};
});
}
public static String firstLetterUppercase(String string) {
if(string == null || string.isEmpty()) {
return string;
}
String first = Character.toString(string.charAt(0));
return string.replaceFirst(first, first.toUpperCase());
}
private static class ImagePanel extends Panel {
private static final long serialVersionUID = 5945747347624323542L;
BufferedImage image;
public ImagePanel(BufferedImage image, JDialog owner) {
this.image = image;
addMouseListener(new MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent e) {
owner.dispose();
};
});
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.drawImage(image, 0, 0, getWidth(), getHeight(), this);
}
}
}
+383
View File
@@ -0,0 +1,383 @@
package speiger.src.ui;
import java.awt.BorderLayout;
import java.awt.Checkbox;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.io.BufferedReader;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.prefs.Preferences;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import speiger.src.data.Collectable;
import speiger.src.data.CollectableType;
import speiger.src.data.Registry;
public class MapPanel extends JPanel {
private static final JFileChooser CHOOSER = new JFileChooser(new File(Preferences.userRoot().node(MapPanel.class.getName()).get("Last Folder", new File(".").getAbsolutePath())));
private static final long serialVersionUID = 7639401429824622357L;
private static Checkbox P_SWITCH;
private static Checkbox MEDALS;
private static Checkbox PANEL;
private static Checkbox CHUCKS;
JFrame owner;
Runnable progressUpdate;
float zoom = 1F;
Point offset = new Point(-568, -271);
Point lastDrag = null;
Image map = new ImageIcon(MapPanel.class.getResource("/assets/images/map.png")).getImage();
Image[] medal = new Image[] {
new ImageIcon(MapPanel.class.getResource("/assets/images/medal.png")).getImage(),
new ImageIcon(MapPanel.class.getResource("/assets/images/medal_disabled.png")).getImage()
};
Image[] panel = new Image[] {
new ImageIcon(MapPanel.class.getResource("/assets/images/panel.png")).getImage(),
new ImageIcon(MapPanel.class.getResource("/assets/images/panel_disabled.png")).getImage()
};
Image[] pswitch = new Image[] {
new ImageIcon(MapPanel.class.getResource("/assets/images/pswitch.png")).getImage(),
new ImageIcon(MapPanel.class.getResource("/assets/images/pswitch_disabled.png")).getImage()
};
Image[] chuck = new Image[] {
new ImageIcon(MapPanel.class.getResource("/assets/images/chucks.png")).getImage(),
new ImageIcon(MapPanel.class.getResource("/assets/images/chucks_disabled.png")).getImage()
};
List<Marker> markers = new ArrayList<Marker>();
List<Marker> visibleMarkers = new ArrayList<Marker>();
Marker lastMarker = null;
public static void main(String...args) {
Toolkit.getDefaultToolkit().setDynamicLayout(false);
JFrame frame = new JFrame();
frame.setLayout(new BorderLayout());
frame.setBounds(0, 0, 800, 600);
frame.setTitle("Mario Kart World Progress Tracker");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
MapPanel panel = new MapPanel(frame);
frame.add(panel, BorderLayout.CENTER);
JMenuBar bar = new JMenuBar();
frame.setJMenuBar(bar);
JMenu menu = bar.add(new JMenu("File"));
menu.add(item("Import Save", T -> {
Preferences prefs = Preferences.userRoot().node(MapPanel.class.getName());
if(CHOOSER.showSaveDialog(frame) == JFileChooser.APPROVE_OPTION) {
File file = CHOOSER.getSelectedFile();
if(!file.getName().endsWith(".json")) return;
try(BufferedReader reader = Files.newBufferedReader(file.toPath())) {
List<UUID> ids = new ArrayList<>();
for(JsonElement element : JsonParser.parseReader(reader).getAsJsonObject().getAsJsonArray("completed")) {
ids.add(UUID.fromString(element.getAsString()));
}
Registry.INSTANCE.importSave(ids);
panel.onImported();
}
catch(Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(frame, "Importing caused an error \n"+e.toString(), "Importing Error", JOptionPane.ERROR_MESSAGE);
}
prefs.put("Last Folder", file.getParent());
}
}, null));
menu.add(item("Export Save", T -> {
Preferences prefs = Preferences.userRoot().node(MapPanel.class.getName());
if(CHOOSER.showSaveDialog(frame) == JFileChooser.APPROVE_OPTION) {
File file = CHOOSER.getSelectedFile();
String name = file.getName();
int index = name.lastIndexOf(".");
if(index > -1) name = name.substring(0, index);
name += ".json";
Path save = Registry.getOrigin().resolve("data/save.json");
if(Files.notExists(save)) return;
try { Files.copy(save, file.toPath().getParent().resolve(name), StandardCopyOption.REPLACE_EXISTING); }
catch(Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(frame, "Exporting caused an error \n"+e.toString(), "Exporting Error", JOptionPane.ERROR_MESSAGE);
}
prefs.put("Last Folder", file.getParent());
}
}, null));
menu.add(item("Download all Previews", T -> {
if(JOptionPane.showConfirmDialog(frame, "Are you sure you want to download all missing images?", "Download Images", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
new DownloadTask(frame);
}
}, null));
menu.add(item("Credits", T -> {
JOptionPane.showMessageDialog(frame, "Credits Go to:\nhttps://mkw.techtangents.net and\nhttps://www.gamerguides.com/mario-kart-world/maps/world \nwhich are used as Resource Providers", "Credits", JOptionPane.INFORMATION_MESSAGE);
}, null));
JMenu controls = new JMenu("Controls");
controls.add("Left Button: Toggle Completion");
controls.add("Right Button: Show Location");
controls.add("Left Button Dragging: Move Map");
controls.add("Mouse Scrolling: Zoom");
JMenu bulk = new JMenu("Bulk Actions");
bulk.add(item("Complete Visible", T -> {
panel.setCompletionState(true);
}, null));
bulk.add(item("Uncomplete Visible", T -> {
panel.setCompletionState(false);
}, null));
bar.add(bulk);
JMenu fuckups = new JMenu("I Fucked up");
fuckups.add(item("Reset Zoom", T -> {
panel.resetZoom();
}, KeyStroke.getKeyStroke('z')));
fuckups.add(item("Reset Offset", T -> {
panel.resetMap();
}, KeyStroke.getKeyStroke('r')));
bar.add(fuckups);
bar.add(controls);
JPanel sideMenu = new JPanel();
sideMenu.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 5, 0));
sideMenu.add(new JLabel("Filter"));
P_SWITCH = (Checkbox)sideMenu.add(box("P-Switches", T -> onFilterChanged(panel::updateFilter)));
MEDALS = (Checkbox)sideMenu.add(box("Medals", T -> onFilterChanged(panel::updateFilter)));
PANEL = (Checkbox)sideMenu.add(box("Panels", T -> onFilterChanged(panel::updateFilter)));
CHUCKS = (Checkbox)sideMenu.add(box("Chucks", T -> onFilterChanged(panel::updateFilter)));
JLabel PSwitchProgress = new JLabel(Registry.INSTANCE.completed(CollectableType.PSWITCH)+" / "+Registry.INSTANCE.total(CollectableType.PSWITCH));
JLabel MedalsProgress = new JLabel(Registry.INSTANCE.completed(CollectableType.MEDAL)+" / "+Registry.INSTANCE.total(CollectableType.MEDAL));
JLabel PanelProgress = new JLabel(Registry.INSTANCE.completed(CollectableType.PANEL)+" / "+Registry.INSTANCE.total(CollectableType.PANEL));
JLabel ChucksProgress = new JLabel(Registry.INSTANCE.completed(CollectableType.CHUCKS)+" / "+Registry.INSTANCE.total(CollectableType.CHUCKS));
panel.progressUpdate = () -> {
PSwitchProgress.setText(Registry.INSTANCE.completed(CollectableType.PSWITCH)+" / "+Registry.INSTANCE.total(CollectableType.PSWITCH));
MedalsProgress.setText(Registry.INSTANCE.completed(CollectableType.MEDAL)+" / "+Registry.INSTANCE.total(CollectableType.MEDAL));
PanelProgress.setText(Registry.INSTANCE.completed(CollectableType.PANEL)+" / "+Registry.INSTANCE.total(CollectableType.PANEL));
ChucksProgress.setText(Registry.INSTANCE.completed(CollectableType.CHUCKS)+" / "+Registry.INSTANCE.total(CollectableType.CHUCKS));
EventQueue.invokeLater(sideMenu::repaint);
};
sideMenu.add(new JLabel("P-Switches:"));
sideMenu.add(PSwitchProgress);
sideMenu.add(new JLabel("Medals:"));
sideMenu.add(MedalsProgress);
sideMenu.add(new JLabel("Panels:"));
sideMenu.add(PanelProgress);
sideMenu.add(new JLabel("Chucks:"));
sideMenu.add(ChucksProgress);
frame.add(sideMenu, BorderLayout.WEST);
panel.resetMap();
frame.setVisible(true);
while(true) {
try {
Registry.INSTANCE.processQueue();
Thread.sleep(Duration.ofMillis(50));
}
catch(Exception e) {
e.printStackTrace();
}
}
}
private static Checkbox box(String name, ItemListener listener) {
Checkbox box = new Checkbox(name, true);
box.addItemListener(listener);
return box;
}
private static void onFilterChanged(Consumer<EnumSet<CollectableType>> consumer) {
EnumSet<CollectableType> types = EnumSet.noneOf(CollectableType.class);
if(P_SWITCH.getState()) types.add(CollectableType.PSWITCH);
if(MEDALS.getState()) types.add(CollectableType.MEDAL);
if(PANEL.getState()) types.add(CollectableType.PANEL);
if(CHUCKS.getState()) types.add(CollectableType.CHUCKS);
consumer.accept(types);
}
private static JMenuItem item(String name, ActionListener action, KeyStroke stroke) {
JMenuItem item = new JMenuItem(name);
item.addActionListener(action);
item.setAccelerator(stroke);
return item;
}
private Image[] fromType(CollectableType type) {
return switch(type) {
case MEDAL -> medal;
case PANEL -> panel;
case PSWITCH -> pswitch;
case CHUCKS -> chuck;
default -> pswitch;
};
}
public MapPanel(JFrame frame) {
this.owner = frame;
Registry.INSTANCE.load();
for(Collectable entry : Registry.INSTANCE.collectables()) {
Marker marker = new Marker(entry.id(), entry.type(), entry.name(), entry.position(), fromType(entry.type()));
markers.add(marker);
visibleMarkers.add(marker);
}
MouseAdapter adapter = new MouseAdapter() {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
double lastZoom = zoom;
zoom = (e.getPreciseWheelRotation() < 0 ? zoom * 1.1F : zoom / 1.1F);
Point mouse = e.getPoint();
double scaleDiff = zoom / lastZoom;
offset.x = (int) (mouse.x - scaleDiff * (mouse.x - offset.x));
offset.y = (int) (mouse.y - scaleDiff * (mouse.y - offset.y));
repaint();
}
@Override
public void mousePressed(MouseEvent e) {
lastDrag = e.getPoint();
}
@Override
public void mouseMoved(MouseEvent e) {
Point hover = screenToMap(e.getPoint());
Marker marker = findMarker(hover, zoom);
if(marker == lastMarker) return;
if(lastMarker != null) lastMarker.setHovered(false);
lastMarker = marker;
if(marker == null) return;
marker.setHovered(true);
repaint();
}
@Override
public void mouseDragged(MouseEvent e) {
if(lastDrag == null) return;
offset.translate((int)(e.getX() - lastDrag.getX()), (int)(e.getY() - lastDrag.getY()));
lastDrag = e.getPoint();
repaint();
}
@Override
public void mouseClicked(MouseEvent e) {
Point hover = screenToMap(e.getPoint());
Marker marker = findMarker(hover, zoom);
if(marker == null) return;
if(e.getButton() == 1) {
marker.toggle();
repaint();
if(progressUpdate != null) progressUpdate.run();
}
else if(e.getButton() == 3) {
Collectable element = Registry.INSTANCE.get(marker.id());
new LocationDisplay(owner, element.name(), element.images());
}
}
};
addMouseListener(adapter);
addMouseWheelListener(adapter);
addMouseMotionListener(adapter);
}
public void onImported() {
markers.forEach(Marker::updateCompletion);
EventQueue.invokeLater(this::repaint);
}
public void resetZoom() {
zoom = 1F;
EventQueue.invokeLater(this::repaint);
}
public void resetMap() {
int imgWidth = map.getWidth(this);
int imgHeight = map.getHeight(this);
int panelWidth = getWidth();
int panelHeight = getHeight();
if (imgWidth > 0 && imgHeight > 0 && panelWidth > 0 && panelHeight > 0) {
offset.setLocation((panelWidth - (int)(imgWidth * zoom)) / 2, (panelHeight - (int)(imgHeight * zoom)) / 2);
}
EventQueue.invokeLater(this::repaint);
}
public void updateFilter(EnumSet<CollectableType> types) {
visibleMarkers.clear();
markers.forEach(T -> {
if(types.contains(T.type())) visibleMarkers.add(T);
});
EventQueue.invokeLater(this::repaint);
}
public void setCompletionState(boolean value) {
visibleMarkers.forEach(T -> T.setCompletion(value));
}
@Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
AffineTransform at = new AffineTransform();
at.translate(offset.x, offset.y);
at.scale(zoom, zoom);
g2.setTransform(at);
g2.drawImage(map, 0, 0, this);
for(Marker marker : visibleMarkers) {
marker.draw(g2, (float)zoom);
}
for(Marker marker : visibleMarkers) {
marker.drawHover(g2, (float)zoom);
}
}
private Marker findMarker(Point point, float scale) {
for(int i = 0,m=visibleMarkers.size();i<m;i++) {
Marker marker = visibleMarkers.get(i);
if(marker.isHovered(point, scale)) return marker;
}
return null;
}
private Point screenToMap(Point screenPoint) {
int mapX = (int) ((screenPoint.x - offset.x) / zoom);
int mapY = (int) ((screenPoint.y - offset.y) / zoom);
return new Point(mapX, mapY);
}
}
+76
View File
@@ -0,0 +1,76 @@
package speiger.src.ui;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.util.UUID;
import speiger.src.data.CollectableType;
import speiger.src.data.Registry;
public class Marker {
UUID id;
CollectableType type;
Point point;
Image[] display;
String name;
boolean disabled;
int size = 32;
boolean hovered;
public Marker(UUID id, CollectableType type, String name, Point point, Image[] display) {
this.id = id;
this.type = type;
this.point = point;
this.display = display;
this.name = name;
this.disabled = Registry.INSTANCE.isCompleted(id);
}
public void toggle() {
setCompletion(!disabled);
}
public void setCompletion(boolean value) {
disabled = value;
if(disabled) Registry.INSTANCE.markComplete(id);
else Registry.INSTANCE.unmarkComplete(id);
}
public String name() { return name; }
public UUID id() { return id; }
public CollectableType type() { return type;}
public boolean isHovered(Point mouse, float scale) {
int rad = Math.clamp((int)(32 / scale), 5, 32)>>1;
return mouse.x >= point.x - rad && mouse.x <= point.x + rad && mouse.y >= point.y - rad && mouse.y <= point.y + rad;
}
public boolean isHovered() {
return hovered;
}
public void setHovered(boolean value) {
this.hovered = value;
}
public void updateCompletion() {
disabled = Registry.INSTANCE.isCompleted(id);
}
public void draw(Graphics2D g, float scale) {
int realSize = Math.clamp((int)(32 / scale), 5, 32);
g.drawImage(display[disabled ? 1 : 0], point.x-(realSize>>1), point.y-(realSize>>1), realSize, realSize, null);
}
public void drawHover(Graphics2D g, float scale) {
int realSize = Math.clamp((int)(32 / scale), 5, 32);
if(hovered) {
g.setColor(Color.BLACK);
g.setStroke(new BasicStroke(1F / scale *2F));
g.drawRect(point.x-(realSize>>1), point.y-(realSize>>1), realSize, realSize);
}
}
}
@@ -0,0 +1,174 @@
package speiger.src.ui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
public class VerticalFlowLayout implements LayoutManager, java.io.Serializable {
private static final long serialVersionUID = -2732129344472207193L;
/**
* This value indicates that each row of components
* should be left-justified.
*/
public static final int TOP = 0;
/**
* This value indicates that each row of components
* should be centered.
*/
public static final int CENTER = 1;
/**
* This value indicates that each row of components
* should be right-justified.
*/
public static final int BOTTOM = 2;
int align;
protected int hgap;
protected int vgap;
/**
* Constructs a new <code>FlowLayout</code> with a centered alignment and a
* default 5-unit horizontal and vertical gap.
*/
public VerticalFlowLayout() {
this(CENTER, 5, 5);
}
public VerticalFlowLayout(int align) {
this(align, 5, 5);
}
public VerticalFlowLayout(int align, int hgap, int vgap) {
this.hgap = hgap;
this.vgap = vgap;
setAlignment(align);
}
public int getAlignment() { return align; }
public void setAlignment(int align) { this.align = align; }
public int getHgap() { return hgap; }
public void setHgap(int hgap) { this.hgap = hgap; }
public int getVgap() { return vgap; }
public void setVgap(int vgap) { this.vgap = vgap; }
public void addLayoutComponent(String name, Component comp) {}
public void removeLayoutComponent(Component comp) {}
public Dimension preferredLayoutSize(Container target) {
synchronized (target.getTreeLock()) {
Dimension dim = new Dimension(0, 0);
int nmembers = target.getComponentCount();
Boolean firstVisibleComponent = true;
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = m.getPreferredSize();
dim.width = Math.max(dim.width, d.width);
if (firstVisibleComponent) {
firstVisibleComponent = false;
} else {
dim.height += vgap;
}
dim.height += d.height;
}
}
Insets insets = target.getInsets();
dim.width += insets.left + insets.right + hgap * 2;
dim.height += insets.top + insets.bottom + vgap * 2;
return dim;
}
}
public Dimension minimumLayoutSize(Container target) {
synchronized (target.getTreeLock()) {
Dimension dim = new Dimension(0, 0);
int nmembers = target.getComponentCount();
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = m.getMinimumSize();
dim.width = Math.max(dim.width, d.width);
if (i > 0) {
dim.height += vgap;
}
dim.height += d.height;
}
}
Insets insets = target.getInsets();
dim.width += insets.left + insets.right + hgap * 2;
dim.height += insets.top + insets.bottom + vgap * 2;
return dim;
}
}
private void moveComponents(Container target, int x, int y, int width, int height,
int rowStart, int rowEnd, boolean ltr) {
synchronized (target.getTreeLock()) {
switch (align) {
case TOP:
y += ltr ? 0 : height;
break;
case CENTER:
y += height / 2;
break;
case BOTTOM:
y += ltr ? height : 0;
break;
}
for (int i = rowStart; i < rowEnd; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
if (ltr) {
m.setLocation(x, y);
} else {
m.setLocation(x, target.getHeight() - y - m.getHeight());
}
y += m.getHeight() + vgap;
}
}
}
}
public void layoutContainer(Container target) {
synchronized (target.getTreeLock()) {
Insets insets = target.getInsets();
int maxlen = target.getHeight() - (insets.top + insets.bottom + vgap * 2);
int nmembers = target.getComponentCount();
int x = insets.left + hgap, y = 0;
int roww = 0, start = 0;
boolean ltr = target.getComponentOrientation().isLeftToRight();
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = m.getPreferredSize();
m.setSize(d.width, d.height);
if ((y == 0) || ((y + d.height) <= maxlen)) {
if (y > 0) y += vgap;
y += d.height;
roww = Math.max(roww, d.width);
} else {
moveComponents(target, x, insets.top + vgap, roww, maxlen - y, start, i, ltr);
y = d.height;
x += hgap + roww;
roww = d.width;
start = i;
}
}
}
moveComponents(target, x, insets.top + vgap, roww, maxlen - y, start, nmembers, ltr);
}
}
}
@@ -0,0 +1,49 @@
{
"entries": [
{
"name": "Charging Chuck 1",
"id": "042cb714-5b4b-4e0b-a2cb-f480a4f72405",
"position": {
"x": 942,
"y": 204
},
"images": [
{
"type": "location",
"id": "01377fd7-896e-49db-b9d6-41895caada58",
"url": "https://www.gamerguides.com/assets/maps/markers/Screenshot_2025-06-09_14-38-48.jpg"
}
]
},
{
"name": "Charging Chuck 2",
"id": "f7d46b9b-5cba-4c51-9ce9-b10e6a57ff0f",
"position": {
"x": 918,
"y": 504
},
"images": [
{
"type": "location",
"id": "70185b92-874c-4e3c-b5e8-fc893d68577d",
"url": "https://www.gamerguides.com/assets/maps/markers/Screenshot_2025-06-11_09-19-35.jpg"
}
]
},
{
"name": "Charging Chuck 3",
"id": "4212133d-7561-43e7-80e2-3b70ceeb87db",
"position": {
"x": 1300,
"y": 440
},
"images": [
{
"type": "location",
"id": "246e2285-f893-4264-86e7-ed7f2655e69d",
"url": "https://www.gamerguides.com/assets/maps/markers/chargin-chuck.jpg"
}
]
}
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+272
View File
@@ -0,0 +1,272 @@
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.imageio.ImageIO;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonWriter;
import speiger.src.data.Collectable;
import speiger.src.data.Registry;
import speiger.src.data.TaggedImage;
public class Testing {
public static void main(String...args) {
applyPatches();
downloadObjects();
}
private static void applyPatches() {
patch(getOrigin().resolve("src/main/resources/assets/data/medals.json"), getOrigin().resolve("backup/medal-patch.txt"));
patch(getOrigin().resolve("src/main/resources/assets/data/pswitches.json"), getOrigin().resolve("backup/pswitch-patch.txt"));
}
private static void patch(Path baseFile, Path patchFile) {
Map<String, String> patches = new HashMap<>();
try {
for(String entry : Files.readAllLines(patchFile)) {
String[] split = entry.split(" = ");
if(split.length != 2) continue;
patches.put(split[0], split[1]);
}
JsonObject obj = null;
try(BufferedReader reader = Files.newBufferedReader(baseFile)) {
obj = JsonParser.parseReader(reader).getAsJsonObject();
}
catch(Exception e) {
e.printStackTrace();
return;
}
for(JsonElement element : obj.getAsJsonArray("entries")) {
JsonObject entry = element.getAsJsonObject();
for(JsonElement image : entry.getAsJsonArray("images")) {
JsonObject imageObj = image.getAsJsonObject();
String replacement = patches.get(imageObj.get("url").getAsString().replace(" ", "%20"));
if(replacement == null) {
continue;
}
imageObj.addProperty("url", replacement);
System.out.println("Successfully Patched to: "+replacement);
}
}
try(JsonWriter writer = new JsonWriter(Files.newBufferedWriter(baseFile))) {
writer.setIndent("\t");
Streams.write(obj, writer);
}
catch(Exception e) {
e.printStackTrace();
}
}
catch(Exception e) {
e.printStackTrace();
}
}
private static void downloadObjects() {
Registry.INSTANCE.load();
ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
ExecutorCompletionService<DownloadedImage> queue = new ExecutorCompletionService<>(service);
try(FileSystem system = FileSystems.newFileSystem(getOrigin().resolve("output/images.zip"), Map.of("create", "true"))) {
int toDo = 0;
for(Collectable collect : Registry.INSTANCE.collectables()) {
for(TaggedImage image : collect.images().values()) {
Path path = system.getPath(image.fileId().toString().replace("-", "_")+".jpg");
if(Files.exists(path)) continue;
queue.submit(new Task(image));
toDo++;
}
}
int total = toDo;
service.shutdown();
while(toDo > 0) {
Future<DownloadedImage> future = null;
while((future = queue.poll()) != null) {
DownloadedImage image = future.get();
toDo--;
if(image.id() == null) continue;
Path path = system.getPath(image.id().toString().replace("-", "_")+".jpg");
try { Files.write(path, image.data()); }
catch(Exception e) { e.printStackTrace(); }
System.out.println("Downloaded: "+(total - toDo)+" / "+total);
}
Thread.sleep(50);
}
}
catch(Exception e) {
e.printStackTrace();
}
}
public static void convertObjects() throws Exception {
Map<String, String> data = Map.of("medals.json", "medal", "panels.json", "panel", "pswitches.json", "pswitch");
try(DirectoryStream<Path> files = Files.newDirectoryStream(Paths.get("D:\\Workspaces\\Java17\\MKW_Map\\src\\main\\resources\\assets\\data"))) {
List<Path> result = new ArrayList<>();
files.forEach(result::add);
for(Path path : result) {
JsonObject obj = convert(JsonParser.parseReader(Files.newBufferedReader(path)).getAsJsonObject().getAsJsonArray("entries"), data.get(path.getFileName().toString()));
String name = path.getFileName().toString();
name = name.substring(0, name.length()-5);
System.out.println("Testing: "+name);
try(JsonWriter writer = new JsonWriter(Files.newBufferedWriter(path.getParent().resolve(name+"new.json")))) {
writer.setIndent("\t");
Streams.write(obj, writer);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
catch(Exception e) {
e.printStackTrace();
}
}
private static JsonObject convert(JsonArray inputArray, String type) {
JsonArray result = new JsonArray();
for(JsonElement entry : inputArray) {
JsonObject obj = entry.getAsJsonObject();
String url = applyHTML(obj.get("name").getAsString(), type);
Set<String> types = parseArray(obj.getAsJsonArray("imgs"));
JsonObject newEntry = new JsonObject();
newEntry.addProperty("name", obj.get("name").getAsString());
newEntry.addProperty("id", UUID.randomUUID().toString());
newEntry.add("position", convert(obj.getAsJsonArray("map_position"), obj.getAsJsonArray("map_offset")));
if(obj.has("end_position")) {
JsonArray array = obj.getAsJsonArray("end_position");
JsonObject end = new JsonObject();
end.addProperty("flying", obj.has("end_flying"));
end.addProperty("x", array.get(0).getAsInt());
end.addProperty("y", array.get(1).getAsInt());
newEntry.add("end", end);
}
JsonArray images = new JsonArray();
if(types.contains("location")) {
JsonObject imageEntry = new JsonObject();
imageEntry.addProperty("type", "location");
imageEntry.addProperty("id", UUID.randomUUID().toString());
imageEntry.addProperty("url", url+"/location.jpg");
images.add(imageEntry);
}
if(types.contains("title")) {
JsonObject imageEntry = new JsonObject();
imageEntry.addProperty("type", "title");
imageEntry.addProperty("id", UUID.randomUUID().toString());
imageEntry.addProperty("url", url+"/title.jpg");
images.add(imageEntry);
}
newEntry.add("images", images);
result.add(newEntry);
}
JsonObject resultObj = new JsonObject();
resultObj.add("entries", result);
return resultObj;
}
private static JsonObject convert(JsonArray pos, JsonArray offset) {
int x = pos.get(0).getAsInt();
int y = pos.get(1).getAsInt();
if(offset != null && offset.size() == 2) {
x += offset.get(0).getAsInt();
y += offset.get(1).getAsInt();
}
JsonObject obj = new JsonObject();
obj.addProperty("x", x);
obj.addProperty("y", y);
return obj;
}
private static String applyHTML(String input, String type) {
return "https://mkw.techtangents.net/marker/"+type+"/"+input.replace("#", "").replace("?", "").replace("\"", "");
}
private static Set<String> parseArray(JsonArray array) {
Set<String> entry = new HashSet<>();
for(JsonElement element : array) {
entry.add(element.toString().replace("\"", ""));
}
return entry;
}
private static Path getOrigin() {
try {
Path data = Path.of(Registry.class.getProtectionDomain().getCodeSource().getLocation().toURI());
if(!Files.isDirectory(data)) return data.getParent();
}
catch(URISyntaxException e) { e.printStackTrace(); }
return Path.of("");
}
public static class Task implements Callable<DownloadedImage> {
TaggedImage image;
public Task(TaggedImage image) {
this.image = image;
}
@Override
public Testing.DownloadedImage call() throws Exception {
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
WritableByteChannel output = Channels.newChannel(stream);
ReadableByteChannel input = Channels.newChannel(new URI(image.url().replace(" ", "%20")).toURL().openStream());
ByteBuffer buffer = ByteBuffer.allocate(8192);
while(input.read(buffer) > 0) {
output.write(buffer.flip());
buffer.flip();
}
input.close();
output.close();
BufferedImage image = ImageIO.read(new ByteArrayInputStream(stream.toByteArray()));
stream.reset();
ImageIO.write(image, "jpg", stream);
return new DownloadedImage(this.image.fileId(), stream.toByteArray());
}
catch(Exception e) {
e.printStackTrace();
}
return new DownloadedImage(null, null);
}
}
public record DownloadedImage(UUID id, byte[] data) {
}
//https://mkw.techtangents.net/marker/pswitch/Collect%20blue%20coins%20along%20a%20scenic%20back%20route!/300_location.jpg
}