Initial Commit

This commit is contained in:
Speiger 2025-08-03 22:10:51 +02:00
commit 33673a05e8
33 changed files with 16758 additions and 0 deletions

32
.classpath Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="bin/main" path="src/main/java">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/main" path="src/main/resources">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/test" path="src/test/java">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/test" path="src/test/resources">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

6
.gitattributes vendored Normal file
View File

@ -0,0 +1,6 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build
/bin/
/.settings/
/.project
/data
/backup
/output

47
build.gradle Normal file
View File

@ -0,0 +1,47 @@
plugins {
id 'java-library'
}
archivesBaseName = "Mario Kart World Tracker"
repositories {
mavenCentral()
}
version = 1.0
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(21)
jar {
manifest {
attributes "Main-Class": 'speiger.src.ui.MapPanel'
}
from {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
configurations.runtimeClasspath.collect { zipTree(it) }
}
}
task baseJar(type: Jar) {
from sourceSets.main.output
archiveClassifier.set("slim")
exclude('assets/images/images.zip')
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
manifest {
attributes "Main-Class": 'speiger.src.ui.MapPanel'
}
from {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
configurations.runtimeClasspath.collect { zipTree(it) }
}
}
dependencies {
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.jsoup:jsoup:1.19.1'
implementation 'commons-codec:commons-codec:1.19.0'
}
artifacts {
archives baseJar
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

104
gradlew.bat vendored Normal file
View File

@ -0,0 +1,104 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

10
settings.gradle Normal file
View File

@ -0,0 +1,10 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/6.4/userguide/multi_project_builds.html
*/
rootProject.name = 'MKW_Map'

View File

@ -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;
}
}

View File

@ -0,0 +1,8 @@
package speiger.src.data;
public enum CollectableType {
PSWITCH,
PANEL,
MEDAL,
CHUCKS
}

View File

@ -0,0 +1,7 @@
package speiger.src.data;
import java.awt.Point;
public record EndPoint(Point location, boolean isFlying) {
}

View File

@ -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) {}
}

View File

@ -0,0 +1,7 @@
package speiger.src.data;
import java.util.UUID;
public record TaggedImage(UUID fileId, String url, String tag) {
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

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);
}
}

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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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
src/test/java/Testing.java Normal file
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
}