diff --git a/.gitignore b/.gitignore index 0b6521df..c1cc16fe 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ # .gradle/ build/ +bin/ # Explicitly specifed as local # diff --git a/assets/map.png b/assets/map.png new file mode 100644 index 00000000..4df2990a Binary files /dev/null and b/assets/map.png differ diff --git a/assets/map10x10.png b/assets/map10x10.png new file mode 100644 index 00000000..de67ef4a Binary files /dev/null and b/assets/map10x10.png differ diff --git a/assets/map20x20.bmp b/assets/map20x20.bmp new file mode 100644 index 00000000..e951c447 Binary files /dev/null and b/assets/map20x20.bmp differ diff --git a/assets/point-popup-bg.png b/assets/point-popup-bg.png new file mode 100644 index 00000000..53115469 Binary files /dev/null and b/assets/point-popup-bg.png differ diff --git a/assets/someDude.png b/assets/someDude.png new file mode 100644 index 00000000..4b5b27a3 Binary files /dev/null and b/assets/someDude.png differ diff --git a/assets/someDudeButGreen.png b/assets/someDudeButGreen.png new file mode 100644 index 00000000..9f3c7024 Binary files /dev/null and b/assets/someDudeButGreen.png differ diff --git a/assets/stone.png b/assets/stone.png new file mode 100644 index 00000000..1f5298a8 Binary files /dev/null and b/assets/stone.png differ diff --git a/build.gradle b/build.gradle index caca2c16..94236a0a 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,13 @@ dependencies { implementation('org.kordamp.ikonli:ikonli-javafx:12.3.0') implementation('org.kordamp.bootstrapfx:bootstrapfx-core:0.4.0') + implementation('com.google.code.gson:gson:2.9.0') + + implementation('org.jetbrains:annotations:23.0.0') + + implementation('org.apache.logging.log4j:log4j-api:2.17.2') + implementation('org.apache.logging.log4j:log4j-core:2.17.2') + testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}") } @@ -59,4 +66,4 @@ jlink { jlinkZip { group = 'distribution' -} \ No newline at end of file +} diff --git a/configurations/config-1/locations/location-1/location-1.json b/configurations/config-1/locations/location-1/location-1.json new file mode 100644 index 00000000..2b451703 --- /dev/null +++ b/configurations/config-1/locations/location-1/location-1.json @@ -0,0 +1,7 @@ +{ + "tag": "location-1", + "objects": [ + { "tag": "object-1", "position": { "row": 0, "col": 5 }, "type": "collectible" }, + { "tag": "object-2", "position": { "row": 1, "col": 3 }, "type": "interactive" } + ] +} \ No newline at end of file diff --git a/configurations/config-1/locations/location-1/objects/object-1.json b/configurations/config-1/locations/location-1/objects/object-1.json new file mode 100644 index 00000000..6f2ed3ae --- /dev/null +++ b/configurations/config-1/locations/location-1/objects/object-1.json @@ -0,0 +1,5 @@ +{ + "tag": "object-1", + "assetPath": "/path/to/the/asset", + "type": "" +} \ No newline at end of file diff --git a/configurations/config-1/locations/location-1/objects/object-2.json b/configurations/config-1/locations/location-1/objects/object-2.json new file mode 100644 index 00000000..e69de29b diff --git a/configurations/config-1/locations/location-2/location-2.json b/configurations/config-1/locations/location-2/location-2.json new file mode 100644 index 00000000..7153494b --- /dev/null +++ b/configurations/config-1/locations/location-2/location-2.json @@ -0,0 +1,7 @@ +{ + "tag": "location-2", + "objects": [ + { "tag": "object-1", "position": { "row": 0, "col": 0 } }, + { "tag": "object-3", "position": { "row": 0, "col": 1 } } + ] +} \ No newline at end of file diff --git a/configurations/config-1/locations/location-2/objects/object-1.json b/configurations/config-1/locations/location-2/objects/object-1.json new file mode 100644 index 00000000..e69de29b diff --git a/configurations/config-1/locations/location-2/objects/object-3.json b/configurations/config-1/locations/location-2/objects/object-3.json new file mode 100644 index 00000000..e69de29b diff --git a/configurations/config-1/root.json b/configurations/config-1/root.json new file mode 100644 index 00000000..087555ca --- /dev/null +++ b/configurations/config-1/root.json @@ -0,0 +1,7 @@ +{ + "tag": "GameByConfig1", + "locations": [ + "location-1", + "location-2" + ] +} \ No newline at end of file diff --git a/configurations/unit-test-configuration/locations/location-1/location-1.json b/configurations/unit-test-configuration/locations/location-1/location-1.json new file mode 100644 index 00000000..33029c78 --- /dev/null +++ b/configurations/unit-test-configuration/locations/location-1/location-1.json @@ -0,0 +1,7 @@ +{ + "tag": "location-1", + "objects": [ + { "tag": "object-1", "position": { "row": 0, "col": 5 } }, + { "tag": "object-2", "position": { "row": 1, "col": 3 } } + ] +} \ No newline at end of file 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 new file mode 100644 index 00000000..e69de29b diff --git a/configurations/unit-test-configuration/locations/location-1/objects/object-2.json b/configurations/unit-test-configuration/locations/location-1/objects/object-2.json new file mode 100644 index 00000000..e69de29b diff --git a/configurations/unit-test-configuration/locations/location-2/location-2.json b/configurations/unit-test-configuration/locations/location-2/location-2.json new file mode 100644 index 00000000..7153494b --- /dev/null +++ b/configurations/unit-test-configuration/locations/location-2/location-2.json @@ -0,0 +1,7 @@ +{ + "tag": "location-2", + "objects": [ + { "tag": "object-1", "position": { "row": 0, "col": 0 } }, + { "tag": "object-3", "position": { "row": 0, "col": 1 } } + ] +} \ 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 new file mode 100644 index 00000000..e69de29b diff --git a/configurations/unit-test-configuration/locations/location-2/objects/object-2.json b/configurations/unit-test-configuration/locations/location-2/objects/object-2.json new file mode 100644 index 00000000..e69de29b diff --git a/configurations/unit-test-configuration/root.json b/configurations/unit-test-configuration/root.json new file mode 100644 index 00000000..7353bec9 --- /dev/null +++ b/configurations/unit-test-configuration/root.json @@ -0,0 +1,7 @@ +{ + "tag": "test-tag", + "locations": [ + "location-1", + "location-2" + ] +} \ No newline at end of file diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..07ff7e75 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,9 @@ +# Game configuration + +Configuration directory should include: + +1. `root.json` file +2. `locations` subdirectory + +e.g. + diff --git a/src/main/java/io/rpg/HelloApplication.java b/src/main/java/io/rpg/HelloApplication.java index 5feb906d..4499fbe6 100644 --- a/src/main/java/io/rpg/HelloApplication.java +++ b/src/main/java/io/rpg/HelloApplication.java @@ -1,23 +1,66 @@ package io.rpg; +import io.rpg.config.ConfigLoader; +import io.rpg.gui.DisplayLayer; +import io.rpg.model.GameObjectStandIn; +import io.rpg.gui.LocationController; +import io.rpg.gui.model.LocationModel; +import io.rpg.model.*; +import io.rpg.model.GameObject; +import javafx.animation.AnimationTimer; import javafx.application.Application; +import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; +import javafx.scene.image.Image; import javafx.stage.Stage; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configurator; +import javafx.util.Pair; import java.io.IOException; +import java.net.URL; public class HelloApplication extends Application { @Override public void start(Stage stage) throws IOException { - FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml")); - Scene scene = new Scene(fxmlLoader.load(), 320, 240); - stage.setTitle("Hello!"); - stage.setScene(scene); - stage.show(); - } + Image someMap = new Image("file:assets/map.png"); + Image someMap10x10 = new Image("file:assets/map10x10.png"); + Image someDude1 = new Image("file:assets/someDude.png"); + Image someDude2 = new Image("file:assets/someDudeButGreen.png"); + Image player = new Image("file:assets/stone.png"); + Game game=new Game(); + DisplayLayer displayLayer = new DisplayLayer(stage); + LocationModel locationModel=displayLayer.showLocation(); + LocationController locationController=displayLayer.getLocationController(); + locationController.setGame(game); + try{ + game.addGameObject(new GameObject(new Vector(0,0), someDude1)); + game.addGameObject(new GameObject(new Vector(0,5), someDude2)); + game.addGameObject(new GameObject(new Vector(5,5), someDude2)); + game.addGameObject(new Player(new Vector(7,7), player)); +// locationModel=displayLayer.showLocation(); + locationModel.setBackgroundImage(someMap); + locationModel.setGame(game); + }catch(Exception e){ + e.printStackTrace(); + } + AnimationTimer animationTimer=new AnimationTimer() { + long lastUpdate=-1; + @Override + public void handle(long now) { + if(lastUpdate!=-1){ + float difference=(now-lastUpdate)/1e6f; + game.update(difference); + locationModel.update(difference); + } + lastUpdate=now; + } + + }; - public static void main(String[] args) { - launch(); + animationTimer.start(); } -} \ No newline at end of file +} diff --git a/src/main/java/io/rpg/config/ConfigConstants.java b/src/main/java/io/rpg/config/ConfigConstants.java new file mode 100644 index 00000000..defc96f5 --- /dev/null +++ b/src/main/java/io/rpg/config/ConfigConstants.java @@ -0,0 +1,8 @@ +package io.rpg.config; + +final public class ConfigConstants { + public static final String ROOT = "root.json"; + public static final String LOCATIONS_DIR = "locations"; + public static final String OBJECTS_DIR = "objects"; + public static final String ASSETS_DIR = "resources"; +} diff --git a/src/main/java/io/rpg/config/ConfigLoader.java b/src/main/java/io/rpg/config/ConfigLoader.java new file mode 100644 index 00000000..a00de6fa --- /dev/null +++ b/src/main/java/io/rpg/config/ConfigLoader.java @@ -0,0 +1,165 @@ +package io.rpg.config; + +import com.google.gson.Gson; + +import io.rpg.model.GameWorldConfig; +import io.rpg.model.location.LocationConfig; + +import io.rpg.model.object.GameObjectConfig; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ConfigLoader { + @NotNull + private final Gson gson; + + @NotNull + private final Path pathToConfigDir; + + @NotNull + private final Path pathToRootFile; + + @NotNull + private final Path pathToLocationsDir; + + public static final String ERR_INVALID_CFG_DIR_PATH = "Could not resolve config directory." + + " Make sure that the config dir path is correct"; + + public static final String ERR_ROOT_FNF = ConfigConstants.ROOT + + " file was not found inside config directory. Make sure that the file exists and is named properly"; + + public static final String ERR_LOCATIONS_DIR_FNF = ConfigConstants.LOCATIONS_DIR + + " directory was not found inside configuration directory"; + + public static final String ERR_LOCATION_DIR_FNF_FOR_TAG = + "Directory was not found for location with tag: "; + + public static final String ERR_LOCATION_CFG_NR_FOR_TAG = + "Configuration file was not found for location: "; + + @NotNull + private final Logger logger; + + public ConfigLoader(@NotNull String configDirPath) { + logger = LogManager.getLogger(ConfigLoader.class); + + logger.info("Initializing"); + + pathToConfigDir = Path.of(configDirPath); + pathToRootFile = pathToConfigDir.resolve(ConfigConstants.ROOT); + pathToLocationsDir = pathToConfigDir.resolve(ConfigConstants.LOCATIONS_DIR); + gson = new Gson(); + + validate(); + } + + public void load() { + logger.info("Load"); + + GameWorldConfig config; + try { + config = loadGameWorldConfig(); + + logger.info("GameWorldConfig loaded"); + logger.info(config.toString()); + + } catch (FileNotFoundException e) { + throw new RuntimeException(ERR_ROOT_FNF); + } + + assert config.getLocations().size() > 0 : "Configuration must specify locations"; + + for (String locationTag : config.getLocations()) { + try { + logger.info("Loading location config for tag: " + locationTag); + + LocationConfig locationConfig = loadLocationConfig(locationTag); + + // todo: this should be called in loadLocationConfig method? + // locationConfig.validate(); + + logger.info("Location config loaded for tag: " + locationTag); + 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")); + } + +// Path GameObjectConfig + + } + + } catch (FileNotFoundException e) { + logger.warn("Failed to load location config for tag: " + locationTag); + e.printStackTrace(); + } + } + } + + @NotNull + GameWorldConfig loadGameWorldConfig() throws FileNotFoundException { + logger.info("Loading game world config"); + BufferedReader reader = new BufferedReader(new FileReader(pathToRootFile.toString())); + GameWorldConfig config = gson.fromJson(reader, GameWorldConfig.class); + + // todo: validate input +// config.validate(); + + return config; + } + + LocationConfig loadLocationConfig(@NotNull String locationTag) throws FileNotFoundException { + logger.info("Loading location: " + locationTag); + + Path locationDir = pathToLocationsDir.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); + } + + 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); + } + + BufferedReader reader = new BufferedReader(new FileReader(locationConfigJson.toString())); + LocationConfig config = gson.fromJson(reader, LocationConfig.class); + config.setPath(locationDir); + return config; + } + + public void validate() { + if (!Files.isDirectory(pathToConfigDir)) { + logger.error(ERR_INVALID_CFG_DIR_PATH); + throw new IllegalArgumentException(ERR_INVALID_CFG_DIR_PATH); + } else if (!Files.isReadable(pathToRootFile)) { + logger.error(ERR_ROOT_FNF); + throw new IllegalArgumentException(ERR_ROOT_FNF); + } else if (!Files.isDirectory(pathToLocationsDir)) { + logger.error(ERR_LOCATIONS_DIR_FNF); + throw new IllegalArgumentException(ERR_LOCATIONS_DIR_FNF); + } + } +} diff --git a/src/main/java/io/rpg/controller/.gitkeep b/src/main/java/io/rpg/controller/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/io/rpg/gui/DisplayLayer.java b/src/main/java/io/rpg/gui/DisplayLayer.java new file mode 100644 index 00000000..c7187cb1 --- /dev/null +++ b/src/main/java/io/rpg/gui/DisplayLayer.java @@ -0,0 +1,32 @@ +package io.rpg.gui; + +import io.rpg.gui.model.LocationModel; +import javafx.fxml.Initializable; +import javafx.scene.Scene; +import javafx.stage.Stage; + +import java.io.IOException; +import java.net.URL; +import java.util.ResourceBundle; + +public class DisplayLayer { + private final Stage mainStage; + private final LocationController locationController; + + public DisplayLayer(Stage mainStage) throws IOException { + this.mainStage = mainStage; + mainStage.show(); + locationController = LocationController.load(); + } + + + public LocationModel showLocation(){ + // maybe initialize the scene only once + mainStage.setScene(locationController.getScene()); + return locationController.getModel().clear(); + } + + public LocationController getLocationController(){ + return locationController; + } +} diff --git a/src/main/java/io/rpg/gui/LocationController.java b/src/main/java/io/rpg/gui/LocationController.java new file mode 100644 index 00000000..f9fe0ed6 --- /dev/null +++ b/src/main/java/io/rpg/gui/LocationController.java @@ -0,0 +1,136 @@ +package io.rpg.gui; + +import io.rpg.gui.model.LocationModel; +import io.rpg.gui.popups.PointsEarnedPopup; +import io.rpg.model.Game; +import io.rpg.model.Vector; +import javafx.event.Event; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Scene; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; + +import java.io.IOException; +import java.net.URL; +import java.util.ResourceBundle; + +public class LocationController implements Initializable { + private final static URL FXML_URL = LocationController.class.getResource("location-view.fxml"); + + @FXML private ImageView mapImageView; + @FXML private Pane foregroundPane, contentPane; + @FXML private HBox parent; + + private LocationModel model; + private Scene scene; + private Game game; + + private final PointsEarnedPopup pointsPopup = new PointsEarnedPopup(); + + public static LocationController load() throws IOException { + FXMLLoader loader = new FXMLLoader(FXML_URL); + loader.load(); + return loader.getController(); + } + + public LocationController() { + + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + mapImageView.imageProperty().addListener((property, oldImg, newImg) -> { + contentPane.setPrefWidth(newImg.getWidth()); + contentPane.setPrefHeight(newImg.getHeight()); + + foregroundPane.setPrefWidth(newImg.getWidth()); + foregroundPane.setPrefHeight(newImg.getHeight()); + + mapImageView.setFitWidth(newImg.getWidth()); + mapImageView.setFitHeight(newImg.getHeight()); + + }); + + model = new LocationModel(mapImageView.imageProperty(), foregroundPane.getChildren(), this); + + scene = new Scene(parent); + scene.addEventFilter(KeyEvent.KEY_TYPED, this::onKeyTyped); + scene.addEventFilter(KeyEvent.KEY_PRESSED, this::onKeyPressed); + scene.addEventFilter(KeyEvent.KEY_RELEASED, this::onKeyReleased); + } + + public void onKeyPressed(KeyEvent event){ + switch(event.getCode()){ + case W: +// game.getPlayer().setDirection(new Vector(0,-1)); + game.getPlayer().setUpPressed(true); + break; + case S: +// game.getPlayer().setDirection(new Vector(0,1)); + game.getPlayer().setDownPressed(true); + break; + case A: +// game.getPlayer().setDirection(new Vector(-1,0)); + game.getPlayer().setLeftPressed(true); + break; + case D: +// game.getPlayer().setDirection(new Vector(1,0)); + game.getPlayer().setRightPressed(true); + break; + } + + } + + public void onKeyReleased(KeyEvent event){ + event.getCharacter(); +// System.out.println(); + switch(event.getCode()){ + case W: +// game.getPlayer().setDirection(new Vector(0,-1)); + game.getPlayer().setUpPressed(false); + break; + case S: +// game.getPlayer().setDirection(new Vector(0,1)); + game.getPlayer().setDownPressed(false); + break; + case A: +// game.getPlayer().setDirection(new Vector(-1,0)); + game.getPlayer().setLeftPressed(false); + break; + case D: +// game.getPlayer().setDirection(new Vector(1,0)); + game.getPlayer().setRightPressed(false); + break; + } + + } + + public void onKeyTyped(KeyEvent event) { + // TODO: 01.04.2022 Implement key actions + System.out.println(event); + String c=event.getCharacter(); +// System.out.println(); + switch(c){ + case "f": + Stage popup = pointsPopup.getPopup(5, scene); + popup.show(); + break; + } + } + + public void setGame(Game game){ + this.game=game; + } + public LocationModel getModel(){ + return model; + } + + public Scene getScene() { + return scene; + } +} diff --git a/src/main/java/io/rpg/gui/model/LocationModel.java b/src/main/java/io/rpg/gui/model/LocationModel.java new file mode 100644 index 00000000..aa0f05b5 --- /dev/null +++ b/src/main/java/io/rpg/gui/model/LocationModel.java @@ -0,0 +1,86 @@ +package io.rpg.gui.model; + +import io.rpg.gui.LocationController; +import io.rpg.model.GameObjectStandIn; +import io.rpg.model.*; +import io.rpg.model.GameObject; +import javafx.beans.property.ObjectProperty; +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.util.Pair; + +import java.util.HashMap; + +public class LocationModel { + static final int SCALE = 32; + private Game game; + private final ObjectProperty background; + // TODO: 01.04.2022 Think about a better name +// objects which we store on the playground +// mapNodes views to be displayed + private final ObservableList mapNodes; +// private + +// private HashMap gameObject2NodeMap; + private final LocationController controller; + + public LocationModel(ObjectProperty background, ObservableList mapNodes, LocationController controller) { + this.background = background; + this.mapNodes = mapNodes; +// this.gameObject2NodeMap = new HashMap<>(); + this.controller = controller; + } + + public LocationModel clear() { +// this.gameObject2NodeMap = new HashMap<>(); + mapNodes.clear(); + return this; + } + + public LocationModel setBackgroundImage(Image backgroundImage){ + background.setValue(backgroundImage); + return this; + } + + public LocationModel addMapObject(GameObject gameObject){ + ImageView imageView = new ImageView(gameObject.getImage()); +// gameObject + Vector mapPosition = getMapPosition(gameObject.getPosition()); +// Pair mapPosition = getMapPosition(gameObject.getPosition()); + imageView.setX(mapPosition.x); + imageView.setY(mapPosition.y); +// gameObject2NodeMap.put(gameObject, imageView); + gameObject.setImageView(imageView); + mapNodes.add(imageView); + imageView.setOnMouseClicked((e) -> onGameObjectAction(gameObject)); + return this; + } + + private void onGameObjectAction(GameObject source){ + // TODO: 01.04.2022 What to do when some GameObject was clicked + System.out.println(source); + } + + // TODO: 01.04.2022 Replace with Position + private Vector getMapPosition(Vector position){ + // size of one tile + return new Vector(position.x * SCALE, position.y * SCALE); +// return new Pair<>(position.x * scale, position.y * scale); + } + + public LocationModel setGame(Game game) { + this.game = game; + for(int i=0;i 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/gui/popups/PointsPopupController.java b/src/main/java/io/rpg/gui/popups/PointsPopupController.java new file mode 100644 index 00000000..a5d60352 --- /dev/null +++ b/src/main/java/io/rpg/gui/popups/PointsPopupController.java @@ -0,0 +1,28 @@ +package io.rpg.gui.popups; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.layout.*; +import javafx.util.Pair; + +public class PointsPopupController { + + @FXML private Label label; + @FXML private Pane background; + + protected void setPointsCount(int pointsCount) { + label.setText("Earned " + pointsCount + " points!"); + } + + protected Pair setBackgroundImage(String url){ + BackgroundImage backgroundImg= new BackgroundImage( + new Image(url), + BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, + BackgroundPosition.DEFAULT, + BackgroundSize.DEFAULT + ); + background.setBackground(new Background(backgroundImg)); + return new Pair<>(background.getPrefWidth(), background.getPrefHeight()); + } +} \ No newline at end of file diff --git a/src/main/java/io/rpg/model/Game.java b/src/main/java/io/rpg/model/Game.java new file mode 100644 index 00000000..b50acc30 --- /dev/null +++ b/src/main/java/io/rpg/model/Game.java @@ -0,0 +1,37 @@ +package io.rpg.model; + +import java.util.ArrayList; + +public class Game { + private Player player; +// Timer + ArrayList gameObjects; + + public Game(){ + 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/model/GameObject.java b/src/main/java/io/rpg/model/GameObject.java new file mode 100644 index 00000000..a9d5c3e9 --- /dev/null +++ b/src/main/java/io/rpg/model/GameObject.java @@ -0,0 +1,42 @@ +package io.rpg.model; + +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/model/GameObjectStandIn.java b/src/main/java/io/rpg/model/GameObjectStandIn.java new file mode 100644 index 00000000..ff7e92d2 --- /dev/null +++ b/src/main/java/io/rpg/model/GameObjectStandIn.java @@ -0,0 +1,26 @@ +package io.rpg.model; + +import javafx.scene.image.Image; +import javafx.util.Pair; + +/** + * A Temporary Class waiting to be replaced with GameObject class from branch RPG-79 + */ +public class GameObjectStandIn { + + private Pair position; + private Image image; + + public GameObjectStandIn(Pair position, Image image) { + this.position = position; + this.image = image; + } + + public Pair getPosition() { + return position; + } + + public Image getImage(){ + return image; + } +} diff --git a/src/main/java/io/rpg/model/GameWorld.java b/src/main/java/io/rpg/model/GameWorld.java new file mode 100644 index 00000000..bfc03791 --- /dev/null +++ b/src/main/java/io/rpg/model/GameWorld.java @@ -0,0 +1,4 @@ +package io.rpg.model; + +final public class GameWorld { +} diff --git a/src/main/java/io/rpg/model/GameWorldConfig.java b/src/main/java/io/rpg/model/GameWorldConfig.java new file mode 100644 index 00000000..53eade11 --- /dev/null +++ b/src/main/java/io/rpg/model/GameWorldConfig.java @@ -0,0 +1,32 @@ +package io.rpg.model; + +import java.util.ArrayList; +import java.util.List; + +public class GameWorldConfig { + private String tag; + + private ArrayList locations; + + // This class is not meant to be instantiated + // by hand. Only Gson should be able to do so + private GameWorldConfig() {} + + public String getTag() { + return tag; + } + + public List getLocations() { + return locations; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("\n{\n").append("\ttag: ").append(tag).append('\n'); + locations.forEach(location -> { + builder.append("\tlocation-tag: ").append(location).append('\n'); + }); + return builder.append("}\n").toString(); + } +} diff --git a/src/main/java/io/rpg/model/Item.java b/src/main/java/io/rpg/model/Item.java new file mode 100644 index 00000000..ee942f7f --- /dev/null +++ b/src/main/java/io/rpg/model/Item.java @@ -0,0 +1,19 @@ +package io.rpg.model; + +public class Item { + private int strength; + private final String name; + + public Item(int strength, String name) { + this.strength = strength; + this.name = name; + } + + public int strength() { + return strength; + } + + public String name() { + return name; + } +} \ No newline at end of file diff --git a/src/main/java/io/rpg/model/Player.java b/src/main/java/io/rpg/model/Player.java new file mode 100644 index 00000000..1b8d8eb5 --- /dev/null +++ b/src/main/java/io/rpg/model/Player.java @@ -0,0 +1,73 @@ +package io.rpg.model; + +import javafx.scene.image.Image; + +public class Player extends GameObject{ + + int strength; + float speed; + Vector direction; + boolean rightPressed; + boolean leftPressed; + boolean upPressed; + boolean downPressed; + + public Player(Vector position, Image image){ + super(position,image); + speed=5f; + direction=new Vector(0,0); + this.rightPressed=false; + this.leftPressed=false; + this.upPressed=false; + this.downPressed=false; + this.strength=0; + } + + public void updateStrength(int value){ + strength += value; + } + + public void setDirection(Vector direction) { + this.direction = direction; + } + + public void update(float elapsed){ + float y=0; + float x=0; +// the sum tells us the direction + if(upPressed) + y+=-1; + + if(downPressed) + y+=1; + + if(leftPressed) + x+=-1; + + if(rightPressed) + x+=1; + + + this.position=new Vector(this.position.x+speed*x*elapsed/1000,this.position.y+speed*y*elapsed/1000); + } + + public void setRightPressed(boolean rightPressed) { + this.rightPressed = rightPressed; + } + + public void setLeftPressed(boolean leftPressed) { + this.leftPressed = leftPressed; + } + + public void setUpPressed(boolean upPressed) { + this.upPressed = upPressed; + } + + public void setDownPressed(boolean downPressed) { + this.downPressed = downPressed; + } + + public void setStrength(int strength) { + this.strength = strength; + } +} diff --git a/src/main/java/io/rpg/model/Vector.java b/src/main/java/io/rpg/model/Vector.java new file mode 100644 index 00000000..55ba4f8b --- /dev/null +++ b/src/main/java/io/rpg/model/Vector.java @@ -0,0 +1,11 @@ +package io.rpg.model; + +public class Vector { + public final float x; + public final float y; + public Vector(float x,float y){ + this.x=x; + this.y=y; + } + +} diff --git a/src/main/java/io/rpg/model/data/Position.java b/src/main/java/io/rpg/model/data/Position.java new file mode 100644 index 00000000..40f2a1bb --- /dev/null +++ b/src/main/java/io/rpg/model/data/Position.java @@ -0,0 +1,41 @@ +package io.rpg.model.data; + +import java.util.Objects; + +// This class can NOT be record due to some issues +// with Gson library +public class Position { + public final int row; + + public final int col; + + public Position(int row, int col) { + this.row = row; + this.col = col; + } + + public int getRow() { + return row; + } + + public int getCol() { + return col; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof Position)){ + return false; + } else { + Position that = (Position) obj; + return this.row == that.row && this.col == that.col; + } + } + + @Override + public int hashCode() { + return Objects.hash(row, col); + } +} diff --git a/src/main/java/io/rpg/model/location/Location.java b/src/main/java/io/rpg/model/location/Location.java new file mode 100644 index 00000000..bea5b5aa --- /dev/null +++ b/src/main/java/io/rpg/model/location/Location.java @@ -0,0 +1,7 @@ +package io.rpg.model.location; + +/** + * Represents single location in our game + */ +public class Location { +} diff --git a/src/main/java/io/rpg/model/location/LocationConfig.java b/src/main/java/io/rpg/model/location/LocationConfig.java new file mode 100644 index 00000000..b7d8043a --- /dev/null +++ b/src/main/java/io/rpg/model/location/LocationConfig.java @@ -0,0 +1,46 @@ +package io.rpg.model.location; + + +import io.rpg.model.object.GameObjectConfig; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class LocationConfig { + @Nullable + private String tag; + + @NotNull + private ArrayList objects; + + @Nullable + private Path path; + + // This class is not meant to be instantiated + // by hand. Only Gson should be able to do so + private LocationConfig() { + objects = new ArrayList<>(); + } + + @Nullable + public String getTag() { + return tag; + } + + @NotNull + public List getObjects() { + return objects; + } + + @Nullable + public Path getPath() { + return path; + } + + public void setPath(@NotNull Path path) { + this.path = path; + } +} diff --git a/src/main/java/io/rpg/model/metadata/.gitkeep b/src/main/java/io/rpg/model/metadata/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/io/rpg/model/object/CollectibleGameObject.java b/src/main/java/io/rpg/model/object/CollectibleGameObject.java new file mode 100644 index 00000000..1cd8abf7 --- /dev/null +++ b/src/main/java/io/rpg/model/object/CollectibleGameObject.java @@ -0,0 +1,15 @@ +package io.rpg.model.object; + +import io.rpg.model.data.Position; +import org.jetbrains.annotations.NotNull; + +final public class CollectibleGameObject extends InteractiveGameObject { + public CollectibleGameObject(@NotNull String tag, @NotNull Position position) { + super(tag, position); + } + + @Override + public void onAction() { + + } +} diff --git a/src/main/java/io/rpg/model/object/DialogGameObject.java b/src/main/java/io/rpg/model/object/DialogGameObject.java new file mode 100644 index 00000000..b191ea8e --- /dev/null +++ b/src/main/java/io/rpg/model/object/DialogGameObject.java @@ -0,0 +1,15 @@ +package io.rpg.model.object; + +import io.rpg.model.data.Position; +import org.jetbrains.annotations.NotNull; + +final public class DialogGameObject extends InteractiveGameObject{ + public DialogGameObject(@NotNull String tag, @NotNull Position position) { + super(tag, position); + } + + @Override + public void onAction() { + + } +} diff --git a/src/main/java/io/rpg/model/object/GameObject.java b/src/main/java/io/rpg/model/object/GameObject.java new file mode 100644 index 00000000..ad6350da --- /dev/null +++ b/src/main/java/io/rpg/model/object/GameObject.java @@ -0,0 +1,47 @@ +package io.rpg.model.object; + +import io.rpg.model.data.Position; +import org.jetbrains.annotations.NotNull; + +/** + * Class representing common state properties for all + * objects appearing in the game + */ +public class GameObject { + /** + * Position of game object in model's representation of location + */ + @NotNull + private final Position position; + + /** + * Unique identifier of this game object + */ + @NotNull + private final String tag; + + /** + * Unique identifier of this game object + */ + @NotNull + public String getTag() { + return tag; + } + + /** + * Position of game object in model's representation of location + */ + @NotNull + public Position getPosition() { + return position; + } + + public void validate() { + // nothing to validate for now + } + + public GameObject(@NotNull String tag, @NotNull Position position) { + this.tag = tag; + this.position = position; + } +} diff --git a/src/main/java/io/rpg/model/object/GameObjectConfig.java b/src/main/java/io/rpg/model/object/GameObjectConfig.java new file mode 100644 index 00000000..ea0de3e6 --- /dev/null +++ b/src/main/java/io/rpg/model/object/GameObjectConfig.java @@ -0,0 +1,20 @@ +package io.rpg.model.object; + +import io.rpg.model.data.Position; +import org.jetbrains.annotations.NotNull; + +public class GameObjectConfig extends GameObject { + private String type; + + public GameObjectConfig(@NotNull String tag, @NotNull Position position) { + super(tag, position); + } + + @Override + public void validate() { + super.validate(); + if (!GameObjects.isValidType(type)) { + throw new IllegalStateException("Invalid object type: " + type); + } + } +} diff --git a/src/main/java/io/rpg/model/object/GameObjects.java b/src/main/java/io/rpg/model/object/GameObjects.java new file mode 100644 index 00000000..b0f05629 --- /dev/null +++ b/src/main/java/io/rpg/model/object/GameObjects.java @@ -0,0 +1,16 @@ +package io.rpg.model.object; + +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +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 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 new file mode 100644 index 00000000..8e73699a --- /dev/null +++ b/src/main/java/io/rpg/model/object/InteractiveGameObject.java @@ -0,0 +1,12 @@ +package io.rpg.model.object; + +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); + } + + 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 new file mode 100644 index 00000000..f98ee0e4 --- /dev/null +++ b/src/main/java/io/rpg/model/object/NavigationalGameObject.java @@ -0,0 +1,18 @@ +package io.rpg.model.object; + +import io.rpg.model.data.Position; +import org.jetbrains.annotations.NotNull; + +final public class NavigationalGameObject extends InteractiveGameObject { + public NavigationalGameObject(@NotNull String tag, + @NotNull Position position) { + super(tag, position); + } + + public void navigateTo(Object target){}; + + @Override + public void onAction() { + + } +} diff --git a/src/main/java/io/rpg/view/.gitkeep b/src/main/java/io/rpg/view/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 28297aa0..b613de50 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -7,7 +7,18 @@ requires com.dlsc.formsfx; requires org.kordamp.ikonli.javafx; requires org.kordamp.bootstrapfx.core; + requires com.google.gson; + requires org.jetbrains.annotations; + requires org.apache.logging.log4j; + requires org.apache.logging.log4j.core; opens io.rpg to javafx.fxml; + opens io.rpg.model to com.google.gson; + opens io.rpg.model.location to com.google.gson; + opens io.rpg.model.object to com.google.gson; + opens io.rpg.model.data to com.google.gson; + opens io.rpg.gui to javafx.fxml; + opens io.rpg.gui.popups to javafx.fxml; + exports io.rpg; -} \ No newline at end of file +} diff --git a/src/main/resources/io/rpg/gui/location-view.fxml b/src/main/resources/io/rpg/gui/location-view.fxml new file mode 100644 index 00000000..2e91c75d --- /dev/null +++ b/src/main/resources/io/rpg/gui/location-view.fxml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/io/rpg/gui/popups/points-earned-view.fxml b/src/main/resources/io/rpg/gui/popups/points-earned-view.fxml new file mode 100644 index 00000000..bbde17d2 --- /dev/null +++ b/src/main/resources/io/rpg/gui/popups/points-earned-view.fxml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/src/test/java/io/rpg/MockTest.java b/src/test/java/io/rpg/MockTest.java index 861e8168..c02dbf6a 100644 --- a/src/test/java/io/rpg/MockTest.java +++ b/src/test/java/io/rpg/MockTest.java @@ -1,9 +1,11 @@ package io.rpg; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class MockTest { @Test void mockTest() { + Assertions.assertTrue(true); } } diff --git a/src/test/java/io/rpg/config/ConfigLoaderTest.java b/src/test/java/io/rpg/config/ConfigLoaderTest.java new file mode 100644 index 00000000..228a26bf --- /dev/null +++ b/src/test/java/io/rpg/config/ConfigLoaderTest.java @@ -0,0 +1,81 @@ +package io.rpg.config; + +import io.rpg.model.GameWorldConfig; + +import io.rpg.model.data.Position; +import io.rpg.model.location.Location; +import io.rpg.model.location.LocationConfig; +import io.rpg.model.object.GameObject; +import io.rpg.model.object.GameObjectConfig; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.FileNotFoundException; +import java.util.List; + +public class ConfigLoaderTest { + private static final String CFG_DIR_PATH = "configurations/unit-test-configuration"; + + @Test + public void ConfigLoaderDoesThrowWithBadDirPath() { + String notExistingPath = "/kokoko/xd/oko"; + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + ConfigLoader configLoader = new ConfigLoader(notExistingPath); + }); + } + + @Test + public void ConfigLoaderDoesNotThrowWithGoodDirPath() { + Assertions.assertDoesNotThrow(() -> { + ConfigLoader configLoader = new ConfigLoader(CFG_DIR_PATH); + }); + } + + @Test + public void GameWorldConfigIsLoadedProperly() throws FileNotFoundException { + List expectedLocationNames = List.of("location-1", "location-2"); + String testTag = "test-tag"; + + ConfigLoader configLoader = new ConfigLoader(CFG_DIR_PATH); + GameWorldConfig config = configLoader.loadGameWorldConfig(); + + Assertions.assertEquals(testTag, config.getTag()); + + List actualLocationNames = config.getLocations(); + Assertions.assertEquals(expectedLocationNames, actualLocationNames); + } + +// @Test +// public void LocationConfigIsLoadedProperly() throws FileNotFoundException { +// ConfigLoader configLoader = new ConfigLoader(CFG_DIR_PATH); +// String locationTag = "location-1"; +// +// Assertions.assertDoesNotThrow(() -> { +// configLoader.loadLocation(locationTag); +// }); +// +// LocationConfig config = configLoader.loadLocation(locationTag); +// Assertions.assertNotNull(config); +// +// Assertions.assertEquals(locationTag, config.getTag()); +// +// GameObjectConfig expectedGameObject1 = new GameObjectConfig("object-1", new Position(0, 5)); +// GameObject expectedGameObject2 = new GameObject("object-2", new Position(1, 3)); +// +// // this test relies on order of deserialization in Gson implementation +// // todo: find a better way to test this +// +// List actualGameObjects = config.getObjects(); +// +// Assertions.assertEquals(2, actualGameObjects.size()); +// +// GameObject actualGameObject1 = actualGameObjects.get(0); +// GameObject actualGameObject2 = actualGameObjects.get(1); +// +// Assertions.assertEquals(expectedGameObject1.getTag(), actualGameObject1.getTag()); +// Assertions.assertEquals(expectedGameObject2.getTag(), actualGameObject2.getTag()); +// Assertions.assertEquals(expectedGameObject1.getPosition(), actualGameObject1.getPosition()); +// Assertions.assertEquals(expectedGameObject2.getPosition(), actualGameObject2.getPosition()); +// } +}