diff --git a/build.gradle b/build.gradle index 2fb76f5c..56424dd5 100644 --- a/build.gradle +++ b/build.gradle @@ -85,6 +85,7 @@ jlink { launcher { name = 'app' } + forceMerge('log4j-api') } jlinkZip { diff --git a/configurations/demo-config-1/root.json b/configurations/demo-config-1/root.json index 76897ee7..1a683df0 100644 --- a/configurations/demo-config-1/root.json +++ b/configurations/demo-config-1/root.json @@ -21,5 +21,6 @@ "quizPopupBackground": "assets/popup-background-3.png", "inventoryPopupBackground": "assets/popup-background.png", "dialoguePopupBackground": "assets/popup-background.png", - "npcFrame": "assets/npc-frame.png" + "npcFrame": "assets/npc-frame.png", + "assetDir": "../../assets" } diff --git a/configurations/unit-test-configurations/test-config-1-full/root.json b/configurations/unit-test-configurations/test-config-1-full/root.json index 08d6748e..2881caf5 100644 --- a/configurations/unit-test-configurations/test-config-1-full/root.json +++ b/configurations/unit-test-configurations/test-config-1-full/root.json @@ -19,5 +19,6 @@ "quizPopupBackground": "assets/popup-background-3.png", "inventoryPopupBackground": "assets/popup-background.png", "dialoguePopupBackground": "assets/popup-background.png", - "npcFrame": "assets/npc-frame.png" + "npcFrame": "assets/npc-frame.png", + "assetDir": "../../../assets" } diff --git a/docs/configuration.md b/docs/configuration.md index 0031a1ed..5649a3f9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -38,6 +38,8 @@ following props: * `player` - Player object specification. It follows the `GameObject` specification rules. See [game object configuration][#game-object-configuration] * `rootLocation` - `tag` of first-to-be-displayed location. +* `assetDirPath` - path to the directory with all the assets used in the configuration. Must be either absolute + or relative to **configuration directory**. Aliases: `asset-dir`, `asset-dir-path`, `assetDir`. * `textPopupBackground` - Url of a picture to be used as a background for all TextPopups * `textPopupButton` - Url of a picture to be used as a button image for all TextPopups * `textImagePopupBackground` - Url of a picture to be used as a background for all TextImagePopups @@ -70,7 +72,8 @@ e.g. "quizPopupBackground": "assets/popup-background-3.png", "inventoryPopupBackground": "assets/popup-background.png", "dialoguePopupBackground": "assets/popup-background.png", - "npcFrame": "assets/npc-frame.png" + "npcFrame": "assets/npc-frame.png", + "assetDir": "../../assets" } ``` diff --git a/src/main/java/io/rpg/config/ConfigLoader.java b/src/main/java/io/rpg/config/ConfigLoader.java index f116525e..0414edd1 100644 --- a/src/main/java/io/rpg/config/ConfigLoader.java +++ b/src/main/java/io/rpg/config/ConfigLoader.java @@ -280,6 +280,7 @@ Result loadRootFile() { // GameWorldConfig is loaded in two stages right now // todo: fix this! Separate initial GameWorldConfig to different class + gameWorldConfigShell.injectConfigDirPath(configDirPath); Result configLoadResult = gameWorldConfigShell.validateStageOne(); if (configLoadResult.isErr()) { diff --git a/src/main/java/io/rpg/config/model/GameWorldConfig.java b/src/main/java/io/rpg/config/model/GameWorldConfig.java index 95dcf331..a896a58b 100644 --- a/src/main/java/io/rpg/config/model/GameWorldConfig.java +++ b/src/main/java/io/rpg/config/model/GameWorldConfig.java @@ -6,9 +6,7 @@ import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.Set; +import java.util.*; /** * This class in not meant to be instantiated by hand. It is used by {@link io.rpg.config.ConfigLoader} @@ -66,6 +64,25 @@ private GameWorldConfig() { private String dialoguePopupBackground; private String npcFrame; + /** + * Describes path to directory with all the assets. + * + * Must be either absolute or relative to configuration directory. + */ + @SerializedName(value = "assetDirPath", alternate = {"asset-dir", "asset-dir-path", "assetDir"}) + private String assetDir; + private transient Path assetDirPath; + + /** + * Ugly as ... But we need it to be able to resolve the paths. + * Better solution might be moving path validation to new class or just to ConfigLoader. + */ + private transient Path configDirPath; + + public void injectConfigDirPath(Path path) { + configDirPath = path; + } + /** * Unique tag for the game. This can be treated as name of the game. @@ -128,42 +145,75 @@ public void addLocationConfig(LocationConfig locationConfig) { */ public Result validateStageOne() { ErrorMessageBuilder builder = new ErrorMessageBuilder(); - if (locationTags.size() < 1) { - builder.append("No location tags detected"); + if (locationTags != null && locationTags.size() < 1) { + builder.append("No location tags detected"); } if (tag == null) { builder.append("Null tag"); } + if (assetDir == null || assetDir.isBlank()) { + builder.append("No asset dir path specified"); + } else if (!Files.isDirectory(Path.of(assetDir))) { + // the path is either invalid or relative to configuration directory + // let's see whether it is relative first + assetDirPath = configDirPath.resolve(assetDir); + if (!Files.isDirectory(assetDirPath)) { + builder.append("Invalid asset directory specified. Neither " + assetDir + " nor " + assetDirPath + " " + + "point to valid directory"); + } else { + assetDir = assetDirPath.toString(); + } + } else { + assetDirPath = Path.of(assetDir); + } + if (playerConfig == null) { builder.append("No player config provided"); } + if (rootLocation == null) { builder.append("No root location set!"); } - if (quizPopupBackground == null || !Files.isRegularFile(Path.of(quizPopupBackground))) { - builder.append("Invalid quiz popup background specified"); - } - if (textImagePopupBackground == null || !Files.isRegularFile(Path.of(textImagePopupBackground))) { - builder.append("Invalid text image popup background specified"); - } - if (textPopupButton == null || !Files.isRegularFile(Path.of(textPopupButton))) { - builder.append("Invalid text popup button specified"); - } - if (textImagePopupButton == null || !Files.isRegularFile(Path.of(textImagePopupButton))) { - builder.append("Invald text image popup button specified"); - } - if (textPopupBackground == null || !Files.isRegularFile(Path.of(textPopupBackground))) { - builder.append("Invalid text popup background specified"); - } - if (inventoryPopupBackground == null || !Files.isRegularFile(Path.of(inventoryPopupBackground))) { - builder.append("Invalid inventory popup background specified"); - } - if (dialoguePopupBackground == null || !Files.isRegularFile(Path.of(dialoguePopupBackground))) { - builder.append("Invalid dialogue popup background specified"); - } - if (npcFrame == null || !Files.isRegularFile(Path.of(npcFrame))) { - builder.append("Invalid NPC Frame specified"); - } + + resolvePathToAsset(assetDirPath, quizPopupBackground).ifPresentOrElse( + pathStr -> { quizPopupBackground = pathStr; }, + () -> builder.append("Invalid quiz popup background specified") + ); + + resolvePathToAsset(assetDirPath, textImagePopupBackground).ifPresentOrElse( + pathStr -> { textImagePopupBackground = pathStr; }, + () -> builder.append("Invalid text image popup background specified") + ); + + resolvePathToAsset(assetDirPath, textPopupButton).ifPresentOrElse( + pathStr -> { textPopupButton = pathStr; }, + () -> builder.append("Invalid text popup button specified") + ); + + resolvePathToAsset(assetDirPath, textImagePopupButton).ifPresentOrElse( + pathStr -> { textImagePopupButton = pathStr; }, + () -> builder.append("Invalid text image popup button specified") + ); + + resolvePathToAsset(assetDirPath, textPopupBackground).ifPresentOrElse( + pathStr -> { textPopupBackground = pathStr; }, + () -> builder.append("Invalid text popup background specified") + ); + + resolvePathToAsset(assetDirPath, inventoryPopupBackground).ifPresentOrElse( + pathStr -> { inventoryPopupBackground = pathStr; }, + () -> builder.append("Invalid inventory popup background specified") + ); + + resolvePathToAsset(assetDirPath, dialoguePopupBackground).ifPresentOrElse( + pathStr -> { dialoguePopupBackground = pathStr; }, + () -> builder.append("Invalid dialogue popup background specified") + ); + + resolvePathToAsset(assetDirPath, npcFrame).ifPresentOrElse( + pathStr -> { npcFrame = pathStr; }, + () -> builder.append("Invalid NPC Frame specified") + ); return builder.isEmpty() ? Result.ok() : Result.err(new IllegalStateException(builder.toString())); @@ -224,4 +274,33 @@ public String getNpcFrame() { public static String resolvePathFormat(String path) { return "file:" + path; } + + /** + * Resolves path to the asset. + * + * Path to the asset must be either relative to the configuration directory + * or absolute. + * + * @param pathStr path to the asset + * @return optional with path to the asset if resolution succeeded, empty optional else + */ + private Optional resolvePathToAsset(Path root, String pathStr) { + if (pathStr == null) { + return Optional.empty(); + } + + Path assetPath = Path.of(pathStr); + + if (Files.isRegularFile(assetPath)) { + return Optional.of(assetPath.toString()); + } + + assetPath = root.resolve(pathStr); + + if (Files.isRegularFile(assetPath)) { + return Optional.of(assetPath.toString()); + } else { + return Optional.empty(); + } + } }