commit 4a38dbfbdcd9d28516ecaa72557a56e40b5eaaf0 Author: Siphalor Date: Wed Jun 10 10:12:39 2020 +0200 Initial commit (basic backend stuff) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43b3068 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# gradle specific directories +.gradle/ +build/ + +# IDE specific stuff +.idea/ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..8ffaf74 --- /dev/null +++ b/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'java' + id 'application' +} + +group 'de.siphalor' +version '1.0-SNAPSHOT' + +sourceCompatibility = JavaVersion.VERSION_11 +targetCompatibility = JavaVersion.VERSION_11 + +repositories { + mavenCentral() +} + +dependencies { + compile "org.jetbrains:annotations:16.0.2" + + testImplementation('org.junit.jupiter:junit-jupiter:5.6.2') +} + +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } +} + +application { + mainClassName = "de.siphalor.was.Start" +} + +jar { + manifest { + attributes( + 'Main-Class': 'de.siphalor.was.Start' + ) + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f3d88b1 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ba94df8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..2fe81a7 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9618d8d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@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 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 diff --git a/packs/test/lang/de_de.lang b/packs/test/lang/de_de.lang new file mode 100644 index 0000000..bcb4572 --- /dev/null +++ b/packs/test/lang/de_de.lang @@ -0,0 +1 @@ +test.hello-world = Hallo Welt! diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..6d26c3d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'what-a-storage' + diff --git a/src/main/java/de/siphalor/was/Start.java b/src/main/java/de/siphalor/was/Start.java new file mode 100644 index 0000000..918f45f --- /dev/null +++ b/src/main/java/de/siphalor/was/Start.java @@ -0,0 +1,16 @@ +package de.siphalor.was; + +import de.siphalor.was.content.lang.I18n; + +public class Start { + public static void main(String[] args) { + WhatAStorage was = WhatAStorage.getInstance(); + was.reload(); + was.start(); + + I18n.setLang("de_de", was.getContentManager()); + System.out.println(I18n.get("test.hello-world")); + + was.run(); + } +} diff --git a/src/main/java/de/siphalor/was/WhatAStorage.java b/src/main/java/de/siphalor/was/WhatAStorage.java new file mode 100644 index 0000000..edb3235 --- /dev/null +++ b/src/main/java/de/siphalor/was/WhatAStorage.java @@ -0,0 +1,129 @@ +package de.siphalor.was; + +import de.siphalor.was.content.ContentManager; +import de.siphalor.was.content.lang.I18n; +import de.siphalor.was.content.pack.FileContentPack; +import de.siphalor.was.content.pack.JarContentPack; +import de.siphalor.was.content.product.ProductManager; +import de.siphalor.was.content.quest.QuestManager; +import de.siphalor.was.state.MainMenuState; +import de.siphalor.was.state.State; + +import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.nio.file.Path; + +public class WhatAStorage { + public static final String TITLE = "What a Storage"; + private static final WhatAStorage INSTANCE = new WhatAStorage(); + + public static WhatAStorage getInstance() { + return INSTANCE; + } + + private final ContentManager contentManager; + private final JarContentPack mainPack; + private final ProductManager productManager; + private final QuestManager questManager; + + private Frame frame; + private State state; + + private long lastTick; + private long minTickTime = 1000 / 60; + private Canvas canvas; + + private WhatAStorage() { + contentManager = new ContentManager(); + mainPack = new JarContentPack("", "content"); + contentManager.addPack(mainPack); + productManager = new ProductManager(); + questManager = new QuestManager(); + } + + public ContentManager getContentManager() { + return contentManager; + } + + public ProductManager getProductManager() { + return productManager; + } + + public QuestManager getQuestManager() { + return questManager; + } + + public void reload() { + contentManager.clear(); + + contentManager.addPack(mainPack); + + File[] packDirs = Path.of("packs").toFile().listFiles(File::isDirectory); + for (File dir : packDirs) { + contentManager.addPack(new FileContentPack(dir.getName(), dir.toPath())); + } + + I18n.reload(contentManager); + + productManager.clear(); + questManager.clear(); + productManager.reload(contentManager); + questManager.reload(contentManager); + + System.out.println("Reloaded game content"); + } + + public void start() { + frame = new Frame(TITLE); + frame.setVisible(true); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + super.windowClosing(e); + frame.dispose(); + } + }); + frame.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + super.componentResized(e); + canvas.setSize(frame.getSize()); + state.onResize(frame.getWidth(), frame.getHeight()); + } + }); + canvas = new Canvas(); + canvas.setSize(frame.getSize()); + frame.add(canvas); + + changeState(new MainMenuState(canvas.getWidth(), canvas.getHeight())); + } + + public void run() { + long time, timeDelta = minTickTime; + lastTick = System.currentTimeMillis(); + + while (frame.isVisible()) { + time = System.currentTimeMillis(); + timeDelta = time - lastTick - minTickTime; + + if (timeDelta >= 0) { + state.tick(); + state.render(canvas.getGraphics()); + lastTick = time; + } + } + } + + public void changeState(State newState) { + if (state != null) { + state.leave(); + } + state = newState; + state.enter(); + state.onResize(canvas.getWidth(), canvas.getHeight()); + } +} diff --git a/src/main/java/de/siphalor/was/assets/AssetsManager.java b/src/main/java/de/siphalor/was/assets/AssetsManager.java new file mode 100644 index 0000000..5ebcb3a --- /dev/null +++ b/src/main/java/de/siphalor/was/assets/AssetsManager.java @@ -0,0 +1,41 @@ +package de.siphalor.was.assets; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public class AssetsManager { + private static final Map imageCache = new HashMap<>(); + + public static final Image MISSINGNO = getImage("assets/missingno.png"); + + @Nullable + public static InputStream getResource(@NotNull String path) { + return Thread.currentThread().getContextClassLoader().getResourceAsStream(path); + } + + public static Image getImage(@NotNull String path) { + Image image = imageCache.get(path); + if (image == null) { + InputStream inputStream = getResource(path); + if (inputStream != null) { + try { + image = ImageIO.read(inputStream); + } catch (IOException e) { + e.printStackTrace(); + image = MISSINGNO; + } + } else { + image = MISSINGNO; + } + imageCache.put(path, image); + } + return image; + } +} diff --git a/src/main/java/de/siphalor/was/content/ContentManager.java b/src/main/java/de/siphalor/was/content/ContentManager.java new file mode 100644 index 0000000..41b16f1 --- /dev/null +++ b/src/main/java/de/siphalor/was/content/ContentManager.java @@ -0,0 +1,77 @@ +package de.siphalor.was.content; + +import de.siphalor.was.content.pack.ContentPack; +import de.siphalor.was.content.resource.Resource; +import org.jetbrains.annotations.NotNull; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +public class ContentManager { + private final List packs = new LinkedList<>(); + + public ContentManager() { + } + + public void clear() { + packs.clear(); + } + + public void addPack(ContentPack pack) { + packs.add(pack); + } + + public Collection getPacks() { + return packs; + } + + @NotNull + public Stream getResources(@NotNull String location, @NotNull String type) { + return packs.stream().flatMap(pack -> pack.getResources(location, type)).distinct(); + } + + @NotNull + public Optional getResource(@NotNull String location) { + Resource resource; + for (ContentPack pack : packs) { + resource = pack.getResource(location); + if (resource != null) { + return Optional.of(resource); + } + } + return Optional.empty(); + } + + @NotNull + public Stream getAllOfResource(@NotNull String location) { + return packs.stream().flatMap(pack -> Stream.ofNullable(pack.getResource(location))); + } + + @NotNull + public Optional getImage(@NotNull String location) { + return getResource(location).map(resource -> { + InputStream inputStream = resource.getInputStream(); + if (inputStream != null) { + try { + return ImageIO.read(inputStream); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return null; + }); + } +} diff --git a/src/main/java/de/siphalor/was/content/lang/I18n.java b/src/main/java/de/siphalor/was/content/lang/I18n.java new file mode 100644 index 0000000..f2a6994 --- /dev/null +++ b/src/main/java/de/siphalor/was/content/lang/I18n.java @@ -0,0 +1,50 @@ +package de.siphalor.was.content.lang; + +import de.siphalor.was.content.ContentManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class I18n { + public static final String DEFAULT = "en_us"; + private static final Lang DEFAULT_LANG = new Lang(DEFAULT); + + @Nullable + private static Lang lang; + + public static void setLang(@NotNull String code, @NotNull ContentManager contentManager) { + if (lang == null || !lang.getCode().equals(code)) { + lang = new Lang(code); + lang.load(contentManager); + } + } + + public static void reload(@NotNull ContentManager contentManager) { + DEFAULT_LANG.load(contentManager); + if (lang != null) { + lang.load(contentManager); + } + } + + @NotNull + public static String get(@NotNull String key) { + String val; + if (lang != null) { + val = lang.get(key); + if (val == null) { + val = DEFAULT_LANG.get(key); + } + } else { + val = DEFAULT_LANG.get(key); + } + + if (val == null) { + return key; + } + return val; + } + + @NotNull + public static String format(@NotNull String key, @Nullable Object... args) { + return String.format(get(key), args); + } +} diff --git a/src/main/java/de/siphalor/was/content/lang/Lang.java b/src/main/java/de/siphalor/was/content/lang/Lang.java new file mode 100644 index 0000000..9231694 --- /dev/null +++ b/src/main/java/de/siphalor/was/content/lang/Lang.java @@ -0,0 +1,53 @@ +package de.siphalor.was.content.lang; + +import de.siphalor.was.content.ContentManager; +import de.siphalor.was.content.resource.Resource; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; +import java.util.Properties; + +public class Lang { + private final String code; + private final Properties properties = new Properties(); + + public Lang(@NotNull String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public void load(@NotNull ContentManager contentManager) { + properties.clear(); + contentManager.getAllOfResource("lang/" + code + ".lang").forEachOrdered(resource -> { + InputStream inputStream = resource.getInputStream(); + if (inputStream != null) { + try { + properties.load(inputStream); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + }); + + if (properties.isEmpty()) { + System.out.println("Failed to load lang file for " + code); + } + } + + @Nullable + public String get(@NotNull String key) { + return (String) properties.get(key); + } +} diff --git a/src/main/java/de/siphalor/was/content/pack/ContentPack.java b/src/main/java/de/siphalor/was/content/pack/ContentPack.java new file mode 100644 index 0000000..5678b01 --- /dev/null +++ b/src/main/java/de/siphalor/was/content/pack/ContentPack.java @@ -0,0 +1,16 @@ +package de.siphalor.was.content.pack; + +import de.siphalor.was.content.resource.Resource; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Stream; + +public interface ContentPack { + @NotNull + Stream getResources(@NotNull String location, @NotNull String type); + @Nullable + Resource getResource(@NotNull String location); + @NotNull + String getId(); +} diff --git a/src/main/java/de/siphalor/was/content/pack/FileContentPack.java b/src/main/java/de/siphalor/was/content/pack/FileContentPack.java new file mode 100644 index 0000000..eabf4f4 --- /dev/null +++ b/src/main/java/de/siphalor/was/content/pack/FileContentPack.java @@ -0,0 +1,53 @@ +package de.siphalor.was.content.pack; + +import de.siphalor.was.content.resource.FileResource; +import de.siphalor.was.content.resource.Resource; +import de.siphalor.was.util.Util; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +public class FileContentPack implements ContentPack { + private final String id; + private final Path base; + + public FileContentPack(@NotNull String id, @NotNull Path base) { + this.id = id; + this.base = base; + } + + @Override + public @NotNull Stream getResources(@NotNull String location, @NotNull String type) { + final String extension = "." + type; + Path dir = base.resolve(Path.of(location)); + if (dir.toFile().isDirectory()) { + try { + return Files.find(dir, Integer.MAX_VALUE, (fPath, fileAttributes) -> fileAttributes.isRegularFile() && fPath.endsWith(extension)).map(path -> + new FileResource(Util.pathToId(id, path.relativize(base).toString()), path.toFile()) + ); + } catch (IOException e) { + e.printStackTrace(); + } + } + return Stream.empty(); + } + + @Override + public Resource getResource(@NotNull String location) { + File file = base.resolve(Path.of(location)).toFile(); + if (file.isFile()) { + return new FileResource(Util.pathToId(id, location), file); + } + return null; + } + + @Override + @NotNull + public String getId() { + return id; + } +} diff --git a/src/main/java/de/siphalor/was/content/pack/JarContentPack.java b/src/main/java/de/siphalor/was/content/pack/JarContentPack.java new file mode 100644 index 0000000..0202edf --- /dev/null +++ b/src/main/java/de/siphalor/was/content/pack/JarContentPack.java @@ -0,0 +1,109 @@ +package de.siphalor.was.content.pack; + +import de.siphalor.was.content.resource.FileResource; +import de.siphalor.was.content.resource.CallbackResource; +import de.siphalor.was.content.resource.Resource; +import de.siphalor.was.util.Util; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Stream; + +public class JarContentPack implements ContentPack { + private final String id; + private final String baseLocation; + private final ClassLoader classLoader; + + public JarContentPack(String id) { + this(id, ""); + } + + public JarContentPack(String id, String baseLocation) { + this(id, baseLocation, Thread.currentThread().getContextClassLoader()); + } + + public JarContentPack(String id, String baseLocation, ClassLoader classLoader) { + this.id = id; + this.baseLocation = baseLocation; + this.classLoader = classLoader; + } + + // Inspired from this: https://stackoverflow.com/a/48190582/7582022 + @Override + @NotNull + public Stream getResources(@NotNull String location, @NotNull String type) { + final String extension = "." + type; + try { + URL url = classLoader.getResource(baseLocation + "/" + location); + + if (url != null) { + if ("file".equals(url.getProtocol())) { + Path basePath = Path.of(url.toURI()); + return Files.find(basePath, Integer.MAX_VALUE, (path, attributes) -> attributes.isRegularFile() && path.getFileName().getName(0).toString().endsWith(extension)).map(path -> + new FileResource(Util.pathToId(id, basePath.relativize(path).toString()), path.toFile()) + ); + } else if ("jar".equals(url.getProtocol())) { + // The url path section begins with "file:" and ends with an exclamation mark and the path inside the jar + // URLs also encode symbols with dollar sign so we need to decode them + String urlPath = url.getPath(); + String jarPath = URLDecoder.decode(urlPath.substring(5, urlPath.indexOf('!')), StandardCharsets.UTF_8); + + JarFile jarFile = new JarFile(jarPath); + Enumeration entries = jarFile.entries(); + + final String absLocation = baseLocation + "/" + location + "/"; + final int absLength = absLocation.length(); + + ArrayList resources = new ArrayList<>(); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.startsWith(absLocation) && name.length() != absLocation.length()) { + resources.add(new CallbackResource(Util.pathToId(id, name.substring(absLength)), () -> classLoader.getResourceAsStream(name))); + } + } + return resources.stream(); + } + } + } catch (IOException | URISyntaxException e) { + e.printStackTrace(); + } + return Stream.empty(); + } + + @Override + @Nullable + public Resource getResource(@NotNull String location) { + try { + URL url = classLoader.getResource(baseLocation + "/" + location); + if (url != null) { + if ("file".equals(url.getProtocol())) { + return new FileResource(Util.pathToId(id, location), new File(url.toURI())); + } else if ("jar".equals(url.getProtocol())) { + return new CallbackResource(Util.pathToId(id, location), () -> classLoader.getResourceAsStream(baseLocation + "/" + location)); + } + } + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return null; + } + + @NotNull + @Override + public String getId() { + return id; + } +} diff --git a/src/main/java/de/siphalor/was/content/product/Product.java b/src/main/java/de/siphalor/was/content/product/Product.java new file mode 100644 index 0000000..cb1bbe8 --- /dev/null +++ b/src/main/java/de/siphalor/was/content/product/Product.java @@ -0,0 +1,14 @@ +package de.siphalor.was.content.product; + +import org.jetbrains.annotations.NotNull; + +public interface Product { + @NotNull + String getPropertySpecifier(); + + int getDepth(); + boolean testY(int y); + + @NotNull + ProductType getType(); +} diff --git a/src/main/java/de/siphalor/was/content/product/ProductManager.java b/src/main/java/de/siphalor/was/content/product/ProductManager.java new file mode 100644 index 0000000..d220922 --- /dev/null +++ b/src/main/java/de/siphalor/was/content/product/ProductManager.java @@ -0,0 +1,40 @@ +package de.siphalor.was.content.product; + +import de.siphalor.was.content.ContentManager; +import de.siphalor.was.util.ResourceManager; +import de.siphalor.was.content.product.dynamic.DynamicProductType; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public class ProductManager implements ResourceManager> { + private final Map> productTypes = new HashMap<>(); + + @Override + public void clear() { + productTypes.clear(); + } + + @Override + public void reload(@NotNull ContentManager contentManager) { + contentManager.getResources("products", "properties").forEach(resource -> { + InputStream inputStream = resource.getInputStream(); + if (inputStream != null) { + productTypes.put(resource.getId(), DynamicProductType.from(inputStream)); + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public ProductType get(String id) { + return productTypes.get(id); + } +} diff --git a/src/main/java/de/siphalor/was/content/product/ProductType.java b/src/main/java/de/siphalor/was/content/product/ProductType.java new file mode 100644 index 0000000..c51915f --- /dev/null +++ b/src/main/java/de/siphalor/was/content/product/ProductType.java @@ -0,0 +1,12 @@ +package de.siphalor.was.content.product; + +import org.jetbrains.annotations.Nullable; + +public abstract class ProductType { + + public ProductType() { + } + + @Nullable + public abstract T getProduct(String[] values); +} diff --git a/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProduct.java b/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProduct.java new file mode 100644 index 0000000..026cd0b --- /dev/null +++ b/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProduct.java @@ -0,0 +1,65 @@ +package de.siphalor.was.content.product.dynamic; + +import de.siphalor.was.content.product.Product; +import de.siphalor.was.content.product.ProductType; +import de.siphalor.was.util.Util; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.function.IntPredicate; + +public class DynamicProduct implements Product { + private ProductType type; + private final String[] properties; + private int depth = 1; + @NotNull + private IntPredicate yPredicate = Util.dummyIntPredicate(); + + public DynamicProduct(String[] properties) { + this.properties = properties; + } + + @Override + @NotNull + public String getPropertySpecifier() { + return String.join("_", properties); + } + + public void setDepth(int depth) { + this.depth = depth; + } + + @Override + public int getDepth() { + return depth; + } + + @Override + public boolean testY(int y) { + return yPredicate.test(y); + } + + // This function exists to resolve the circular references of ProductType <-> Product on creation + void setType(ProductType type) { + this.type = type; + } + + @Override + @NotNull + public ProductType getType() { + return type; + } + + public void setYPredicate(@NotNull IntPredicate yPredicate) { + this.yPredicate = yPredicate; + } + + @Override + public String toString() { + return "DynamicProduct{" + + "properties=" + Arrays.toString(properties) + + ", depth=" + depth + + ", yPredicate=" + yPredicate + + '}'; + } +} diff --git a/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProductType.java b/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProductType.java new file mode 100644 index 0000000..e752e9e --- /dev/null +++ b/src/main/java/de/siphalor/was/content/product/dynamic/DynamicProductType.java @@ -0,0 +1,154 @@ +package de.siphalor.was.content.product.dynamic; + +import de.siphalor.was.content.product.ProductType; +import de.siphalor.was.util.Util; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.function.IntPredicate; +import java.util.stream.Collectors; + +public class DynamicProductType extends ProductType { + private final Map variations; + private final String[] properties; + + public DynamicProductType(List variations, String[] properties) { + this.variations = new HashMap<>(); + variations.forEach(p -> { + p.setType(this); + this.variations.put(p.getPropertySpecifier(), p); + }); + this.properties = properties; + } + + @NotNull + public static DynamicProductType from(@NotNull InputStream inputStream) { + Properties propertiesFile = new Properties(); + + try { + propertiesFile.load(inputStream); + + String[] propNames = ((String) propertiesFile.get("properties")).split(","); + + if (propNames.length > 0) { + // propNameList = propNames without blank elements & all elements trimmed + List propNameList = new ArrayList<>(propNames.length); + List> properties = new ArrayList<>(propNames.length); + for (String propName : propNames) { + propName = propName.trim(); + if (propName.isEmpty()) { + continue; + } + propNameList.add(propName); + if (propertiesFile.containsKey(propName + ".variants")) { + String[] variants = ((String) propertiesFile.get(propName + ".variants")).split(","); + List propertyVariants = new ArrayList<>(variants.length); + for (String variant : variants) { + variant = variant.trim(); + propertyVariants.add(makePrototype(propertiesFile, propName, variant)); + } + properties.add(propertyVariants); + } else { + // if there are no variants assume true/false flag + properties.add(List.of( + makePrototype(propertiesFile, propName, "true"), + makePrototype(propertiesFile, propName, "false") + )); + } + } + + List prototypes = properties.stream().reduce((pts1, pts2) -> { + return pts1.stream().flatMap(pt -> { + return pts2.stream().map(pt::combine); + }).collect(Collectors.toList()); + }).get(); + + List products = prototypes.stream().map(pt -> { + DynamicProduct product = new DynamicProduct(pt.value.split(";")); + pt.apply(product); + return product; + }).collect(Collectors.toList()); + + return new DynamicProductType(products, propNameList.toArray(String[]::new)); + } + } catch (IOException | NullPointerException e) { + e.printStackTrace(); + } + + return new DynamicProductType(List.of(new DynamicProduct(Util.emptyArray())), Util.emptyArray()); + } + + private static ProductPrototype makePrototype(Properties propertiesFile, String property, String variant) { + ProductPrototype prototype = new ProductPrototype(variant); + String base = property + "." + variant + "."; + if (propertiesFile.containsKey(base + "depth")) { + prototype.depth = Integer.parseInt((String) propertiesFile.get(base + "depth")); + } + if (propertiesFile.containsKey(base + "y")) { + String val = (String) propertiesFile.get(base + "y"); + switch (val.charAt(0)) { + case '>': { + int i = Integer.parseInt(val.substring(1)); + prototype.yPredicate = value -> value > i; + break; + } + case '<': { + int i = Integer.parseInt(val.substring(1)); + prototype.yPredicate = value -> value < i; + break; + } + default: + int i = Integer.parseInt(val); + prototype.yPredicate = value -> value == i; + break; + } + } + return prototype; + } + + @Override + public DynamicProduct getProduct(String[] values) { + return variations.get(String.join("_", values)); + } + + private static class ProductPrototype { + String value; + + Integer depth = null; + IntPredicate yPredicate = null; + + public ProductPrototype(String value) { + this.value = value; + } + + public void apply(DynamicProduct product) { + if (depth != null) + product.setDepth(depth); + if (yPredicate != null) + product.setYPredicate(yPredicate); + } + + public ProductPrototype combine(ProductPrototype prototype) { + ProductPrototype result = new ProductPrototype(value + ";" + prototype.value); + if (prototype.depth != null) + result.depth = prototype.depth; + else + result.depth = depth; + if (result.yPredicate != null) + result.yPredicate = prototype.yPredicate; + else + result.yPredicate = yPredicate; + return result; + } + } + + @Override + public String toString() { + return "DynamicProductType{" + + "properties=" + String.join(" & ", properties) + + ", variations=\n" + variations.values().stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator())) + + '}'; + } +} diff --git a/src/main/java/de/siphalor/was/content/quest/Quest.java b/src/main/java/de/siphalor/was/content/quest/Quest.java new file mode 100644 index 0000000..00ac82f --- /dev/null +++ b/src/main/java/de/siphalor/was/content/quest/Quest.java @@ -0,0 +1,36 @@ +package de.siphalor.was.content.quest; + +import de.siphalor.was.content.product.Product; +import org.jetbrains.annotations.NotNull; + +public class Quest { + @NotNull + private final Type type; + private final int reward; + @NotNull + private final Product product; + + public Quest(@NotNull Type type, int reward, @NotNull Product product) { + this.product = product; + this.reward = reward; + this.type = type; + } + + @NotNull + public Product getProduct() { + return product; + } + + @NotNull + public Type getType() { + return type; + } + + public int getReward() { + return reward; + } + + enum Type { + IN, OUT + } +} diff --git a/src/main/java/de/siphalor/was/content/quest/QuestGenerator.java b/src/main/java/de/siphalor/was/content/quest/QuestGenerator.java new file mode 100644 index 0000000..6e5300e --- /dev/null +++ b/src/main/java/de/siphalor/was/content/quest/QuestGenerator.java @@ -0,0 +1,29 @@ +package de.siphalor.was.content.quest; + +public interface QuestGenerator { + /** + * Restarts this generator + */ + void restart(); + + /** + * Returns the next {@link Quest} but doesn't advance + * @return the next {@link Quest} + * @see QuestGenerator#next() + */ + Quest peek(); + + /** + * Returns the next {@link Quest} and advances + * @return the next {@link Quest} + * @see QuestGenerator#peek() + */ + Quest next(); + + /** + * Returns whether another {@link Quest} can be generated + * @return whether the generator is at its end + * @see QuestGenerator#restart() + */ + boolean hasNext(); +} diff --git a/src/main/java/de/siphalor/was/content/quest/QuestManager.java b/src/main/java/de/siphalor/was/content/quest/QuestManager.java new file mode 100644 index 0000000..55c92d2 --- /dev/null +++ b/src/main/java/de/siphalor/was/content/quest/QuestManager.java @@ -0,0 +1,39 @@ +package de.siphalor.was.content.quest; + +import de.siphalor.was.WhatAStorage; +import de.siphalor.was.content.ContentManager; +import de.siphalor.was.util.ResourceManager; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public class QuestManager implements ResourceManager { + private final Map questGenerators = new HashMap<>(); + + @Override + public void clear() { + questGenerators.clear(); + } + + public void reload(@NotNull ContentManager contentManager) { + contentManager.getResources("quests", "csv").forEach(resource -> { + InputStream inputStream = resource.getInputStream(); + if (inputStream != null) { + questGenerators.put(resource.getId(), StaticQuestGenerator.fromCsv(inputStream, WhatAStorage.getInstance().getProductManager())); + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public QuestGenerator get(String id) { + return questGenerators.get(id); + } +} diff --git a/src/main/java/de/siphalor/was/content/quest/StaticQuestGenerator.java b/src/main/java/de/siphalor/was/content/quest/StaticQuestGenerator.java new file mode 100644 index 0000000..481ebae --- /dev/null +++ b/src/main/java/de/siphalor/was/content/quest/StaticQuestGenerator.java @@ -0,0 +1,89 @@ +package de.siphalor.was.content.quest; + +import de.siphalor.was.content.product.Product; +import de.siphalor.was.content.product.ProductManager; +import de.siphalor.was.content.product.ProductType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +public class StaticQuestGenerator implements QuestGenerator { + @NotNull + private final List quests; + private int index = 0; + + public StaticQuestGenerator(@NotNull List quests) { + this.quests = quests; + } + + @Nullable + public static StaticQuestGenerator fromCsv(@NotNull InputStream inputStream, @NotNull ProductManager productManager) { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String line; + + List quests = new ArrayList<>(); + + try { + while ((line = reader.readLine()) != null) { + try { + String[] parts = line.split(","); + if (parts.length >= 3) { + Quest.Type type = Quest.Type.valueOf(parts[0].toUpperCase(Locale.ENGLISH)); + ProductType productType = productManager.get(parts[1]); + if (productType != null) { + int reward = Integer.parseInt(parts[2]); + + Product product = productType.getProduct(Arrays.copyOfRange(parts, 3, parts.length)); + + if (product != null) { + quests.add(new Quest(type, reward, product)); + } else { + System.out.println("Invalid product in quest: " + line); + } + } else { + System.out.println("Invalid product in quest: " + parts[2]); + } + } else { + System.out.println("Invalid line in quests file: " + line); + } + } catch (Exception e) { + System.out.println("Failed to load quest in csv file in line: " + line); + e.printStackTrace(); + } + } + return new StaticQuestGenerator(quests); + } catch (IOException e) { + System.out.println("Failed to load quest csv file:"); + e.printStackTrace(); + } + return null; + } + + @Override + public void restart() { + index = 0; + } + + @Override + public Quest peek() { + return quests.get(index); + } + + @Override + public Quest next() { + return quests.get(index++); + } + + @Override + public boolean hasNext() { + return index < quests.size(); + } +} diff --git a/src/main/java/de/siphalor/was/content/resource/CallbackResource.java b/src/main/java/de/siphalor/was/content/resource/CallbackResource.java new file mode 100644 index 0000000..ea92eb9 --- /dev/null +++ b/src/main/java/de/siphalor/was/content/resource/CallbackResource.java @@ -0,0 +1,20 @@ +package de.siphalor.was.content.resource; + +import org.jetbrains.annotations.Nullable; + +import java.io.InputStream; +import java.util.function.Supplier; + +public class CallbackResource extends Resource { + Supplier supplier; + + public CallbackResource(String id, Supplier supplier) { + super(id); + this.supplier = supplier; + } + + @Override + public @Nullable InputStream getInputStream() { + return supplier.get(); + } +} diff --git a/src/main/java/de/siphalor/was/content/resource/FileResource.java b/src/main/java/de/siphalor/was/content/resource/FileResource.java new file mode 100644 index 0000000..d65d431 --- /dev/null +++ b/src/main/java/de/siphalor/was/content/resource/FileResource.java @@ -0,0 +1,25 @@ +package de.siphalor.was.content.resource; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +public class FileResource extends Resource { + private final File file; + + public FileResource(String id, File file) { + super(id); + this.file = file; + } + + @Override + public InputStream getInputStream() { + try { + return new FileInputStream(file); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/de/siphalor/was/content/resource/Resource.java b/src/main/java/de/siphalor/was/content/resource/Resource.java new file mode 100644 index 0000000..fd44db1 --- /dev/null +++ b/src/main/java/de/siphalor/was/content/resource/Resource.java @@ -0,0 +1,20 @@ +package de.siphalor.was.content.resource; + +import org.jetbrains.annotations.Nullable; + +import java.io.InputStream; + +public abstract class Resource { + private final String id; + + protected Resource(String id) { + this.id = id; + } + + @Nullable + public abstract InputStream getInputStream(); + + public String getId() { + return id; + } +} diff --git a/src/main/java/de/siphalor/was/game/Storage.java b/src/main/java/de/siphalor/was/game/Storage.java new file mode 100644 index 0000000..b0b2a49 --- /dev/null +++ b/src/main/java/de/siphalor/was/game/Storage.java @@ -0,0 +1,5 @@ +package de.siphalor.was.game; + +public class Storage { + +} diff --git a/src/main/java/de/siphalor/was/state/GameState.java b/src/main/java/de/siphalor/was/state/GameState.java new file mode 100644 index 0000000..17f027f --- /dev/null +++ b/src/main/java/de/siphalor/was/state/GameState.java @@ -0,0 +1,35 @@ +package de.siphalor.was.state; + +import de.siphalor.was.assets.AssetsManager; + +import java.awt.*; +import java.awt.image.BufferedImage; + +public class GameState extends State { + private BufferedImage main; + + public GameState(int width, int height) { + super(width, height); + } + + @Override + public void tick() { + + } + + @Override + public void render(Graphics graphics) { + graphics.drawImage(main, 0, 0, getWidth(), getHeight(), (img, infoflags, x, y, width, height) -> false); + } + + @Override + public void onResize(int width, int height) { + super.onResize(width, height); + main = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + redrawBg(main.getGraphics()); + } + + public void redrawBg(Graphics graphics) { + graphics.drawImage(AssetsManager.getImage("assets/menu/bg.png"), 0, 0, getWidth(), getHeight(), (img, infoflags, x, y, width, height) -> false); + } +} diff --git a/src/main/java/de/siphalor/was/state/MainMenuState.java b/src/main/java/de/siphalor/was/state/MainMenuState.java new file mode 100644 index 0000000..812c132 --- /dev/null +++ b/src/main/java/de/siphalor/was/state/MainMenuState.java @@ -0,0 +1,31 @@ +package de.siphalor.was.state; + +import de.siphalor.was.assets.AssetsManager; + +import java.awt.*; + +public class MainMenuState extends State { + private boolean bgDirty = true; + + public MainMenuState(int width, int height) { + super(width, height); + } + + @Override + public void tick() { + + } + + @Override + public void render(Graphics graphics) { + if (bgDirty) { + graphics.drawImage(AssetsManager.getImage("assets/bg.png"), 0, 0, getWidth() - 1, getHeight() - 1, (img, infoflags, x, y, width, height) -> false); + } + } + + @Override + public void onResize(int width, int height) { + super.onResize(width, height); + bgDirty = true; + } +} diff --git a/src/main/java/de/siphalor/was/state/State.java b/src/main/java/de/siphalor/was/state/State.java new file mode 100644 index 0000000..757dd94 --- /dev/null +++ b/src/main/java/de/siphalor/was/state/State.java @@ -0,0 +1,37 @@ +package de.siphalor.was.state; + +import java.awt.*; + +public abstract class State { + private int width, height; + + public State(int width, int height) { + this.width = width; + this.height = height; + onResize(width, height); + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public void enter() { + + } + + public void leave() { + + } + + public abstract void tick(); + public abstract void render(Graphics graphics); + + public void onResize(int width, int height) { + this.width = width; + this.height = height; + } +} diff --git a/src/main/java/de/siphalor/was/util/Pair.java b/src/main/java/de/siphalor/was/util/Pair.java new file mode 100644 index 0000000..79caa38 --- /dev/null +++ b/src/main/java/de/siphalor/was/util/Pair.java @@ -0,0 +1,39 @@ +package de.siphalor.was.util; + +import java.util.Objects; + +public class Pair { + private final A first; + private final B second; + + public static Pair of(A first, B second) { + return new Pair<>(first, second); + } + + public Pair(A first, B second) { + this.first = first; + this.second = second; + } + + public A getFirst() { + return first; + } + + public B getSecond() { + return second; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Pair pair = (Pair) o; + return Objects.equals(first, pair.first) && + Objects.equals(second, pair.second); + } + + @Override + public int hashCode() { + return Objects.hash(first, second); + } +} diff --git a/src/main/java/de/siphalor/was/util/ResourceManager.java b/src/main/java/de/siphalor/was/util/ResourceManager.java new file mode 100644 index 0000000..6102b78 --- /dev/null +++ b/src/main/java/de/siphalor/was/util/ResourceManager.java @@ -0,0 +1,11 @@ +package de.siphalor.was.util; + +import de.siphalor.was.content.ContentManager; +import org.jetbrains.annotations.NotNull; + +public interface ResourceManager { + void clear(); + void reload(@NotNull ContentManager contentManager); + + T get(String id); +} diff --git a/src/main/java/de/siphalor/was/util/Util.java b/src/main/java/de/siphalor/was/util/Util.java new file mode 100644 index 0000000..ae97899 --- /dev/null +++ b/src/main/java/de/siphalor/was/util/Util.java @@ -0,0 +1,37 @@ +package de.siphalor.was.util; + +import java.util.function.IntPredicate; +import java.util.function.Predicate; + +public class Util { + private static final Predicate DUMMY_PREDICATE = o -> true; + private static final IntPredicate DUMMY_INT_PREDICATE = value -> true; + + private static final Object[] EMPTY_ARRAY = new Object[0]; + + @SuppressWarnings("unchecked") + public static Predicate dummyPredicate() { + return (Predicate) DUMMY_PREDICATE; + } + + public static IntPredicate dummyIntPredicate() { + return DUMMY_INT_PREDICATE; + } + + @SuppressWarnings("unchecked") + public static T[] emptyArray() { + return (T[]) EMPTY_ARRAY; + } + + public static String pathToId(String packId, String path) { + int dot = path.lastIndexOf('.'); + if (dot >= 0) { + path = path.substring(0, dot); + } + path = path.replace('/', '.').replace('\\', '.'); + if (packId.isEmpty()) { + return path; + } + return packId + "." + path; + } +} diff --git a/src/main/resources/assets/missingno.png b/src/main/resources/assets/missingno.png new file mode 100644 index 0000000..db22893 Binary files /dev/null and b/src/main/resources/assets/missingno.png differ diff --git a/src/main/resources/content/lang/en_us.lang b/src/main/resources/content/lang/en_us.lang new file mode 100644 index 0000000..9b07f7c --- /dev/null +++ b/src/main/resources/content/lang/en_us.lang @@ -0,0 +1,34 @@ +test.hello-world = Hello World! + +quests.normal = Normal + +products.paper = Paper +products.paper.color = Color +products.paper.color.green = Green +products.paper.color.blue = Blue +products.paper.color.white = White +# These paper formats are not equivalent but at least kind of similar in the amount they're used +products.paper.format = Format +products.paper.format.a3 = Ledger +products.paper.format.a4 = Legal +products.paper.format.a5 = Letter + +products.wood = Wood +products.wood.type = Type +products.wood.type.beech = Beech +products.wood.type.oak = Oak +products.wood.type.pine = Pine +products.wood.form = Form +products.wood.form.pieces = Pieces +products.wood.form.boards = Boards +products.wood.form.beams = Beams + +products.stone = Stone +products.stone.type = Type +products.stone.type.granite = Granite +products.stone.type.marble = Marble +products.stone.type.sandstone = Sandstone +products.stone.weight = Weight +products.stone.weight.light = Light +products.stone.weight.medium = Medium +products.stone.weight.heavy = Heavy diff --git a/src/main/resources/content/products/paper.properties b/src/main/resources/content/products/paper.properties new file mode 100644 index 0000000..e7b09ce --- /dev/null +++ b/src/main/resources/content/products/paper.properties @@ -0,0 +1,3 @@ +properties = color, format +color.variants =white, green, blue +format.variants = a3, a4, a5 diff --git a/src/main/resources/content/products/stone.properties b/src/main/resources/content/products/stone.properties new file mode 100644 index 0000000..eb36b14 --- /dev/null +++ b/src/main/resources/content/products/stone.properties @@ -0,0 +1,4 @@ +properties = type, weight +type.variants = marble, granite, sandstone +weight.variants = light, medium, heavy +weight.heavy.y = 1 diff --git a/src/main/resources/content/products/wood.properties b/src/main/resources/content/products/wood.properties new file mode 100644 index 0000000..b240a85 --- /dev/null +++ b/src/main/resources/content/products/wood.properties @@ -0,0 +1,4 @@ +properties = type, form +type.variants = pine, beech, oak +form.variants = boards, pieces, beams +form.beams.depth = 3 diff --git a/src/main/resources/content/quests/normal.csv b/src/main/resources/content/quests/normal.csv new file mode 100644 index 0000000..6caaaad --- /dev/null +++ b/src/main/resources/content/quests/normal.csv @@ -0,0 +1,47 @@ +in,paper,200,white,a4 +in,paper,300,blue,a5 +in,wood,200,pine,boards +in,wood,500,beech,beams +in,wood,200,oak,pieces +in,paper,200,blue,a5 +in,paper,200,blue,a5 +in,stone,400,marble,medium +in,stone,500,granite,heavy +in,stone,200,sandstone,light +out,paper,1000,blue,a5 +out,wood,1200,oak,pieces +out,stone,1000,marble,medium +out,paper,1500,white,a5 +in,wood,400,oak,beams +in,wood,600,oak,pieces +in,wood,200,beech,pieces +in,stone,400,granite,light +in,paper,200,blue,a3 +in,paper,200,blue,a5 +in,wood,600,oak,pieces +in,wood,600,beech,beams +in,stone,200,sandstone,heavy +in,stone,600,granite,heavy +in,wood,400,beech,boards +in,wood,200,beech,pieces +out,wood,1000,beech,pieces +out,paper,1200,blue,a5 +out,stone,1500,granite,heavy +out,wood,1000,beech,beams +out,stone,1300,sandstone,heavy +in,stone,400,granite,heavy +in,stone,600,marble,medium +in,stone,400,granite,light +in,stone,400,granite,light +in,paper,400,white,a4 +in,stone,400,granite,light +in,wood,600,beech,boards +in,wood,600,pine,boards +in,stone,400,sandstone,light +out,paper,1000,white,a4 +out,stone,1200,marble,medium +out,wood,1100,beech,boards +out,paper,1500,white,a4 +out,wood,1000,pine,boards +out,stone,1200,sandstone,light +out,wood,1100,pine,boards diff --git a/src/test/java/Tests.java b/src/test/java/Tests.java new file mode 100644 index 0000000..ec12c15 --- /dev/null +++ b/src/test/java/Tests.java @@ -0,0 +1,2 @@ +public class Tests { +}