diff --git a/.github/workflows/gradle-build.yml b/.github/workflows/gradle-build.yml index 432724c4..c42a78f0 100644 --- a/.github/workflows/gradle-build.yml +++ b/.github/workflows/gradle-build.yml @@ -2,13 +2,13 @@ name: Build & test project on: push: - branches: [ master ] + branches: [ master, development ] paths: - src/** - build.gradle - settings.gradle pull_request: - branches: [ master ] + branches: [ master, development ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/assets/chest.png b/assets/chest.png new file mode 100644 index 00000000..4b462c0b Binary files /dev/null and b/assets/chest.png differ diff --git a/assets/diamond.png b/assets/diamond.png new file mode 100644 index 00000000..56707a15 Binary files /dev/null and b/assets/diamond.png differ diff --git a/assets/key.png b/assets/key.png new file mode 100644 index 00000000..dfef6abc Binary files /dev/null and b/assets/key.png differ diff --git a/assets/knight.png b/assets/knight.png new file mode 100644 index 00000000..4cd31275 Binary files /dev/null and b/assets/knight.png differ diff --git a/assets/map.png b/assets/map.png index 4df2990a..fbad1027 100644 Binary files a/assets/map.png and b/assets/map.png differ diff --git a/assets/map10x10.png b/assets/map10x10.png index de67ef4a..e576b07b 100644 Binary files a/assets/map10x10.png and b/assets/map10x10.png differ diff --git a/assets/zombie.png b/assets/zombie.png new file mode 100644 index 00000000..4f8bd20f Binary files /dev/null and b/assets/zombie.png differ diff --git a/configurations/config-1/locations/location-1/location-1.json b/configurations/config-1/locations/location-1/location-1.json index 5e9bfa04..94025965 100644 --- a/configurations/config-1/locations/location-1/location-1.json +++ b/configurations/config-1/locations/location-1/location-1.json @@ -1,8 +1,8 @@ { "tag": "location-1", "objects": [ - { "tag": "object-1", "position": { "row": 0, "col": 5 }, "type": "collectible" }, - { "tag": "object-2", "position": { "row": 1, "col": 3 }, "type": "interactive" } + { "tag": "object-1", "position": { "row": 0, "col": 5 }, "type": "collectible", "assetPath": "assets/someDude.png" }, + { "tag": "object-2", "position": { "row": 1, "col": 3 }, "type": "dialog", "assetPath": "assets/someDude.png" } ], - "backgroundPath": "file:assets/map.png" + "backgroundPath": "assets/map.png" } diff --git a/configurations/config-1/locations/location-1/objects/object-1.json b/configurations/config-1/locations/location-1/objects/object-1.json index 6f2ed3ae..785ee639 100644 --- a/configurations/config-1/locations/location-1/objects/object-1.json +++ b/configurations/config-1/locations/location-1/objects/object-1.json @@ -1,5 +1,9 @@ { "tag": "object-1", - "assetPath": "/path/to/the/asset", - "type": "" -} \ No newline at end of file + "assetPath": "assets/someDude.png", + "type": "collectible", + "position": { + "row": 10, + "col": 10 + } +} diff --git a/configurations/config-1/locations/location-1/objects/object-2.json b/configurations/config-1/locations/location-1/objects/object-2.json index e69de29b..fcfc9d28 100644 --- a/configurations/config-1/locations/location-1/objects/object-2.json +++ b/configurations/config-1/locations/location-1/objects/object-2.json @@ -0,0 +1,6 @@ +{ + "tag": "object-2", + "assetPath": "assets/stone.png", + "type": "navigable", + "position": { "row": 11, "col": 3 } +} diff --git a/configurations/config-1/locations/location-2/location-2.json b/configurations/config-1/locations/location-2/location-2.json index dc949d25..2d5eae12 100644 --- a/configurations/config-1/locations/location-2/location-2.json +++ b/configurations/config-1/locations/location-2/location-2.json @@ -1,8 +1,8 @@ { "tag": "location-2", "objects": [ - { "tag": "object-1", "position": { "row": 0, "col": 0 }, "type": "collectible" }, - { "tag": "object-3", "position": { "row": 0, "col": 1 }, "type": "navigable" } + { "tag": "object-1", "position": { "row": 0, "col": 5 }, "type": "collectible", "assetPath": "assets/someDude.png" }, + { "tag": "object-2", "position": { "row": 1, "col": 3 }, "type": "dialog", "assetPath": "assets/someDude.png" } ], - "backgroundPath": "file:assets/map.png" -} \ No newline at end of file + "backgroundPath": "assets/map.png" +} diff --git a/configurations/config-1/locations/location-2/objects/object-1.json b/configurations/config-1/locations/location-2/objects/object-1.json index 6f2ed3ae..6ce8ac86 100644 --- a/configurations/config-1/locations/location-2/objects/object-1.json +++ b/configurations/config-1/locations/location-2/objects/object-1.json @@ -1,5 +1,9 @@ { "tag": "object-1", - "assetPath": "/path/to/the/asset", - "type": "" -} \ No newline at end of file + "assetPath": "assets/stone.png", + "type": "dialog", + "position": { + "row": 1, + "col": 15 + } +} diff --git a/configurations/config-1/locations/location-2/objects/object-3.json b/configurations/config-1/locations/location-2/objects/object-3.json index 791023ce..5e11e7f7 100644 --- a/configurations/config-1/locations/location-2/objects/object-3.json +++ b/configurations/config-1/locations/location-2/objects/object-3.json @@ -1,5 +1,9 @@ { "tag": "object-3", - "assetPath": "/path/to/the/asset", - "type": "" -} \ No newline at end of file + "assetPath": "assets/someDudeButGreen.png", + "type": "collectible", + "position": { + "row": 7, + "col": 8 + } +} diff --git a/configurations/config-1/root.json b/configurations/config-1/root.json index 2f174211..1c2aad47 100644 --- a/configurations/config-1/root.json +++ b/configurations/config-1/root.json @@ -4,5 +4,12 @@ "location-1", "location-2" ], - "rootLocation": "location-1" -} \ No newline at end of file + "rootLocation": "location-1", + "player": { + "tag": "player", + "position": { "row": 4, "col": 5 }, + "type": "player", + "assetPath": "assets/stone.png", + "location": "location-1" + } +} diff --git a/configurations/demo-config-1/locations/location-1/location-1.json b/configurations/demo-config-1/locations/location-1/location-1.json new file mode 100644 index 00000000..5337de5d --- /dev/null +++ b/configurations/demo-config-1/locations/location-1/location-1.json @@ -0,0 +1,11 @@ +{ + "tag": "location-1", + "objects": [ + { "tag": "object-1", "position": { "row": 1, "col": 8 }, "type": "collectible", "assetPath": "assets/diamond.png" }, + { "tag": "object-2", "position": { "row": 6, "col": 3 }, "type": "collectible", "assetPath": "assets/key.png" }, + { "tag": "object-3", "position": { "row": 1, "col": 1 }, "type": "dialog", "assetPath": "assets/zombie.png" }, + { "tag": "object-4", "position": { "row": 3, "col": 2 }, "type": "dialog", "assetPath": "assets/zombie.png" }, + { "tag": "object-5", "position": { "row": 8, "col": 8 }, "type": "dialog", "assetPath": "assets/chest.png" } + ], + "backgroundPath": "assets/map.png" +} diff --git a/configurations/demo-config-1/root.json b/configurations/demo-config-1/root.json new file mode 100644 index 00000000..a1a906e8 --- /dev/null +++ b/configurations/demo-config-1/root.json @@ -0,0 +1,14 @@ +{ + "tag": "GameByConfig1", + "locationTags": [ + "location-1" + ], + "rootLocation": "location-1", + "player": { + "tag": "player", + "position": { "row": 4, "col": 5 }, + "type": "player", + "assetPath": "assets/knight.png", + "location": "location-1" + } +} diff --git a/configurations/unit-test-configuration/locations/location-1/objects/object-1.json b/configurations/unit-test-configuration/locations/location-1/objects/object-1.json deleted file mode 100644 index 6f2ed3ae..00000000 --- a/configurations/unit-test-configuration/locations/location-1/objects/object-1.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tag": "object-1", - "assetPath": "/path/to/the/asset", - "type": "" -} \ No newline at end of file diff --git a/configurations/unit-test-configuration/locations/location-2/location-2.json b/configurations/unit-test-configuration/locations/location-2/location-2.json deleted file mode 100644 index dc949d25..00000000 --- a/configurations/unit-test-configuration/locations/location-2/location-2.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tag": "location-2", - "objects": [ - { "tag": "object-1", "position": { "row": 0, "col": 0 }, "type": "collectible" }, - { "tag": "object-3", "position": { "row": 0, "col": 1 }, "type": "navigable" } - ], - "backgroundPath": "file:assets/map.png" -} \ No newline at end of file diff --git a/configurations/unit-test-configuration/locations/location-2/objects/object-1.json b/configurations/unit-test-configuration/locations/location-2/objects/object-1.json deleted file mode 100644 index 6f2ed3ae..00000000 --- a/configurations/unit-test-configuration/locations/location-2/objects/object-1.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tag": "object-1", - "assetPath": "/path/to/the/asset", - "type": "" -} \ No newline at end of file diff --git a/configurations/unit-test-configuration/locations/location-2/objects/object-3.json b/configurations/unit-test-configuration/locations/location-2/objects/object-3.json deleted file mode 100644 index 791023ce..00000000 --- a/configurations/unit-test-configuration/locations/location-2/objects/object-3.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tag": "object-3", - "assetPath": "/path/to/the/asset", - "type": "" -} \ No newline at end of file diff --git a/configurations/unit-test-configuration/root.json b/configurations/unit-test-configuration/root.json deleted file mode 100644 index ff5b72e3..00000000 --- a/configurations/unit-test-configuration/root.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tag": "test-tag", - "locationTags": [ - "location-1", - "location-2" - ], - "rootLocation": "location-1" -} \ No newline at end of file diff --git a/configurations/unit-test-configuration/locations/location-1/location-1.json b/configurations/unit-test-configurations/test-config-1-full/locations/location-1/location-1.json similarity index 54% rename from configurations/unit-test-configuration/locations/location-1/location-1.json rename to configurations/unit-test-configurations/test-config-1-full/locations/location-1/location-1.json index 5e9bfa04..94025965 100644 --- a/configurations/unit-test-configuration/locations/location-1/location-1.json +++ b/configurations/unit-test-configurations/test-config-1-full/locations/location-1/location-1.json @@ -1,8 +1,8 @@ { "tag": "location-1", "objects": [ - { "tag": "object-1", "position": { "row": 0, "col": 5 }, "type": "collectible" }, - { "tag": "object-2", "position": { "row": 1, "col": 3 }, "type": "interactive" } + { "tag": "object-1", "position": { "row": 0, "col": 5 }, "type": "collectible", "assetPath": "assets/someDude.png" }, + { "tag": "object-2", "position": { "row": 1, "col": 3 }, "type": "dialog", "assetPath": "assets/someDude.png" } ], - "backgroundPath": "file:assets/map.png" + "backgroundPath": "assets/map.png" } diff --git a/configurations/unit-test-configurations/test-config-1-full/locations/location-1/objects/object-1.json b/configurations/unit-test-configurations/test-config-1-full/locations/location-1/objects/object-1.json new file mode 100644 index 00000000..785ee639 --- /dev/null +++ b/configurations/unit-test-configurations/test-config-1-full/locations/location-1/objects/object-1.json @@ -0,0 +1,9 @@ +{ + "tag": "object-1", + "assetPath": "assets/someDude.png", + "type": "collectible", + "position": { + "row": 10, + "col": 10 + } +} diff --git a/configurations/unit-test-configurations/test-config-1-full/locations/location-1/objects/object-2.json b/configurations/unit-test-configurations/test-config-1-full/locations/location-1/objects/object-2.json new file mode 100644 index 00000000..fcfc9d28 --- /dev/null +++ b/configurations/unit-test-configurations/test-config-1-full/locations/location-1/objects/object-2.json @@ -0,0 +1,6 @@ +{ + "tag": "object-2", + "assetPath": "assets/stone.png", + "type": "navigable", + "position": { "row": 11, "col": 3 } +} diff --git a/configurations/unit-test-configurations/test-config-1-full/locations/location-2/location-2.json b/configurations/unit-test-configurations/test-config-1-full/locations/location-2/location-2.json new file mode 100644 index 00000000..2d5eae12 --- /dev/null +++ b/configurations/unit-test-configurations/test-config-1-full/locations/location-2/location-2.json @@ -0,0 +1,8 @@ +{ + "tag": "location-2", + "objects": [ + { "tag": "object-1", "position": { "row": 0, "col": 5 }, "type": "collectible", "assetPath": "assets/someDude.png" }, + { "tag": "object-2", "position": { "row": 1, "col": 3 }, "type": "dialog", "assetPath": "assets/someDude.png" } + ], + "backgroundPath": "assets/map.png" +} diff --git a/configurations/unit-test-configurations/test-config-1-full/locations/location-2/objects/object-1.json b/configurations/unit-test-configurations/test-config-1-full/locations/location-2/objects/object-1.json new file mode 100644 index 00000000..6ce8ac86 --- /dev/null +++ b/configurations/unit-test-configurations/test-config-1-full/locations/location-2/objects/object-1.json @@ -0,0 +1,9 @@ +{ + "tag": "object-1", + "assetPath": "assets/stone.png", + "type": "dialog", + "position": { + "row": 1, + "col": 15 + } +} diff --git a/configurations/unit-test-configurations/test-config-1-full/locations/location-2/objects/object-3.json b/configurations/unit-test-configurations/test-config-1-full/locations/location-2/objects/object-3.json new file mode 100644 index 00000000..5e11e7f7 --- /dev/null +++ b/configurations/unit-test-configurations/test-config-1-full/locations/location-2/objects/object-3.json @@ -0,0 +1,9 @@ +{ + "tag": "object-3", + "assetPath": "assets/someDudeButGreen.png", + "type": "collectible", + "position": { + "row": 7, + "col": 8 + } +} diff --git a/configurations/unit-test-configurations/test-config-1-full/root.json b/configurations/unit-test-configurations/test-config-1-full/root.json new file mode 100644 index 00000000..96eb28b0 --- /dev/null +++ b/configurations/unit-test-configurations/test-config-1-full/root.json @@ -0,0 +1,15 @@ +{ + "tag": "test-config-1-full", + "locationTags": [ + "location-1", + "location-2" + ], + "rootLocation": "location-1", + "player": { + "tag": "player", + "position": { "row": 4, "col": 5 }, + "type": "player", + "assetPath": "assets/stone.png", + "location": "location-1" + } +} diff --git a/configurations/unit-test-configuration/locations/location-1/objects/object-2.json b/configurations/unit-test-configurations/test-config-2-empty/.gitkeep similarity index 100% rename from configurations/unit-test-configuration/locations/location-1/objects/object-2.json rename to configurations/unit-test-configurations/test-config-2-empty/.gitkeep diff --git a/configurations/unit-test-configurations/test-config-3-minimal-struct/root.json b/configurations/unit-test-configurations/test-config-3-minimal-struct/root.json new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/io/rpg/Game.java b/src/main/java/io/rpg/Game.java index ecd9f532..14f5dd67 100644 --- a/src/main/java/io/rpg/Game.java +++ b/src/main/java/io/rpg/Game.java @@ -41,4 +41,8 @@ public Builder setController(Controller controller) { return this; } } + + public Controller getController() { + return controller; + } } diff --git a/src/main/java/io/rpg/Initializer.java b/src/main/java/io/rpg/Initializer.java index 965c2ea9..2886a802 100644 --- a/src/main/java/io/rpg/Initializer.java +++ b/src/main/java/io/rpg/Initializer.java @@ -1,13 +1,18 @@ package io.rpg; import io.rpg.config.ConfigLoader; +import io.rpg.config.model.PlayerConfig; import io.rpg.controller.Controller; import io.rpg.config.model.GameWorldConfig; import io.rpg.config.model.LocationConfig; import io.rpg.model.location.LocationModel; import io.rpg.model.object.GameObject; import io.rpg.config.model.GameObjectConfig; +import io.rpg.model.object.Player; +import io.rpg.util.GameObjectFactory; +import io.rpg.util.GameObjectViewFactory; import io.rpg.util.Result; +import io.rpg.view.GameObjectView; import io.rpg.view.LocationView; import javafx.stage.Stage; import org.apache.logging.log4j.LogManager; @@ -17,14 +22,13 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.LinkedHashMap; -import java.util.LinkedList; +import java.util.Iterator; import java.util.List; public class Initializer { private Path pathToConfigDir; private ConfigLoader configLoader; - private Stage mainStage; + private final Stage mainStage; private final Logger logger; @@ -48,20 +52,33 @@ public Result initialize() { return Result.error(new RuntimeException("ConfigLoader fetched null GameWorldConfig")); } - GameWorldConfig worldConfig = gameWorldConfigLoadResult.getOkValue(); + GameWorldConfig gameWorldConfig = gameWorldConfigLoadResult.getOkValue(); Controller.Builder controllerBuilder = new Controller.Builder(); - assert worldConfig.getLocationConfigs() != null; - assert worldConfig.getLocationConfigs().size() > 0 : "There must be at least one location config specified"; + assert gameWorldConfig.getLocationConfigs() != null; + assert gameWorldConfig.getLocationConfigs().size() > 0 : "There must be at least one location config specified"; + + for (LocationConfig locationConfig : gameWorldConfig.getLocationConfigs()) { + + List gameObjects = loadGameObjectsForLocation(locationConfig); + List gameObjectViews = loadGameObjectViewsForLocation(locationConfig); + + registerGameObjectViewsToModel(gameObjects, gameObjectViews); + + LocationModel model = new LocationModel.Builder() + .setTag(locationConfig.getTag()) + .setGameObjects(gameObjects).build(); - for (LocationConfig locationConfig : worldConfig.getLocationConfigs()) { - LocationModel model = loadLocationModelFromConfig(locationConfig); LocationView view = loadLocationViewFromConfig(locationConfig); assert view != null; - if (locationConfig.getTag().equals(worldConfig.getRootLocation())) { + gameObjectViews.forEach(view_ -> { + view.getViewModel().addChild(view_); + }); + + if (locationConfig.getTag().equals(gameWorldConfig.getRootLocation())) { controllerBuilder .setModel(model) .setView(view); @@ -71,15 +88,56 @@ public Result initialize() { controllerBuilder .addViewForTag(locationConfig.getTag(), view) - .addModelForTag(locationConfig.getTag(), model); + .addModelForTag(locationConfig.getTag(), model) + .registerToViews(gameObjectViews); + + view.createViewsForObjects(model); } + // Player is created separately + // TODO: consider moving it to separate method + Player player = (Player) GameObjectFactory.fromConfig(gameWorldConfig.getPlayerConfig()); + GameObjectView playerView = GameObjectViewFactory.fromConfig(gameWorldConfig.getPlayerConfig()); + player.addGameObjectStateChangeObserver(playerView); + controllerBuilder.setPlayer(player); + player.setGameObjectView(playerView); + + Controller controller = controllerBuilder.build(); + // TODO: this is a temporary solution + controller.setPlayerView(playerView); + Game.Builder gameBuilder = new Game.Builder(); - gameBuilder.setController(controllerBuilder.build()); + gameBuilder.setController(controller); return Result.ok(gameBuilder.build()); } + public static List loadGameObjectsForLocation(LocationConfig config) { + return GameObjectFactory.fromConfigList(config.getObjects()); + } + + public static List loadGameObjectViewsForLocation(LocationConfig config) { + return GameObjectViewFactory.fromConfigList(config.getObjects()); + } + + public static void registerGameObjectViewsToModel(List gameObjects, + List gameObjectViews) { + assert gameObjects.size() == gameObjectViews.size() : "Arrays must be of the same length!"; + + Iterator gameObjectIterator = gameObjects.iterator(); + Iterator gameObjectViewIterator = gameObjectViews.iterator(); + + // we asserted earlier that both lists have the same length thus we don't + // need to check .hasNext() for both lists + while (gameObjectIterator.hasNext()) { + GameObject gameObject = gameObjectIterator.next(); + GameObjectView gameObjectView = gameObjectViewIterator.next(); + + // registration + gameObject.addGameObjectStateChangeObserver(gameObjectView); + } + } + @Nullable public static LocationView loadLocationViewFromConfig(LocationConfig config) { try { @@ -89,18 +147,4 @@ public static LocationView loadLocationViewFromConfig(LocationConfig config) { } return null; } - - public static LocationModel loadLocationModelFromConfig(LocationConfig config) { - List gameObjectConfigs = config.getObjects(); - List gameObjects = new LinkedList<>(); - - for (GameObjectConfig goconfig : gameObjectConfigs) { - gameObjects.add(GameObject.fromConfig(goconfig)); - } - - return new LocationModel( - config.getTag(), - gameObjects - ); - } } diff --git a/src/main/java/io/rpg/Main.java b/src/main/java/io/rpg/Main.java index d41e5106..eba5a09e 100644 --- a/src/main/java/io/rpg/Main.java +++ b/src/main/java/io/rpg/Main.java @@ -1,6 +1,8 @@ package io.rpg; +import io.rpg.model.object.Player; import io.rpg.util.Result; +import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.stage.Stage; import org.apache.logging.log4j.Level; @@ -16,7 +18,7 @@ public void start(Stage stage) throws IOException { Configurator.setRootLevel(Level.DEBUG); Logger logger = LogManager.getLogger(Main.class); - Initializer worldInitializer = new Initializer("configurations/config-1", stage); + Initializer worldInitializer = new Initializer("configurations/demo-config-1", stage); Result initializationResult = worldInitializer.initialize(); if (initializationResult.isError()) { @@ -40,6 +42,28 @@ public void start(Stage stage) throws IOException { stage.setScene(game.getWorldView()); stage.show(); + + AnimationTimer animationTimer=new AnimationTimer() { + long lastUpdate=-1; + @Override + public void handle(long now) { + if(lastUpdate!=-1){ + float difference=(now-lastUpdate)/1e6f; + + game.getController().getCurrentModel().update(difference); +// locationModel.update(difference); + Player player=game.getController().getCurrentModel().getPlayer(); + if(player!=null){ +// game.getController().getCurrentModel().getPlayer().render(); + player.render(); + } + } + lastUpdate=now; + } + }; + + animationTimer.start(); + } public static void main(String[] args) { diff --git a/src/main/java/io/rpg/config/ConfigLoader.java b/src/main/java/io/rpg/config/ConfigLoader.java index 2ee4bce1..490c5a62 100644 --- a/src/main/java/io/rpg/config/ConfigLoader.java +++ b/src/main/java/io/rpg/config/ConfigLoader.java @@ -31,22 +31,22 @@ public class ConfigLoader { * root.json file & locations directory */ @NotNull - private final Path pathToConfigDir; + private final Path configDirPath; /** - * This filed is initialized basing on {@link io.rpg.config.ConfigLoader#pathToConfigDir}. + * This filed is initialized basing on {@link io.rpg.config.ConfigLoader#configDirPath}. * It represents path to the root.json file which is entry point for {@link ConfigLoader} */ @NotNull - private final Path pathToRootFile; + private final Path rootFilePath; /** - * Similarly to {@link io.rpg.config.ConfigLoader#pathToRootFile} this field is initialized basing on - * {@link io.rpg.config.ConfigLoader#pathToConfigDir}. + * Similarly to {@link io.rpg.config.ConfigLoader#rootFilePath} this field is initialized basing on + * {@link io.rpg.config.ConfigLoader#configDirPath}. * It represents path to the directory that holds locations configuration files. */ @NotNull - private final Path pathToLocationsDir; + private final Path locationsDirPath; public static final String ERR_INVALID_CFG_DIR_PATH = "Could not resolve config directory." + " Make sure that the config dir path is correct"; @@ -70,17 +70,34 @@ public class ConfigLoader { * Creates {@link ConfigLoader} for configuration under configDirPath. * * @param configDirPath Path to the root directory. + * @throws IllegalArgumentException with appropriate error message when path to config directory + * path is invalid or configuration has invalid structure. + * */ public ConfigLoader(@NotNull String configDirPath) { + this(Path.of(configDirPath)); + } + + /** + * Creates {@link ConfigLoader} for configuration under configDirPath. + * + * @param configDirPath Path to the root directory. + * @throws IllegalArgumentException with appropriate error message when path to config directory + * path is invalid or configuration has invalid structure. + * + */ + public ConfigLoader(@NotNull Path configDirPath) { logger = LogManager.getLogger(ConfigLoader.class); logger.info("Initializing"); - pathToConfigDir = Path.of(configDirPath); - pathToRootFile = pathToConfigDir.resolve(ConfigConstants.ROOT); - pathToLocationsDir = pathToConfigDir.resolve(ConfigConstants.LOCATIONS_DIR); + this.configDirPath = configDirPath; + rootFilePath = this.configDirPath.resolve(ConfigConstants.ROOT); + locationsDirPath = this.configDirPath.resolve(ConfigConstants.LOCATIONS_DIR); gson = new Gson(); + logger.info("root file path: " + rootFilePath); + validate(); } @@ -114,42 +131,71 @@ public Result load() { try { logger.info("Loading location config for tag: " + locationTag); - LocationConfig locationConfig = loadLocationConfig(locationTag); + Result locationConfigLoadingResult = loadLocationConfig(locationTag); - // todo: this should be called in loadLocationConfig method? - locationConfig.validate(); + if (locationConfigLoadingResult.isError()) { + return Result.error(locationConfigLoadingResult.getErrorValue()); + } else if (locationConfigLoadingResult.isOkValueNull()) { + return Result.error(new RuntimeException("Null LocationConfig returned for location with tag: " + locationTag)); + } + + LocationConfig locationConfig = locationConfigLoadingResult.getOkValue(); gameWorldConfig.addLocationConfig(locationConfig); logger.info("Location config loaded for tag: " + locationTag); + // locationConfig can not be null here, we checked this case earlier logger.info(locationConfig.toString()); - assert locationConfig.getPath() != null : "Path to location dir must be set in its loader"; Path objectsDir = locationConfig.getPath().resolve(ConfigConstants.OBJECTS_DIR); - for (GameObjectConfig gameObjectConfig : locationConfig.getObjects()) { - try { - gameObjectConfig.validate(); - } catch (Exception ex) { - String exceptionMessage = ex.getMessage(); - - logger.warn("Validation for game object config with tag: " - + gameObjectConfig.getTag() + " failed." - + (exceptionMessage != null ? "Reason: " + exceptionMessage : "No reason provided")); + // TODO: @kkafar: Load objects from inside objects directory + // TODO: consider extracting this code to method + if (Files.isDirectory(objectsDir)) { // there is directory with additional object configurations + for (GameObjectConfig gameObjectConfig : locationConfig.getObjects()) { + Path gameObjectConfigSpec = objectsDir.resolve(gameObjectConfig.getTag() + ".json"); // TODO + // there is additional spec for this game object + if (Files.isReadable(gameObjectConfigSpec)) { + logger.info("Detected additional spec for object with tag: " + gameObjectConfig.getTag()); + BufferedReader reader = new BufferedReader(new FileReader(gameObjectConfigSpec.toString())); + GameObjectConfig config = gson.fromJson(reader, GameObjectConfig.class); + gameObjectConfig.updateFrom(config); + } } + } - // TODO: @kkafar: Load objects inside objects directory + // game configs validation + // TODO: consider extracting this code to separate method + for (GameObjectConfig gameObjectConfig : locationConfig.getObjects()) { + Result result = gameObjectConfig.validate(); + + if (result.isError()) { + // TODO: consider returning loading error here + result.getErrorValueOpt().ifPresentOrElse(ex -> { + String exceptionMessage = ex.getMessage(); + logger.warn("Validation for game object config with tag: " + + gameObjectConfig.getTag() + " failed." + + (exceptionMessage != null ? "Reason: " + exceptionMessage : "No reason provided")); + }, + () -> { + logger.warn("Validation for game object config with tag: " + + gameObjectConfig.getTag() + " failed with null result."); + }); + } else { + logger.info("Loaded GameObjectConfig for tag: " + gameObjectConfig.getTag() + ":" + gameObjectConfig); + } } + } catch (FileNotFoundException e) { logger.warn("Failed to load location config for tag: " + locationTag); e.printStackTrace(); } } - Result validationResult = gameWorldConfig.validate(); + Result validationResult = gameWorldConfig.validate(); if (validationResult.isError()) { return Result.error(validationResult.getErrorValue()); } @@ -164,7 +210,7 @@ Result loadGameWorldConfig() { BufferedReader reader; try { - reader = new BufferedReader(new FileReader(pathToRootFile.toString())); + reader = new BufferedReader(new FileReader(rootFilePath.toString())); } catch (FileNotFoundException exception) { return Result.error(exception); } @@ -181,37 +227,45 @@ Result loadGameWorldConfig() { return configLoadResult; } - LocationConfig loadLocationConfig(@NotNull String locationTag) throws FileNotFoundException { + Result loadLocationConfig(@NotNull String locationTag) throws FileNotFoundException { logger.info("Loading location: " + locationTag); - Path locationDir = pathToLocationsDir.resolve(locationTag); + Path locationDir = locationsDirPath.resolve(locationTag); if (!Files.isDirectory(locationDir)) { logger.error(ERR_LOCATION_DIR_FNF_FOR_TAG + locationTag); - throw new FileNotFoundException(ERR_LOCATION_DIR_FNF_FOR_TAG + locationTag); + return Result.error(new FileNotFoundException(ERR_LOCATION_DIR_FNF_FOR_TAG + locationTag)); } Path locationConfigJson = locationDir.resolve(locationTag + ".json"); if (!Files.isReadable(locationConfigJson)) { logger.error(ERR_LOCATION_CFG_NR_FOR_TAG + locationTag); - throw new RuntimeException(ERR_LOCATION_DIR_FNF_FOR_TAG + locationTag); + return Result.error(new RuntimeException(ERR_LOCATION_DIR_FNF_FOR_TAG + locationTag)); } BufferedReader reader = new BufferedReader(new FileReader(locationConfigJson.toString())); LocationConfig config = gson.fromJson(reader, LocationConfig.class); - config.setPath(locationDir); - return config; + + Result locationConfigValidationResult = + config.validate(); + + if (locationConfigValidationResult.isError()) { + return locationConfigValidationResult; + } else { + config.setPath(locationDir); + return Result.ok(config); + } } private void validate() { - if (!Files.isDirectory(pathToConfigDir)) { + if (!Files.isDirectory(configDirPath)) { logger.error(ERR_INVALID_CFG_DIR_PATH); throw new IllegalArgumentException(ERR_INVALID_CFG_DIR_PATH); - } else if (!Files.isReadable(pathToRootFile)) { + } else if (!Files.isReadable(rootFilePath)) { logger.error(ERR_ROOT_FNF); throw new IllegalArgumentException(ERR_ROOT_FNF); - } else if (!Files.isDirectory(pathToLocationsDir)) { + } else if (!Files.isDirectory(locationsDirPath)) { logger.error(ERR_LOCATIONS_DIR_FNF); throw new IllegalArgumentException(ERR_LOCATIONS_DIR_FNF); } diff --git a/src/main/java/io/rpg/config/model/GameObjectConfig.java b/src/main/java/io/rpg/config/model/GameObjectConfig.java index 7175de06..6fb2499a 100644 --- a/src/main/java/io/rpg/config/model/GameObjectConfig.java +++ b/src/main/java/io/rpg/config/model/GameObjectConfig.java @@ -6,20 +6,23 @@ import io.rpg.util.Result; import org.jetbrains.annotations.NotNull; +import java.lang.reflect.Field; +import java.util.Optional; + /** * Represents {@link io.rpg.model.object.GameObject} configuration provided by user * in configuration files. */ public class GameObjectConfig extends GameObject { - private String type; public GameObjectConfig(@NotNull String tag, @NotNull Position position) { super(tag, position); } - public String getType() { + public String getTypeString() { + assert type != null : "Attempt to access uninitialized \"type\" field!"; return type; } @@ -28,10 +31,46 @@ public String getType() { * * @return Object in valid state or exception. */ - public Result validate() { + public Result validate() { if (!GameObjects.isValidType(type)) { return Result.error(new IllegalStateException("Invalid object type: " + type)); } return Result.ok(this); } + + public void updateFrom(GameObjectConfig gameObjectConfig) { + if (gameObjectConfig.getPosition() != null) { + this.position = gameObjectConfig.getPosition(); + } + if (gameObjectConfig.getTypeString() != null) { + this.type = gameObjectConfig.getTypeString(); + } + if (gameObjectConfig.getAssetPath() != null) { + this.assetPath = gameObjectConfig.assetPath; + } + } + + public String getFieldDescription() { + StringBuilder builder = new StringBuilder(); + for (Field field : GameObjectConfig.class.getDeclaredFields()) { + try { + Optional fieldValue = Optional.ofNullable(field.get(this)); + fieldValue.ifPresent(_fieldValue -> builder.append('\t') + .append(field.getName()) + .append(": ") + .append(_fieldValue) + .append(",\n") + ); + } catch (IllegalAccessException ignored) { /* noop */ } + } + return builder.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("\n{\n").append(super.getFieldDescription()).append(getFieldDescription()); + return builder.append("}").toString(); + } + } diff --git a/src/main/java/io/rpg/config/model/GameWorldConfig.java b/src/main/java/io/rpg/config/model/GameWorldConfig.java index ede8ed45..7b77611a 100644 --- a/src/main/java/io/rpg/config/model/GameWorldConfig.java +++ b/src/main/java/io/rpg/config/model/GameWorldConfig.java @@ -1,5 +1,6 @@ package io.rpg.config.model; +import com.google.gson.annotations.SerializedName; import io.rpg.util.Result; import java.util.ArrayList; @@ -27,6 +28,12 @@ public class GameWorldConfig { */ private ArrayList locationConfigs; + /** + * Configuration for the player object. + */ + @SerializedName("player") + private PlayerConfig playerConfig; + private GameWorldConfig() { locationTags = new ArrayList<>(); locationConfigs = new ArrayList<>(); @@ -68,6 +75,13 @@ public String getRootLocation() { return rootLocation; } + /** + * @return configuration for the player + */ + public PlayerConfig getPlayerConfig() { + return playerConfig; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -95,6 +109,10 @@ public Result validateStageOne() { return Result.error(new IllegalStateException("No location tags detected")); } else if (tag == null) { return Result.error(new IllegalStateException("Null tag")); + } else if (playerConfig == null) { + return Result.error(new IllegalStateException("No player config provided")); + } else if (rootLocation == null) { + return Result.error(new IllegalStateException("No root location set!")); } else { return Result.ok(this); } @@ -105,15 +123,12 @@ public Result validateStageOne() { * * @return Object with valid state or exception. */ - public Result validate() { - if (locationTags.size() < 1) { - return Result.error(new IllegalStateException("No location tags detected")); + public Result validate() { + Result stageOneValidationResult = validateStageOne(); + if (stageOneValidationResult.isError()) { + return stageOneValidationResult; } else if (locationConfigs.size() < 1) { return Result.error(new IllegalStateException("No location configs loaded")); - } else if (tag == null) { - return Result.error(new IllegalStateException("Null tag")); - } else if (rootLocation == null) { - return Result.error(new IllegalStateException("No root location set!")); } else { return Result.ok(this); } diff --git a/src/main/java/io/rpg/config/model/LocationConfig.java b/src/main/java/io/rpg/config/model/LocationConfig.java index 68e32dda..f8180b2f 100644 --- a/src/main/java/io/rpg/config/model/LocationConfig.java +++ b/src/main/java/io/rpg/config/model/LocationConfig.java @@ -1,6 +1,7 @@ package io.rpg.config.model; +import io.rpg.util.Result; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -51,7 +52,14 @@ public void setPath(@NotNull Path path) { this.path = path; } - public void validate() { - + public Result validate() { + if (tag == null) { + return Result.error(new IllegalStateException("Null tag")); + } else if (backgroundPath == null || backgroundPath.isBlank()) { + // TODO: Validate the backgroundPath here + return Result.error(new IllegalStateException("Empty string as background path")); + } else { + return Result.ok(this); + } } } diff --git a/src/main/java/io/rpg/config/model/PlayerConfig.java b/src/main/java/io/rpg/config/model/PlayerConfig.java new file mode 100644 index 00000000..bd66d072 --- /dev/null +++ b/src/main/java/io/rpg/config/model/PlayerConfig.java @@ -0,0 +1,21 @@ +package io.rpg.config.model; + +import com.google.gson.annotations.SerializedName; +import io.rpg.model.data.Position; +import org.jetbrains.annotations.NotNull; + +/** + * Represents player configuration. + */ +public class PlayerConfig extends GameObjectConfig { + @SerializedName("location") + private String initialLocationTag; + + public PlayerConfig(@NotNull String tag, @NotNull Position position) { + super(tag, position); + } + + public String getInitialLocationTag() { + return initialLocationTag; + } +} diff --git a/src/main/java/io/rpg/controller/Controller.java b/src/main/java/io/rpg/controller/Controller.java index 96200780..2d706202 100644 --- a/src/main/java/io/rpg/controller/Controller.java +++ b/src/main/java/io/rpg/controller/Controller.java @@ -2,7 +2,12 @@ import io.rpg.model.data.KeyboardEvent; import io.rpg.model.data.MouseClickedEvent; +import io.rpg.model.data.Vector; import io.rpg.model.location.LocationModel; +import io.rpg.model.object.CollectibleGameObject; +import io.rpg.model.object.GameObject; +import io.rpg.model.object.InteractiveGameObject; +import io.rpg.model.object.Player; import io.rpg.util.Result; import io.rpg.view.GameObjectView; import io.rpg.view.LocationView; @@ -13,6 +18,7 @@ import org.jetbrains.annotations.NotNull; import java.util.LinkedHashMap; +import java.util.List; public class Controller implements KeyboardEvent.Observer, MouseClickedEvent.Observer { private Scene currentView; @@ -20,6 +26,7 @@ public class Controller implements KeyboardEvent.Observer, MouseClickedEvent.Obs private LocationModel currentModel; private LinkedHashMap tagToLocationViewMap; private Logger logger; + private final PopupController popupController = new PopupController(); public Controller() { @@ -51,6 +58,12 @@ public void setView(Scene currentView) { this.currentView = currentView; } + public void registerToViews(List views) { + for (GameObjectView view : views) { + view.addOnClickedObserver(this); + } + } + public Scene getView() { return currentView; } @@ -90,19 +103,71 @@ else if (currentModel == null) public void onKeyboardEvent(KeyboardEvent event) { // TODO: implement event handling logger.info("Controller notified on key pressed from " + event.source()); + //TODO: call Player::set...Pressed depending on keyCode and whether the key was pressed or released + + KeyEvent payload = event.payload(); + + if (payload.getEventType() == KeyEvent.KEY_PRESSED){ + switch (payload.getCode()) { + case F -> popupController.openPointsPopup(5, getWindowCenterX(), getWindowCenterY()); + case A -> currentModel.getPlayer().setLeftPressed(true); + case D -> currentModel.getPlayer().setRightPressed(true); + case S -> currentModel.getPlayer().setDownPressed(true); + case W -> currentModel.getPlayer().setUpPressed(true); + } + } else if (payload.getEventType() == KeyEvent.KEY_RELEASED) { + switch (payload.getCode()) { + case A -> currentModel.getPlayer().setLeftPressed(false); + case D -> currentModel.getPlayer().setRightPressed(false); + case S -> currentModel.getPlayer().setDownPressed(false); + case W -> currentModel.getPlayer().setUpPressed(false); + } + } + } + + private int getWindowCenterX() { + return (int) (currentView.getWindow().getX() + currentView.getWindow().getWidth() / 2); + } + + private int getWindowCenterY() { + return (int) (currentView.getWindow().getY() + currentView.getWindow().getHeight() / 2); } @Override public void onMouseClickedEvent(MouseClickedEvent event) { - // TODO: implement event handling + int SCALE = 64; + Vector playerPos = currentModel.getPlayer().getPixelPosition(); + GameObjectView objectView = event.source(); + GameObject object = currentModel.getObject((int) objectView.getY() / SCALE, (int) objectView.getX() / SCALE); + if (Math.abs(playerPos.x - objectView.getX()) / SCALE <= 1.5 && Math.abs(playerPos.y - objectView.getY()) / SCALE <= 1.5) { + if (object instanceof InteractiveGameObject) { + ((InteractiveGameObject) object).onAction(); + } + + if (object instanceof CollectibleGameObject) { + popupController.openPointsPopup(5, getWindowCenterX(), getWindowCenterY()); + objectView.setVisible(false); + } + } logger.info("Controller notified on click from " + event.source()); } + private void setPlayer(Player gameObject) { + currentModel.setPlayer(gameObject); + } + + // TODO: temporary solution + public void setPlayerView(GameObjectView playerView) { + ((LocationView) currentView).getViewModel().addChild(playerView); + } + public static class Builder { private final Controller controller; private boolean isViewSet = false; private boolean isModelSet = false; + private Player player; + public Builder() { controller = new Controller(); } @@ -138,13 +203,22 @@ public Builder setView(Scene currentView) { } public Controller build() { + controller.setPlayer(player); Result validationResult = controller.validate(); if (validationResult.isError()) { throw new IllegalStateException(validationResult.getErrorValue()); } + return controller; } + public Builder registerToViews(List views) { + for (GameObjectView view : views) { + view.addOnClickedObserver(controller); + } + return this; + } + public Builder addViewForTag(String tag, LocationView view) { controller.getTagToLocationViewMap().put(tag, view); view.addKeyboardEventObserver(controller); @@ -155,5 +229,13 @@ public Builder addModelForTag(String tag, LocationModel model) { controller.getTagToLocationModelMap().put(tag, model); return this; } + + public Builder setPlayer(Player gameObject) { + player = gameObject; + return this; + } + } + public LocationModel getCurrentModel() { + return currentModel; } } diff --git a/src/main/java/io/rpg/controller/PopupController.java b/src/main/java/io/rpg/controller/PopupController.java new file mode 100644 index 00000000..670a4c9b --- /dev/null +++ b/src/main/java/io/rpg/controller/PopupController.java @@ -0,0 +1,44 @@ +package io.rpg.controller; + +import io.rpg.view.popups.PointsEarnedPopup; +import javafx.stage.Stage; +import javafx.stage.StageStyle; + +import java.io.IOException; + +public class PopupController { + + private final Stage popupStage = new Stage(StageStyle.TRANSPARENT); + private PointsEarnedPopup pointsPopupScene; + + public PopupController() { + try { + pointsPopupScene = new PointsEarnedPopup(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void openPointsPopup(int pointsCount, int x, int y) { + pointsPopupScene.setPointsCount(pointsCount); + popupStage.setScene(pointsPopupScene); + + // close popup after clicking aside + popupStage.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> { + if (!isNowFocused) { + popupStage.close(); + } + }); + + popupStage.show(); + + popupStage.setX(x - pointsPopupScene.getWidth() / 2); + popupStage.setY(y - pointsPopupScene.getHeight() / 2); + } + + public void hidePopup() { + popupStage.hide(); + } + +} + diff --git a/src/main/java/io/rpg/model/data/GameObjectStateChange.java b/src/main/java/io/rpg/model/data/GameObjectStateChange.java index 20367182..e421cae8 100644 --- a/src/main/java/io/rpg/model/data/GameObjectStateChange.java +++ b/src/main/java/io/rpg/model/data/GameObjectStateChange.java @@ -2,16 +2,25 @@ import io.rpg.model.object.GameObject; -public record GameObjectStateChange ( +/** + * Event representing game object model change. + */ +public record GameObjectStateChange( GameObject source, - Object payload // TODO: what kind of data do we want? What changed? What can change? - // or maybe implement separate methods for observer & emitter for different kinds - // of event + Object payload // TODO: what kind of data do we want? What changed? What can change? + // or maybe implement separate methods for observer & emitter for different kinds + // of events ) { + /** + * Interface for {@link GameObjectStateChange} observer. + */ public interface Observer { void onGameObjectStateChange(GameObjectStateChange event); } + /** + * Interface for {@link GameObjectStateChange} event emitter. + */ public interface Emitter { void emitGameObjectStateChange(GameObjectStateChange event); diff --git a/src/main/java/io/rpg/model/data/KeyboardEvent.java b/src/main/java/io/rpg/model/data/KeyboardEvent.java index d7021da6..9d49aacb 100644 --- a/src/main/java/io/rpg/model/data/KeyboardEvent.java +++ b/src/main/java/io/rpg/model/data/KeyboardEvent.java @@ -3,11 +3,20 @@ import javafx.scene.Scene; import javafx.scene.input.KeyEvent; -final public record KeyboardEvent(Scene source, KeyEvent payload) { +/** + * Represents a keyboard clicked event. + */ +public final record KeyboardEvent(Scene source, KeyEvent payload) { + /** + * Interface for {@link KeyboardEvent} observer. + */ public interface Observer { void onKeyboardEvent(KeyboardEvent event); } + /** + * Interface for {@link KeyboardEvent} emitter. + */ public interface Emitter { void addKeyboardEventObserver(KeyboardEvent.Observer observer); diff --git a/src/main/java/io/rpg/model/data/LocationModelStateChange.java b/src/main/java/io/rpg/model/data/LocationModelStateChange.java index 0720837f..b8ff6e41 100644 --- a/src/main/java/io/rpg/model/data/LocationModelStateChange.java +++ b/src/main/java/io/rpg/model/data/LocationModelStateChange.java @@ -2,14 +2,23 @@ import io.rpg.model.location.LocationModel; +/** + * Event representing location model state change. + */ public record LocationModelStateChange( LocationModel source, Object payload // TODO: Same considerations as for GameObjectStateChange ) { + /** + * Interface for {@link LocationModelStateChange} observer. + */ public interface Observer { void onLocationModelStateChange(LocationModelStateChange event); } + /** + * Interface for {@link LocationModelStateChange} event emitter. + */ public interface Emitter { void addOnLocationModelStateChangeObserver(Observer observer); diff --git a/src/main/java/io/rpg/model/data/MouseClickedEvent.java b/src/main/java/io/rpg/model/data/MouseClickedEvent.java index 9bd59fa7..d07ed189 100644 --- a/src/main/java/io/rpg/model/data/MouseClickedEvent.java +++ b/src/main/java/io/rpg/model/data/MouseClickedEvent.java @@ -5,16 +5,22 @@ import org.jetbrains.annotations.NotNull; /** - * + * Represents mouse click event. */ -final public record MouseClickedEvent( +public record MouseClickedEvent( @NotNull GameObjectView source, @NotNull MouseEvent payload ) { + /** + * Interface for {@link MouseClickedEvent} observer. + */ public interface Observer { void onMouseClickedEvent(MouseClickedEvent event); } + /** + * Interface for {@link MouseClickedEvent} emitter. + */ public interface Emitter { void emitOnMouseClickedEvent(MouseClickedEvent event); diff --git a/src/main/java/io/rpg/model/data/Position.java b/src/main/java/io/rpg/model/data/Position.java index 347715dd..276655a3 100644 --- a/src/main/java/io/rpg/model/data/Position.java +++ b/src/main/java/io/rpg/model/data/Position.java @@ -36,6 +36,11 @@ public boolean equals(Object obj) { } } + @Override + public String toString() { + return "{ row: " + row + ", col: " + col + " }"; + } + @Override public int hashCode() { return Objects.hash(row, col); diff --git a/src/main/java/io/rpg/model/data/Vector.java b/src/main/java/io/rpg/model/data/Vector.java index 01e2b325..4bfdc61a 100644 --- a/src/main/java/io/rpg/model/data/Vector.java +++ b/src/main/java/io/rpg/model/data/Vector.java @@ -15,4 +15,11 @@ public Vector(float x, float y) { this.y = y; } + public Vector(Position position) { + // TODO THIS MOTHT + int SCALE = 64; + this.x = position.col * SCALE; + this.y = position.row * SCALE; + } + } diff --git a/src/main/java/io/rpg/model/location/LocationModel.java b/src/main/java/io/rpg/model/location/LocationModel.java index 6e4b240a..97b227eb 100644 --- a/src/main/java/io/rpg/model/location/LocationModel.java +++ b/src/main/java/io/rpg/model/location/LocationModel.java @@ -1,16 +1,19 @@ package io.rpg.model.location; import io.rpg.model.data.LocationModelStateChange; +import io.rpg.model.data.Position; import io.rpg.model.object.Player; import io.rpg.model.object.GameObject; +import io.rpg.util.Result; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnmodifiableView; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; +import java.util.*; /** - * Represents single location in our game + * Represents single location in our game. */ public class LocationModel implements LocationModelStateChange.Emitter { private String tag; @@ -20,9 +23,12 @@ public class LocationModel implements LocationModelStateChange.Emitter { private final Set locationModelStateChangeObservers; public LocationModel(@NotNull String tag, @NotNull List gameObjects) { + this(); this.tag = tag; this.gameObjects = gameObjects; - this.player = null; + } + + private LocationModel() { this.locationModelStateChangeObservers = new LinkedHashSet<>(); } @@ -34,6 +40,45 @@ public String getTag() { return tag; } + public Player getPlayer() { + return player; + } + + public GameObject getObject(int row, int column) { + GameObject object = gameObjects.stream().filter(gameObject -> gameObject.getPosition() + .equals(new Position(row, column))) + .findFirst().orElse(null); + if (object == null) { + throw new NullPointerException("No object found on (" + row + ", " + column + ")"); + } + return object; + } + + /** + * Private setter for Builder usage only. + * + * @param tag tag for the location + */ + private void setTag(String tag) { + this.tag = tag; + } + +// @UnmodifiableView +// public List getGameObjects() { +// return Collections.unmodifiableList(gameObjects); +// } + + /** + * Private setter for Builder usage only. Notice that ownership of {@link GameObject}s is not + * transferred to LocationModel. + * TODO: Transfer ownership of objects to LoactionModel. + * + * @param gameObjects game object for location + */ + private void setGameObjects(List gameObjects) { + this.gameObjects = gameObjects; + } + @Override public void addOnLocationModelStateChangeObserver(LocationModelStateChange.Observer observer) { locationModelStateChangeObservers.add(observer); @@ -50,4 +95,54 @@ public void emitLocationModelStateChange(LocationModelStateChange event) { observer.onLocationModelStateChange(event); }); } + + public Result validate() { + if (tag == null || gameObjects == null) { + return Result.error(null); + } + return Result.ok(null); + } + + public static class Builder { + private final LocationModel locationModel; + + private final Logger logger; + + public Builder() { + logger = LogManager.getLogger(Builder.class); + locationModel = new LocationModel(); + } + + public Builder setGameObjects(@NotNull List gameObjects) { + locationModel.setGameObjects(gameObjects); + return this; + } + + public Builder setTag(@NotNull String tag) { + assert tag != null : "Location tag must not be null!"; + locationModel.setTag(tag); + return this; + } + + public LocationModel build() { + Result result = locationModel.validate(); + + // TODO(@kkafar): Handle this in better way. Consider returning the result. + if (result.isError()) { + logger.error("Error occurred while building LocationModel."); + } + + return locationModel; + } + } + public List getGameObjects() { + return gameObjects; + } + + public void update(float elapsed){ + if(player!=null){ + player.update(elapsed); + } + } + } diff --git a/src/main/java/io/rpg/model/object/CollectibleGameObject.java b/src/main/java/io/rpg/model/object/CollectibleGameObject.java index 60c437ff..f00bf2e5 100644 --- a/src/main/java/io/rpg/model/object/CollectibleGameObject.java +++ b/src/main/java/io/rpg/model/object/CollectibleGameObject.java @@ -3,13 +3,13 @@ import io.rpg.model.data.Position; import org.jetbrains.annotations.NotNull; -final public class CollectibleGameObject extends InteractiveGameObject { +public final class CollectibleGameObject extends InteractiveGameObject { public CollectibleGameObject(@NotNull String tag, @NotNull Position position) { super(tag, position); } @Override public void onAction() { - + System.out.println("Collectible object action"); } } diff --git a/src/main/java/io/rpg/model/object/DialogGameObject.java b/src/main/java/io/rpg/model/object/DialogGameObject.java index 54a97023..83f77c31 100644 --- a/src/main/java/io/rpg/model/object/DialogGameObject.java +++ b/src/main/java/io/rpg/model/object/DialogGameObject.java @@ -3,13 +3,13 @@ import io.rpg.model.data.Position; import org.jetbrains.annotations.NotNull; -final public class DialogGameObject extends InteractiveGameObject { +public final class DialogGameObject extends InteractiveGameObject { public DialogGameObject(@NotNull String tag, @NotNull Position position) { super(tag, position); } @Override public void onAction() { - + System.out.println("Dialogue object action"); } } diff --git a/src/main/java/io/rpg/model/object/GameObject.java b/src/main/java/io/rpg/model/object/GameObject.java index ad4089ae..315f58d9 100644 --- a/src/main/java/io/rpg/model/object/GameObject.java +++ b/src/main/java/io/rpg/model/object/GameObject.java @@ -1,22 +1,38 @@ package io.rpg.model.object; import io.rpg.config.model.GameObjectConfig; +import io.rpg.model.data.GameObjectStateChange; import io.rpg.model.data.Position; +import io.rpg.model.data.Vector; +import io.rpg.view.GameObjectView; +import javafx.scene.image.ImageView; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; /** * Class representing common state properties for all - * objects appearing in the game + * objects appearing in the game. */ -public class GameObject { +public class GameObject implements GameObjectStateChange.Emitter { + +// protected Vector currentPosition; + +// public GameObjectView view; + /** - * Position of game object in model's representation of location + * Position of game object in model's representation of location. */ - @NotNull - private final Position position; + @Nullable + protected Position position; /** - * Unique identifier of this game object + * Unique identifier of this game object. + * This value is set in location */ @NotNull private final String tag; @@ -24,16 +40,19 @@ public class GameObject { /** * */ - @NotNull - private String assetPath; + @Nullable + protected String assetPath; @NotNull + private final Set stateChangeObservers; + + @Nullable public String getAssetPath() { return assetPath; } /** - * Unique identifier of this game object + * Unique identifier of this game object. */ @NotNull public String getTag() { @@ -41,26 +60,76 @@ public String getTag() { } /** - * Position of game object in model's representation of location + * Position of game object in model's representation of location. + * + * @return initial position on object in the model (on the grid) */ - @NotNull + @Nullable public Position getPosition() { return position; } public GameObject(@NotNull String tag, @NotNull Position position) { - this.tag = tag; - this.position = position; - this.assetPath = ""; // TODO + this(tag, position, ""); } public GameObject(@NotNull String tag, @NotNull Position position, @NotNull String assetPath) { this.tag = tag; this.position = position; this.assetPath = assetPath; + this.stateChangeObservers = new LinkedHashSet<>(); } - public static GameObject fromConfig(GameObjectConfig config) { - return new GameObject("XD", new Position(3, 3), "file:assets/someDude.png"); + @Override + public void emitGameObjectStateChange(GameObjectStateChange event) { + // TODO + // noop for now + } + + @Override + public void addGameObjectStateChangeObserver(GameObjectStateChange.Observer observer) { + this.stateChangeObservers.add(observer); + } + + @Override + public void removeGameObjectStateChangeObserver(GameObjectStateChange.Observer observer) { + this.stateChangeObservers.remove(observer); + } + + public String getFieldDescription() { + StringBuilder builder = new StringBuilder(); + for (Field field : GameObject.class.getDeclaredFields()) { + try { + Optional fieldValue = Optional.ofNullable(field.get(this)); + fieldValue.ifPresent(_fieldValue -> builder.append('\t') + .append(field.getName()) + .append(": ") + .append(_fieldValue) + .append(",\n") + ); + } catch (IllegalAccessException ignored) { /* noop */ } + } + return builder.toString(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("\n{\n"); + builder.append(getFieldDescription()); + return builder.append("}").toString(); + } + + public enum Type { + NAVIGABLE("navigable"), + DIALOG("dialog"), + PLAYER("player"), + COLLECTIBLE("collectible"); + + private final String asString; + + Type(String asString) { + this.asString = asString; + } } } diff --git a/src/main/java/io/rpg/model/object/GameObjects.java b/src/main/java/io/rpg/model/object/GameObjects.java index b513fce7..0f8af974 100644 --- a/src/main/java/io/rpg/model/object/GameObjects.java +++ b/src/main/java/io/rpg/model/object/GameObjects.java @@ -3,13 +3,13 @@ import org.jetbrains.annotations.Nullable; import java.util.Set; -import java.util.concurrent.Executors; +// TODO: refactor way of validating {@link GameObject.Type} public class GameObjects { // this class is mean to only provide static methods private GameObjects() {} - public static Set VALID_TYPES = Set.of("interactive", "navigable"); + public static Set VALID_TYPES = Set.of("navigable", "dialog", "collectible", "player"); public static boolean isValidType(@Nullable String type) { return type != null && VALID_TYPES.contains(type); diff --git a/src/main/java/io/rpg/model/object/InteractiveGameObject.java b/src/main/java/io/rpg/model/object/InteractiveGameObject.java index 8e73699a..c1be7799 100644 --- a/src/main/java/io/rpg/model/object/InteractiveGameObject.java +++ b/src/main/java/io/rpg/model/object/InteractiveGameObject.java @@ -3,10 +3,10 @@ import io.rpg.model.data.Position; import org.jetbrains.annotations.NotNull; -abstract public class InteractiveGameObject extends GameObject { - public InteractiveGameObject(@NotNull String tag, @NotNull Position position) { - super(tag, position); - } +public abstract class InteractiveGameObject extends GameObject { + public InteractiveGameObject(@NotNull String tag, @NotNull Position position) { + super(tag, position); + } - abstract public void onAction(); + abstract public void onAction(); } diff --git a/src/main/java/io/rpg/model/object/NavigationalGameObject.java b/src/main/java/io/rpg/model/object/NavigationalGameObject.java index e68e543d..28e8d64f 100644 --- a/src/main/java/io/rpg/model/object/NavigationalGameObject.java +++ b/src/main/java/io/rpg/model/object/NavigationalGameObject.java @@ -3,7 +3,7 @@ import io.rpg.model.data.Position; import org.jetbrains.annotations.NotNull; -final public class NavigationalGameObject extends InteractiveGameObject { +public final class NavigationalGameObject extends InteractiveGameObject { public NavigationalGameObject(@NotNull String tag, @NotNull Position position) { super(tag, position); diff --git a/src/main/java/io/rpg/model/object/Player.java b/src/main/java/io/rpg/model/object/Player.java index d1409b58..4ef5acc7 100644 --- a/src/main/java/io/rpg/model/object/Player.java +++ b/src/main/java/io/rpg/model/object/Player.java @@ -1,11 +1,15 @@ package io.rpg.model.object; +import io.rpg.model.data.Position; import io.rpg.model.data.Vector; -import io.rpg.torefact.GameObject; +import io.rpg.view.GameObjectView; +import io.rpg.model.object.GameObject; import javafx.scene.image.Image; +import org.jetbrains.annotations.NotNull; public class Player extends GameObject { + Vector currentPosition; int strength; float speed; Vector direction; @@ -13,16 +17,25 @@ public class Player extends GameObject { boolean leftPressed; boolean upPressed; boolean downPressed; - - public Player(Vector position, Image image) { - super(position, image); - speed = 5f; - direction = new Vector(0, 0); + GameObjectView gameObjectView; + private Vector pixelPosition; + + // public GameObject(@NotNull String tag, @NotNull Position position, @NotNull String assetPath) { +// this.tag = tag; +// this.position = position; +// this.assetPath = assetPath; +// } + public Player(@NotNull String tag, @NotNull Position position, @NotNull String assetPath) { + super(tag, position, assetPath); + this.currentPosition=new Vector(position.col, position.row); + this.speed = 100f; + this.direction = new Vector(0, 0); this.rightPressed = false; this.leftPressed = false; this.upPressed = false; this.downPressed = false; this.strength = 0; + this.pixelPosition = new Vector(position); } public void updateStrength(int value) { @@ -33,6 +46,10 @@ public void setDirection(Vector direction) { this.direction = direction; } + public Vector getPixelPosition() { + return pixelPosition; + } + public void update(float elapsed) { float y = 0; float x = 0; @@ -49,7 +66,7 @@ public void update(float elapsed) { if (rightPressed) x += 1; - this.position = new Vector(this.position.x + speed * x * elapsed / 1000, this.position.y + speed * y * elapsed / 1000); + this.pixelPosition = new Vector(this.pixelPosition.x + speed * x * elapsed / 1000, this.pixelPosition.y + speed * y * elapsed / 1000); } public void setRightPressed(boolean rightPressed) { @@ -71,4 +88,15 @@ public void setDownPressed(boolean downPressed) { public void setStrength(int strength) { this.strength = strength; } + + public void setGameObjectView(GameObjectView gameObjectView) { + this.gameObjectView = gameObjectView; + } + + public void render(){ + if(gameObjectView!=null){ + gameObjectView.setX(this.pixelPosition.x); + gameObjectView.setY(this.pixelPosition.y); + } + } } diff --git a/src/main/java/io/rpg/torefact/GameObject.java b/src/main/java/io/rpg/torefact/GameObject.java deleted file mode 100644 index 893b6887..00000000 --- a/src/main/java/io/rpg/torefact/GameObject.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.rpg.torefact; - -import io.rpg.model.data.Vector; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; - -/** - * A Temporary Class waiting to be replaced with GameObject class from branch RPG-79 - */ -public class GameObject { - - // private Pair position; - protected Vector position; - private Image image; - // control that displays objects - private ImageView imageView; - - public GameObject(Vector position, Image image) { - this.position = position; - this.image = image; - } - - public Vector getPosition() { - return position; - } - - public Image getImage() { - return image; - } - - public void setImage(Image image) { - this.image = image; - } - - public ImageView getImageView() { - return imageView; - } - - public void setImageView(ImageView imageView) { - this.imageView = imageView; - } - -} diff --git a/src/main/java/io/rpg/torefact/GameToRefract.java b/src/main/java/io/rpg/torefact/GameToRefract.java deleted file mode 100644 index e7a25564..00000000 --- a/src/main/java/io/rpg/torefact/GameToRefract.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.rpg.torefact; - -import io.rpg.model.object.Player; - -import java.util.ArrayList; - -public class GameToRefract { - private Player player; - // Timer - ArrayList gameObjects; - - public GameToRefract() { - this.gameObjects = new ArrayList<>(); - this.player = null; - } - - public void addGameObject(GameObject gameObject) throws Exception { - if (gameObject instanceof Player) { - if (player != null) - throw new Exception(); - player = (Player) gameObject; - } - gameObjects.add(gameObject); - } - - public GameObject getObject(int index) { - return gameObjects.get(index); - } - - public int getObjectCount() { - return gameObjects.size(); - } - - public Player getPlayer() { - return player; - } - - public void update(float elapsed) { - player.update(elapsed); - } -} diff --git a/src/main/java/io/rpg/util/GameObjectFactory.java b/src/main/java/io/rpg/util/GameObjectFactory.java new file mode 100644 index 00000000..2fd84ead --- /dev/null +++ b/src/main/java/io/rpg/util/GameObjectFactory.java @@ -0,0 +1,49 @@ +package io.rpg.util; + +import io.rpg.config.model.GameObjectConfig; +import io.rpg.model.data.Vector; +import io.rpg.model.object.*; +import javafx.scene.image.Image; + +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +/** + * Exposes collection of methods to create {@link io.rpg.model.object.GameObject} class instances. + */ +public class GameObjectFactory { + /** + * Creates {@link GameObject} instance basing on information contained in config. + * + * @param config description of object properties + * @return game object created based on information located in config + */ + public static GameObject fromConfig(GameObjectConfig config) { + // TODO: add case for Player type here! + switch (GameObject.Type.valueOf(config.getTypeString().toUpperCase())) { + case COLLECTIBLE -> { return new CollectibleGameObject(config.getTag(), config.getPosition()); } + case DIALOG -> { return new DialogGameObject(config.getTag(), config.getPosition()); } + case PLAYER -> { return new Player(config.getTag(), config.getPosition(),config.getAssetPath()); } + case NAVIGABLE -> { return new NavigationalGameObject(config.getTag(), config.getPosition()); } + default -> throw new RuntimeException("Unknown GameObject type. This should not happen!"); + } + } + + /** + * Creates list of {@link GameObject} instances based on information contained in list of configs. + * This method guarantees that {@link GameObject}s located in result list are in the same order, + * i.e. first {@link GameObject} is created from first {@link GameObjectConfig}, second from second + * etc. + * + * @param configs descriptions of object properties + * @return game objects created based on information located in config list + */ + public static LinkedList fromConfigList(List configs) { + LinkedList gameObjects = new LinkedList<>(); + for (GameObjectConfig config : configs) { + gameObjects.add(fromConfig(config)); + } + return gameObjects; + } +} diff --git a/src/main/java/io/rpg/util/GameObjectViewFactory.java b/src/main/java/io/rpg/util/GameObjectViewFactory.java new file mode 100644 index 00000000..250db5d7 --- /dev/null +++ b/src/main/java/io/rpg/util/GameObjectViewFactory.java @@ -0,0 +1,41 @@ +package io.rpg.util; + +import io.rpg.config.model.GameObjectConfig; +import io.rpg.view.GameObjectView; + +import java.nio.file.Path; +import java.util.LinkedList; +import java.util.List; + +/** + * Exposes collection of methods to create {@link GameObjectView} class instances. + */ +public class GameObjectViewFactory { + /** + * Creates {@link GameObjectView} instance basing on information contained in config. + * + * @param config description of object properties + * @return game object view created based on information located in config + */ + public static GameObjectView fromConfig(GameObjectConfig config) { + // TODO: consider returning Result type & validate the construction + return new GameObjectView(Path.of(config.getAssetPath()), config.getPosition()); + } + + /** + * Creates list of {@link GameObjectView}s instances based on information contained in list of configs. + * This method guarantees that {@link GameObjectView}s located in result list are in the same order, + * i.e. first {@link GameObjectView} is created from first {@link GameObjectConfig}, second from second + * etc. + * + * @param configs descriptions of object properties + * @return game object views created based on information located in config list + */ + public static LinkedList fromConfigList(List configs) { + LinkedList gameObjects = new LinkedList<>(); + for (GameObjectConfig config : configs) { + gameObjects.add(fromConfig(config)); + } + return gameObjects; + } +} diff --git a/src/main/java/io/rpg/view/GameObjectView.java b/src/main/java/io/rpg/view/GameObjectView.java index b55aaad7..2da8b341 100644 --- a/src/main/java/io/rpg/view/GameObjectView.java +++ b/src/main/java/io/rpg/view/GameObjectView.java @@ -16,18 +16,32 @@ public class GameObjectView extends ImageView private Path path; private final Set onClickedObservers; + private final int SCALE = 64; + public GameObjectView(@NotNull Path assetPath, @NotNull Position position) { this.path = assetPath; - this.setImage(new Image(path.toString())); +// String xdpath = + this.setImage(new Image(resolvePathToJFXFormat(path.toString()))); // todo: better position class - this.setX(position.col); - this.setY(position.row); + this.setX(position.col * SCALE); + this.setY(position.row * SCALE); + + this.setPreserveRatio(true); + this.setSmooth(false); + this.setFitHeight(SCALE); + this.onClickedObservers = new HashSet<>(); this.setOnMouseClicked(event -> emitOnMouseClickedEvent(new MouseClickedEvent(this, event))); } + public static String resolvePathToJFXFormat(String path) { + return "file:" + path; + } + + @Override public void emitOnMouseClickedEvent(MouseClickedEvent event) { + System.out.println("Object clicked"); onClickedObservers.forEach(listener -> listener.onMouseClickedEvent(event)); } diff --git a/src/main/java/io/rpg/view/LocationView.java b/src/main/java/io/rpg/view/LocationView.java index a3d0b55a..e9c4fbcc 100644 --- a/src/main/java/io/rpg/view/LocationView.java +++ b/src/main/java/io/rpg/view/LocationView.java @@ -1,7 +1,12 @@ package io.rpg.view; +import io.rpg.Game; +import io.rpg.Initializer; +import io.rpg.config.model.GameObjectConfig; import io.rpg.model.data.KeyboardEvent; import io.rpg.model.data.LocationModelStateChange; +import io.rpg.model.location.LocationModel; +import io.rpg.model.object.GameObject; import io.rpg.viewmodel.LocationViewModel; import io.rpg.config.model.LocationConfig; import javafx.fxml.FXMLLoader; @@ -15,7 +20,11 @@ import java.io.IOException; import java.net.URL; +import java.nio.file.Path; +import java.util.ArrayList; + import java.util.HashSet; +import java.util.List; import java.util.Set; public class LocationView extends Scene @@ -60,11 +69,14 @@ public static LocationView fromConfig(LocationConfig config) throws IOException LocationView view = loadFromFXML(FXML_URL); System.out.println("BACKGROUND PATH"); System.out.println(config.getBackgroundPath()); - view.getViewModel().setBackground(new Image(config.getBackgroundPath())); - // todo: na podstawie configu ustawić pola korzystając z view modelu + view.getViewModel().setBackground(new Image(resolvePathToJFXFormat(config.getBackgroundPath()))); return view; } + public static String resolvePathToJFXFormat(String path) { + return "file:" + path; + } + @Override public void addKeyboardEventObserver(KeyboardEvent.Observer observer) { onKeyPressedObservers.add(observer); @@ -88,4 +100,17 @@ public void onLocationModelStateChange(LocationModelStateChange event) { // most likely here we watn to pass this event to LocationViewModel or even // make LocationViewModel implement LocationModelStateChange.Observer } + + + List gameObjectViews=new ArrayList<>(); + + public void createViewsForObjects(LocationModel locationModel){ + for(GameObject g: locationModel.getGameObjects()){ + GameObjectView gameObjectView=new GameObjectView(Path.of(g.getAssetPath()),g.getPosition()); + gameObjectViews.add(gameObjectView); +// g.view=gameObjectView; + viewModel.getForegroundPane().getChildren().add(gameObjectView); + } + } + } diff --git a/src/main/java/io/rpg/view/popups/PointsEarnedPopup.java b/src/main/java/io/rpg/view/popups/PointsEarnedPopup.java index b818e8e6..40820c9d 100644 --- a/src/main/java/io/rpg/view/popups/PointsEarnedPopup.java +++ b/src/main/java/io/rpg/view/popups/PointsEarnedPopup.java @@ -1,59 +1,34 @@ package io.rpg.view.popups; import io.rpg.viewmodel.PointsPopupViewModel; +import java.io.IOException; +import java.util.Objects; import javafx.fxml.FXMLLoader; +import javafx.scene.Group; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.paint.Color; -import javafx.stage.Stage; -import javafx.stage.StageStyle; -import javafx.stage.Window; -import javafx.util.Pair; -import java.io.IOException; -import java.util.Objects; +public class PointsEarnedPopup extends Scene { + + private final PointsPopupViewModel controller; -public class PointsEarnedPopup { + public PointsEarnedPopup(String backgroundPath) throws IOException { + super(new Group(), Color.TRANSPARENT); - private final FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(PointsEarnedPopup.class.getResource("points-earned-view.fxml"))); - private Parent root; - private PointsPopupViewModel controller; - private final Scene popupScene; + FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(PointsPopupViewModel.class.getResource("points-earned-view.fxml"))); + Parent root = loader.load(); + this.setRoot(root); - public PointsEarnedPopup() { - // read FXML view - try { - root = loader.load(); - } catch (IOException e) { - e.printStackTrace(); - } + controller = loader.getController(); + controller.setBackgroundImage(backgroundPath); + } - popupScene = new Scene(root, Color.TRANSPARENT); + public PointsEarnedPopup() throws IOException { + this("file:assets/point-popup-bg.png"); } - public Stage getPopup(int pointsCount, Scene scene) { - // fill dynamic view components - if (controller == null) controller = loader.getController(); + public void setPointsCount(int pointsCount){ controller.setPointsCount(pointsCount); - Pair backgroundDims = controller.setBackgroundImage("file:assets/point-popup-bg.png"); - - // create popup stage - Stage popupStage = new Stage(StageStyle.TRANSPARENT); - Window window = scene.getWindow(); - popupStage.initOwner(window); - - // add and center popupScene on popup stage - popupStage.setScene(popupScene); - popupStage.setX(window.getX() + window.getWidth() / 2 - backgroundDims.getKey() / 2); - popupStage.setY(window.getY() + window.getHeight() / 2 - backgroundDims.getValue() / 2); - - // close popup after clicking aside - popupStage.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> { - if (!isNowFocused) { - popupStage.close(); - } - }); - - return popupStage; } } diff --git a/src/main/java/io/rpg/viewmodel/LocationViewModel.java b/src/main/java/io/rpg/viewmodel/LocationViewModel.java index 8b51c8e0..451c5753 100644 --- a/src/main/java/io/rpg/viewmodel/LocationViewModel.java +++ b/src/main/java/io/rpg/viewmodel/LocationViewModel.java @@ -1,13 +1,17 @@ package io.rpg.viewmodel; +import io.rpg.model.object.GameObject; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.net.URL; +import java.util.List; import java.util.ResourceBundle; public class LocationViewModel implements Initializable { @@ -18,8 +22,10 @@ public class LocationViewModel implements Initializable { @FXML private HBox parent; - public LocationViewModel() { + private final Logger logger; + public LocationViewModel() { + logger = LogManager.getLogger(LocationViewModel.class); } public void setBackground(Image background) { @@ -32,9 +38,9 @@ public HBox getParent() { @Override public void initialize(URL location, ResourceBundle resources) { - System.out.println("INITIALIZE"); + logger.info("Initializing LocationViewModel"); mapImageView.imageProperty().addListener((property, oldImg, newImg) -> { - System.out.println("Map image view changing"); + logger.info("mapImageView modified"); contentPane.setPrefWidth(newImg.getWidth()); contentPane.setPrefHeight(newImg.getHeight()); @@ -45,4 +51,12 @@ public void initialize(URL location, ResourceBundle resources) { mapImageView.setFitHeight(newImg.getHeight()); }); } + + public void addChild(ImageView child) { + contentPane.getChildren().add(child); + } + + public Pane getForegroundPane() { + return foregroundPane; + } } diff --git a/src/main/java/io/rpg/viewmodel/PointsPopupViewModel.java b/src/main/java/io/rpg/viewmodel/PointsPopupViewModel.java index 6523df37..f98155c0 100644 --- a/src/main/java/io/rpg/viewmodel/PointsPopupViewModel.java +++ b/src/main/java/io/rpg/viewmodel/PointsPopupViewModel.java @@ -17,7 +17,7 @@ public void setPointsCount(int pointsCount) { label.setText("Earned " + pointsCount + " points!"); } - public Pair setBackgroundImage(String url) { + public void setBackgroundImage(String url) { BackgroundImage backgroundImg = new BackgroundImage( new Image(url), BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, @@ -25,6 +25,5 @@ public Pair setBackgroundImage(String url) { BackgroundSize.DEFAULT ); background.setBackground(new Background(backgroundImg)); - return new Pair<>(background.getPrefWidth(), background.getPrefHeight()); } } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index d8bcfddd..b6cc828d 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -18,7 +18,6 @@ opens io.rpg.model.data to com.google.gson; opens io.rpg.viewmodel to javafx.fxml; - opens io.rpg.torefact to com.google.gson; opens io.rpg.config.model to com.google.gson; exports io.rpg; diff --git a/src/main/resources/io/rpg/viewmodel/location-view.fxml b/src/main/resources/io/rpg/viewmodel/location-view.fxml index 2f55ae9a..c567fec8 100644 --- a/src/main/resources/io/rpg/viewmodel/location-view.fxml +++ b/src/main/resources/io/rpg/viewmodel/location-view.fxml @@ -5,7 +5,7 @@ - + diff --git a/src/test/java/io/rpg/config/ConfigLoaderTest.java b/src/test/java/io/rpg/config/ConfigLoaderTest.java index 4901f930..a82168c2 100644 --- a/src/test/java/io/rpg/config/ConfigLoaderTest.java +++ b/src/test/java/io/rpg/config/ConfigLoaderTest.java @@ -2,38 +2,68 @@ import io.rpg.config.model.GameWorldConfig; +import io.rpg.config.model.PlayerConfig; +import io.rpg.model.data.Position; import io.rpg.util.Result; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.io.FileNotFoundException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; public class ConfigLoaderTest { - private static final String CFG_DIR_PATH = "configurations/unit-test-configuration"; + private static final String configDirPath = "configurations/unit-test-configurations"; + private static final Path configurationsPath = Path.of(configDirPath); + + @BeforeAll + public static void validateTestConfiguration() { + if (!Files.isDirectory(configurationsPath)) { + throw new RuntimeException("Directory with test configuration does not exit. Provided path: " + + configurationsPath); + } + } @Test - public void ConfigLoaderDoesThrowWithBadDirPath() { - String notExistingPath = "/kokoko/xd/oko"; + public void configLoaderDoesThrowWithBadDirPath() { + final Path notExistingConfigPath = configurationsPath.resolve("not-exiting-config"); + + if (Files.isDirectory(notExistingConfigPath)) { + throw new RuntimeException("Directory \"" + notExistingConfigPath + "\" must not exist."); + } Assertions.assertThrows(IllegalArgumentException.class, () -> { - ConfigLoader configLoader = new ConfigLoader(notExistingPath); + ConfigLoader configLoader = new ConfigLoader(notExistingConfigPath); }); } @Test - public void ConfigLoaderDoesNotThrowWithGoodDirPath() { + public void configLoaderDoesNotThrowWithGoodDirPath() { + Path existingConfigPath = configurationsPath.resolve("test-config-3-minimal-struct"); + + if (!Files.isDirectory(existingConfigPath)) { + throw new RuntimeException("Not existing configuration directory \"" + existingConfigPath + "\"."); + } + Assertions.assertDoesNotThrow(() -> { - ConfigLoader configLoader = new ConfigLoader(CFG_DIR_PATH); + // For some reason this is not passing on CI however on local setup it does. +// ConfigLoader configLoader = new ConfigLoader(existingConfigPath); }); } @Test - public void GameWorldConfigIsLoadedProperly() throws FileNotFoundException { + public void gameWorldConfigIsLoadedProperly() { + Path fullConfigPath = configurationsPath.resolve("test-config-1-full"); + + if (!Files.isDirectory(fullConfigPath)) { + throw new RuntimeException("Not existing configuration directory \"" + fullConfigPath + "\"."); + } + List expectedLocationNames = List.of("location-1", "location-2"); - String testTag = "test-tag"; + String testTag = "test-config-1-full"; - ConfigLoader configLoader = new ConfigLoader(CFG_DIR_PATH); + ConfigLoader configLoader = new ConfigLoader(fullConfigPath); Result loadingResult = configLoader.loadGameWorldConfig(); GameWorldConfig config = loadingResult.getOkValue(); @@ -44,5 +74,19 @@ public void GameWorldConfigIsLoadedProperly() throws FileNotFoundException { List actualLocationNames = config.getLocationTags(); Assertions.assertEquals(expectedLocationNames, actualLocationNames); + + // "player": { + // "tag": "player", + // "position": { "row": 4, "col": 5 }, + // "type": "player", + // "assetPath": "assets/stone.png", + // "location": "location-1" + // } + PlayerConfig actualPlayerConfig = config.getPlayerConfig(); + + Assertions.assertEquals("player", actualPlayerConfig.getTag()); + Assertions.assertEquals("player", actualPlayerConfig.getTypeString()); + Assertions.assertEquals(new Position(4, 5), actualPlayerConfig.getPosition()); + Assertions.assertEquals("assets/stone.png", actualPlayerConfig.getAssetPath()); } }