Initial commit (basic backend stuff)

This commit is contained in:
2020-06-10 10:12:39 +02:00
commit 4a38dbfbdc
43 changed files with 1712 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
# gradle specific directories
.gradle/
build/
# IDE specific stuff
.idea/

39
build.gradle Normal file
View File

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

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-6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

183
gradlew vendored Normal file
View File

@@ -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" "$@"

100
gradlew.bat vendored Normal file
View File

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

View File

@@ -0,0 +1 @@
test.hello-world = Hallo Welt!

2
settings.gradle Normal file
View File

@@ -0,0 +1,2 @@
rootProject.name = 'what-a-storage'

View File

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

View File

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

View File

@@ -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<String, Image> 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;
}
}

View File

@@ -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<ContentPack> packs = new LinkedList<>();
public ContentManager() {
}
public void clear() {
packs.clear();
}
public void addPack(ContentPack pack) {
packs.add(pack);
}
public Collection<ContentPack> getPacks() {
return packs;
}
@NotNull
public Stream<Resource> getResources(@NotNull String location, @NotNull String type) {
return packs.stream().flatMap(pack -> pack.getResources(location, type)).distinct();
}
@NotNull
public Optional<Resource> 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<Resource> getAllOfResource(@NotNull String location) {
return packs.stream().flatMap(pack -> Stream.ofNullable(pack.getResource(location)));
}
@NotNull
public Optional<Image> 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;
});
}
}

View File

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

View File

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

View File

@@ -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<Resource> getResources(@NotNull String location, @NotNull String type);
@Nullable
Resource getResource(@NotNull String location);
@NotNull
String getId();
}

View File

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

View File

@@ -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<Resource> 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<JarEntry> entries = jarFile.entries();
final String absLocation = baseLocation + "/" + location + "/";
final int absLength = absLocation.length();
ArrayList<Resource> 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;
}
}

View File

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

View File

@@ -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<ProductType<?>> {
private final Map<String, ProductType<?>> 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);
}
}

View File

@@ -0,0 +1,12 @@
package de.siphalor.was.content.product;
import org.jetbrains.annotations.Nullable;
public abstract class ProductType<T extends Product> {
public ProductType() {
}
@Nullable
public abstract T getProduct(String[] values);
}

View File

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

View File

@@ -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<DynamicProduct> {
private final Map<String, DynamicProduct> variations;
private final String[] properties;
public DynamicProductType(List<DynamicProduct> 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<String> propNameList = new ArrayList<>(propNames.length);
List<List<ProductPrototype>> 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<ProductPrototype> 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<ProductPrototype> prototypes = properties.stream().reduce((pts1, pts2) -> {
return pts1.stream().flatMap(pt -> {
return pts2.stream().map(pt::combine);
}).collect(Collectors.toList());
}).get();
List<DynamicProduct> 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())) +
'}';
}
}

View File

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

View File

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

View File

@@ -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<QuestGenerator> {
private final Map<String, QuestGenerator> 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);
}
}

View File

@@ -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<Quest> quests;
private int index = 0;
public StaticQuestGenerator(@NotNull List<Quest> 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<Quest> 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();
}
}

View File

@@ -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<InputStream> supplier;
public CallbackResource(String id, Supplier<InputStream> supplier) {
super(id);
this.supplier = supplier;
}
@Override
public @Nullable InputStream getInputStream() {
return supplier.get();
}
}

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
package de.siphalor.was.game;
public class Storage {
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,39 @@
package de.siphalor.was.util;
import java.util.Objects;
public class Pair<A, B> {
private final A first;
private final B second;
public static <A, B> Pair<A, B> 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);
}
}

View File

@@ -0,0 +1,11 @@
package de.siphalor.was.util;
import de.siphalor.was.content.ContentManager;
import org.jetbrains.annotations.NotNull;
public interface ResourceManager<T> {
void clear();
void reload(@NotNull ContentManager contentManager);
T get(String id);
}

View File

@@ -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 <T> Predicate<T> dummyPredicate() {
return (Predicate<T>) DUMMY_PREDICATE;
}
public static IntPredicate dummyIntPredicate() {
return DUMMY_INT_PREDICATE;
}
@SuppressWarnings("unchecked")
public static <T> 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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

View File

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

View File

@@ -0,0 +1,3 @@
properties = color, format
color.variants =white, green, blue
format.variants = a3, a4, a5

View File

@@ -0,0 +1,4 @@
properties = type, weight
type.variants = marble, granite, sandstone
weight.variants = light, medium, heavy
weight.heavy.y = 1

View File

@@ -0,0 +1,4 @@
properties = type, form
type.variants = pine, beech, oak
form.variants = boards, pieces, beams
form.beams.depth = 3

View File

@@ -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
1 in paper 200 white a4
2 in paper 300 blue a5
3 in wood 200 pine boards
4 in wood 500 beech beams
5 in wood 200 oak pieces
6 in paper 200 blue a5
7 in paper 200 blue a5
8 in stone 400 marble medium
9 in stone 500 granite heavy
10 in stone 200 sandstone light
11 out paper 1000 blue a5
12 out wood 1200 oak pieces
13 out stone 1000 marble medium
14 out paper 1500 white a5
15 in wood 400 oak beams
16 in wood 600 oak pieces
17 in wood 200 beech pieces
18 in stone 400 granite light
19 in paper 200 blue a3
20 in paper 200 blue a5
21 in wood 600 oak pieces
22 in wood 600 beech beams
23 in stone 200 sandstone heavy
24 in stone 600 granite heavy
25 in wood 400 beech boards
26 in wood 200 beech pieces
27 out wood 1000 beech pieces
28 out paper 1200 blue a5
29 out stone 1500 granite heavy
30 out wood 1000 beech beams
31 out stone 1300 sandstone heavy
32 in stone 400 granite heavy
33 in stone 600 marble medium
34 in stone 400 granite light
35 in stone 400 granite light
36 in paper 400 white a4
37 in stone 400 granite light
38 in wood 600 beech boards
39 in wood 600 pine boards
40 in stone 400 sandstone light
41 out paper 1000 white a4
42 out stone 1200 marble medium
43 out wood 1100 beech boards
44 out paper 1500 white a4
45 out wood 1000 pine boards
46 out stone 1200 sandstone light
47 out wood 1100 pine boards

2
src/test/java/Tests.java Normal file
View File

@@ -0,0 +1,2 @@
public class Tests {
}