Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: asset path resolution #91

Merged
merged 3 commits into from
Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ jlink {
launcher {
name = 'app'
}
forceMerge('log4j-api')
}

jlinkZip {
Expand Down
3 changes: 2 additions & 1 deletion configurations/demo-config-1/root.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
5 changes: 4 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
}
```

Expand Down
1 change: 1 addition & 0 deletions src/main/java/io/rpg/config/ConfigLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ Result<GameWorldConfig, Exception> loadRootFile() {

// GameWorldConfig is loaded in two stages right now
// todo: fix this! Separate initial GameWorldConfig to different class
gameWorldConfigShell.injectConfigDirPath(configDirPath);
Result<Void, Exception> configLoadResult = gameWorldConfigShell.validateStageOne();

if (configLoadResult.isErr()) {
Expand Down
137 changes: 108 additions & 29 deletions src/main/java/io/rpg/config/model/GameWorldConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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 <b>configuration directory</b>.
*/
@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.
Expand Down Expand Up @@ -128,42 +145,75 @@ public void addLocationConfig(LocationConfig locationConfig) {
*/
public Result<Void, Exception> 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()));
Expand Down Expand Up @@ -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<String> 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();
}
}
}