diff --git a/build.gradle b/build.gradle index f6db9524..2fb76f5c 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}") testImplementation("org.junit.jupiter:junit-jupiter-params:${junitVersion}") - testImplementation("org.mockito:mockito-core:3.+") + testImplementation("org.mockito:mockito-core:4.5.1") } test { diff --git a/src/main/java/io/rpg/Game.java b/src/main/java/io/rpg/Game.java index 5939cd3d..cfadf33e 100644 --- a/src/main/java/io/rpg/Game.java +++ b/src/main/java/io/rpg/Game.java @@ -2,24 +2,56 @@ import io.rpg.controller.Controller; import io.rpg.model.actions.Action; +import javafx.animation.AnimationTimer; import javafx.stage.Stage; public class Game { private Controller controller; private Action onStart; + private AnimationTimer timer; + private Runnable onEnd; private Game() { - + onEnd = () -> {}; } public void setController(Controller controller) { this.controller = controller; + controller.getGameEndController().setOnEnd(this::end); } public void start(Stage stage) { - stage.show(); controller.setMainStage(stage); controller.consumeAction(onStart); + startAnimationTimer(); + stage.show(); + } + + public void end() { + timer.stop(); + onEnd.run(); + } + + public void setOnEnd(Runnable onEnd) { + this.onEnd = onEnd; + } + + private void startAnimationTimer() { + timer = new AnimationTimer() { + long lastUpdate = -1; + @Override + public void handle(long now) { + if (lastUpdate != -1) { + float difference = (now - lastUpdate) / 1e6f; + + getController().getPlayerController() + .getPlayer() + .update(difference); + } + lastUpdate = now; + } + }; + timer.start(); } public static class Builder { diff --git a/src/main/java/io/rpg/Initializer.java b/src/main/java/io/rpg/Initializer.java index 52673716..374c7aa3 100644 --- a/src/main/java/io/rpg/Initializer.java +++ b/src/main/java/io/rpg/Initializer.java @@ -2,12 +2,11 @@ import com.kkafara.rt.Result; import io.rpg.config.ConfigLoader; -import io.rpg.controller.Controller; import io.rpg.config.model.GameWorldConfig; import io.rpg.config.model.LocationConfig; +import io.rpg.controller.Controller; import io.rpg.controller.PlayerController; import io.rpg.model.actions.LocationChangeAction; -import io.rpg.model.data.MapDirection; import io.rpg.model.location.LocationModel; import io.rpg.model.object.GameObject; import io.rpg.model.object.Player; @@ -19,29 +18,24 @@ import io.rpg.view.popups.QuestionPopup; import io.rpg.view.popups.TextImagePopup; import io.rpg.view.popups.TextPopup; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Iterator; +import java.util.List; import javafx.geometry.Point2D; -import javafx.stage.Stage; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.IOException; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; - public class Initializer { private Path pathToConfigDir; private ConfigLoader configLoader; - private final Stage mainStage; private final Logger logger; - public Initializer(@NotNull String pathToConfigDir, @NotNull Stage mainStage) { + public Initializer(@NotNull String pathToConfigDir) { this.configLoader = new ConfigLoader(pathToConfigDir); - this.mainStage = mainStage; this.logger = LogManager.getLogger(Initializer.class); } diff --git a/src/main/java/io/rpg/Main.java b/src/main/java/io/rpg/Main.java index 965f954a..4dfc0c4c 100644 --- a/src/main/java/io/rpg/Main.java +++ b/src/main/java/io/rpg/Main.java @@ -1,7 +1,8 @@ package io.rpg; import com.kkafara.rt.Result; -import javafx.animation.AnimationTimer; +import io.rpg.wrapper.WrapperController; +import java.io.IOException; import javafx.application.Application; import javafx.stage.Stage; import org.apache.logging.log4j.Level; @@ -9,15 +10,27 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; -import java.io.IOException; - +/** + * Entry point of the app. + */ public class Main extends Application { + + private final Logger logger = LogManager.getLogger(Main.class); + @Override public void start(Stage stage) throws IOException { Configurator.setRootLevel(Level.DEBUG); - Logger logger = LogManager.getLogger(Main.class); + String path = getParameters().getNamed().get("config"); + if (path == null) { + WrapperController wrapperController = WrapperController.load(); + wrapperController.show(stage); + } else { + fastStart(path, stage); + } + } - Initializer worldInitializer = new Initializer("configurations/demo-config-1", stage); + private void fastStart(String path, Stage stage) { + Initializer worldInitializer = new Initializer(path); Result initializationResult = worldInitializer.initialize(); if (initializationResult.isErr()) { @@ -36,24 +49,8 @@ public void start(Stage stage) throws IOException { return; } - // TODO: 04.05.2022 Null check for game was already made but IDE still screams Game game = initializationResult.getOk(); game.start(stage); - - AnimationTimer animationTimer = new AnimationTimer() { - long lastUpdate = -1; - - @Override - public void handle(long now) { - if (lastUpdate != -1) { - float difference = (now - lastUpdate) / 1e6f; - - game.getController().getPlayerController().getPlayer().update(difference); - } - lastUpdate = now; - } - }; - animationTimer.start(); } public static void main(String[] args) { diff --git a/src/main/java/io/rpg/controller/Controller.java b/src/main/java/io/rpg/controller/Controller.java index 3d7751f2..ca9bccef 100644 --- a/src/main/java/io/rpg/controller/Controller.java +++ b/src/main/java/io/rpg/controller/Controller.java @@ -34,6 +34,7 @@ public class Controller implements KeyboardEvent.Observer, MouseClickedEvent.Obs private Logger logger; private final PopupController popupController = new PopupController(); private PlayerController playerController; + private GameEndController gameEndController; private Stage mainStage; @@ -42,6 +43,7 @@ public Controller() { tagToLocationModelMap = new LinkedHashMap<>(); tagToLocationViewMap = new LinkedHashMap<>(); + gameEndController = new GameEndController(); } public Controller(LinkedHashMap tagToLocationModelMap, @@ -154,13 +156,8 @@ public void acceptQuizResult(boolean correct, int pointsCount) { } private void onAction(GameEndAction action) { - GameEndView view = GameEndView.load(); - view.setDescription(action.description); - double prevWidth = mainStage.getWidth(); - double prevHeight = mainStage.getHeight(); - mainStage.setScene(view); - mainStage.setWidth(prevWidth); - mainStage.setHeight(prevHeight); + gameEndController.showGameEnd(mainStage, action.description); + } private void onAction(BattleAction action) { @@ -266,6 +263,9 @@ public PlayerController getPlayerController() { return playerController; } + public GameEndController getGameEndController() { + return gameEndController; + } public static class Builder { private final Controller controller; diff --git a/src/main/java/io/rpg/controller/GameEndController.java b/src/main/java/io/rpg/controller/GameEndController.java new file mode 100644 index 00000000..67806e11 --- /dev/null +++ b/src/main/java/io/rpg/controller/GameEndController.java @@ -0,0 +1,35 @@ +package io.rpg.controller; + +import io.rpg.view.GameEndView; +import javafx.event.EventType; +import javafx.scene.input.InputEvent; +import javafx.scene.input.MouseEvent; +import javafx.stage.Stage; + +public class GameEndController { + private final GameEndView view; + private Runnable onEnd; + + public GameEndController() { + view = GameEndView.load(); + onEnd = () -> {}; + view.setOnClick(this::end); + } + + public void showGameEnd(Stage stage, String description) { + view.setDescription(description); + double prevWidth = stage.getWidth(); + double prevHeight = stage.getHeight(); + stage.setScene(view); + stage.setWidth(prevWidth); + stage.setHeight(prevHeight); + } + + public void setOnEnd(Runnable onEnd) { + this.onEnd = onEnd; + } + + private void end() { + onEnd.run(); + } +} diff --git a/src/main/java/io/rpg/view/GameEndView.java b/src/main/java/io/rpg/view/GameEndView.java index ed68725d..93821aa3 100644 --- a/src/main/java/io/rpg/view/GameEndView.java +++ b/src/main/java/io/rpg/view/GameEndView.java @@ -27,4 +27,8 @@ public static GameEndView load() { public void setDescription(String description) { viewModel.setDescription(description); } + + public void setOnClick(Runnable runnable) { + viewModel.getParent().setOnMouseClicked((e) -> runnable.run()); + } } diff --git a/src/main/java/io/rpg/wrapper/ConfigChooser.java b/src/main/java/io/rpg/wrapper/ConfigChooser.java new file mode 100644 index 00000000..aabf3242 --- /dev/null +++ b/src/main/java/io/rpg/wrapper/ConfigChooser.java @@ -0,0 +1,34 @@ +package io.rpg.wrapper; + +import java.io.File; +import java.util.Optional; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +public class ConfigChooser { + private final FileChooser chooser; + + public ConfigChooser() { + chooser = new FileChooser(); + chooser.setTitle("Select root.json"); + chooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("config", "root.json"), + new FileChooser.ExtensionFilter("json", "*.json") + ); + } + + /** + * Open file selection window. + + * @param stage - main stage. + * @return Optional of selected file folder path. + */ + public Optional open(Stage stage) { + File file = chooser.showOpenDialog(stage); + if (file == null) { + return Optional.empty(); + } + + return Optional.of(file.getParentFile().getAbsolutePath()); + } +} diff --git a/src/main/java/io/rpg/wrapper/WrapperController.java b/src/main/java/io/rpg/wrapper/WrapperController.java new file mode 100644 index 00000000..3bb9d962 --- /dev/null +++ b/src/main/java/io/rpg/wrapper/WrapperController.java @@ -0,0 +1,147 @@ +package io.rpg.wrapper; + +import com.kkafara.rt.Result; +import io.rpg.Game; +import io.rpg.Initializer; +import io.rpg.Main; +import java.io.IOException; +import java.util.Optional; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.TextArea; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Adds start and load screens. + */ +public class WrapperController { + private final Logger logger = LogManager.getLogger(WrapperController.class); + @FXML private BorderPane mainPane; + @FXML private TextArea outputArea; + @FXML private Button startButton; + private Stage stage; + private Scene scene; + private Parent startView; + private Parent loadView; + + private final ConfigChooser chooser; + private Game game; + + public WrapperController() { + chooser = new ConfigChooser(); + } + + public static WrapperController load() throws IOException { + FXMLLoader loader = new FXMLLoader(WrapperController.class.getResource("main-view.fxml")); + loader.load(); + WrapperController controller = loader.getController(); + + controller.loadStartView(); + controller.loadLoadView(); + + controller.createScene(); + return controller; + } + + private void loadStartView() throws IOException { + FXMLLoader loader = new FXMLLoader(WrapperController.class.getResource("start-view.fxml")); + loader.setController(this); + this.startView = loader.load(); + } + + private void loadLoadView() throws IOException { + FXMLLoader loader = new FXMLLoader(WrapperController.class.getResource("load-view.fxml")); + loader.setController(this); + this.loadView = loader.load(); + } + + private void createScene() { + scene = new Scene(mainPane); + } + + public void show(Stage stage) { + this.stage = stage; + showView(startView); + stage.setScene(scene); + stage.show(); + } + + private void showView(Parent view) { + mainPane.setCenter(view); + } + + @FXML + private void onBeginClick() { + showView(loadView); + } + + @FXML + private void onExitClick() { + stage.close(); + } + + @FXML + private void onBackClick() { + showView(startView); + } + + @FXML + private void onLoadClick() { + Optional pathOptional = this.chooser.open(stage); + if (pathOptional.isEmpty()) { + printLine("No file was selected"); + startButton.setDisable(true); + return; + } + + String path = pathOptional.get(); + printLine("Selected: " + path); + startButton.setDisable(false); + + loadGame(path) + .ifOk(g -> g.setOnEnd(() -> show(stage))); + } + + private Result loadGame(String path) { + Initializer worldInitializer = new Initializer(path); + Result initializationResult = worldInitializer.initialize(); + + if (initializationResult.isErr()) { + logger.error("Initialization error"); + printLine("Initialization error"); + + initializationResult.getErrOpt().ifPresentOrElse( + ex -> { + logger.error(ex.getMessage()); + printLine(ex.getMessage()); + ex.printStackTrace(); + }, + () -> printLine("No reason provided") + ); + return Result.err(); + } else if (initializationResult.isOkValueNull()) { + printLine("Initialization returned null value"); + return Result.err(); + } else { + printLine("File was correctly loaded. Press START to begin game"); + } + + return Result.ok(game); + } + + @FXML + private void onStartClick() { + startButton.setDisable(true); + game.start(stage); + } + + private void printLine(String line) { + outputArea.setText(outputArea.getText() + "\n" + line); + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 62a873ff..c3817e5c 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -20,6 +20,7 @@ opens io.rpg.model.actions to com.google.gson; opens io.rpg.viewmodel to javafx.fxml; + opens io.rpg.wrapper to javafx.fxml; opens io.rpg.view to javafx.fxml; opens io.rpg.config.model to com.google.gson; diff --git a/src/main/resources/css/styles.css b/src/main/resources/css/styles.css index 272bb6a7..a955fdf0 100644 --- a/src/main/resources/css/styles.css +++ b/src/main/resources/css/styles.css @@ -4,5 +4,46 @@ .answer_button:hover { -fx-border-color: white; - -fx-border-width: 5px; + -fx-border-width: 2; +} + +.menu_button { + -fx-background-color: #3d3d3d; + -fx-text-fill: white; + -fx-pref-height: 60; + -fx-font-family: "Sitka Small"; + -fx-wrap-text: true; + -fx-border-width: 2; + -fx-border-color: #6c6c6c; +} + +.small_button { + -fx-pref-width: 100; + -fx-max-width: 100; + -fx-font-size: 11; + -fx-pref-height: 40; +} + +.big_button { + -fx-pref-width: 150; + -fx-max-width: 200; + -fx-pref-height: 60; + -fx-font-size: 26; +} + +.menu_textarea { + -fx-text-fill: white; +} +.menu_textarea .content{ + -fx-background-color: #3d3d3d; + -fx-text-fill: white; + -fx-font-family: "Sitka Small"; + -fx-wrap-text: true; + -fx-border-width: 2; + -fx-border-color: #6c6c6c; +} + +.menu_button:hover { + -fx-border-color: #8c8c8c; + -fx-effect: dropshadow(three-pass-box, rgba(255, 255, 255, 0.8), 10, 0, 0, 0); } \ No newline at end of file diff --git a/src/main/resources/io/rpg/wrapper/load-view.fxml b/src/main/resources/io/rpg/wrapper/load-view.fxml new file mode 100644 index 00000000..5141f094 --- /dev/null +++ b/src/main/resources/io/rpg/wrapper/load-view.fxml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + +