diff --git a/build.gradle b/build.gradle index 78531794..84a83bc2 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,8 @@ 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.+") } test { diff --git a/src/main/java/io/rpg/Game.java b/src/main/java/io/rpg/Game.java index 268cd528..5939cd3d 100644 --- a/src/main/java/io/rpg/Game.java +++ b/src/main/java/io/rpg/Game.java @@ -2,7 +2,6 @@ import io.rpg.controller.Controller; import io.rpg.model.actions.Action; -import javafx.scene.Scene; import javafx.stage.Stage; public class Game { @@ -20,7 +19,7 @@ public void setController(Controller controller) { public void start(Stage stage) { stage.show(); controller.setMainStage(stage); - controller.onAction(onStart); + controller.consumeAction(onStart); } public static class Builder { diff --git a/src/main/java/io/rpg/controller/Controller.java b/src/main/java/io/rpg/controller/Controller.java index 551c3395..df5dd70f 100644 --- a/src/main/java/io/rpg/controller/Controller.java +++ b/src/main/java/io/rpg/controller/Controller.java @@ -1,6 +1,7 @@ package io.rpg.controller; import io.rpg.model.actions.Action; +import io.rpg.model.actions.ActionConsumer; import io.rpg.model.actions.LocationChangeAction; import io.rpg.model.data.KeyboardEvent; import io.rpg.model.data.MouseClickedEvent; @@ -12,10 +13,10 @@ import io.rpg.view.LocationView; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.function.Supplier; import javafx.geometry.Point2D; import javafx.scene.Scene; import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; import javafx.stage.Stage; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -24,7 +25,7 @@ import java.util.LinkedHashMap; import java.util.List; -public class Controller implements KeyboardEvent.Observer, MouseClickedEvent.Observer { +public class Controller implements KeyboardEvent.Observer, MouseClickedEvent.Observer, ActionConsumer { private Scene currentView; private LinkedHashMap tagToLocationModelMap; private LocationModel currentModel; @@ -68,7 +69,8 @@ public void setView(Scene currentView) { this.currentView = currentView; } - public void onAction(Action action) { + @Override + public void consumeAction(Action action) { Class[] args = {action.getClass()}; Method onSpecificAction; @@ -146,7 +148,7 @@ public void onKeyboardEvent(KeyboardEvent event) { case F -> popupController.openPointsPopup(5, getWindowCenterX(), getWindowCenterY()); case G -> popupController.openTextPopup("Hello!", getWindowCenterX(), getWindowCenterY()); case Q -> popupController.openQuestionPopup(new Question("How many bits are there in one byte?", new String[]{"1/8", "1024", "8", "256"}, 'C'), getWindowCenterX(), getWindowCenterY()); - case L -> onAction((Action) new LocationChangeAction("location-2", new Position(1, 2))); + case L -> consumeAction((Action) new LocationChangeAction("location-2", new Position(1, 2))); } } // } else if (payload.getEventType() == KeyEvent.KEY_RELEASED) { @@ -171,15 +173,13 @@ public void onMouseClickedEvent(MouseClickedEvent event) { .orElseThrow(() -> new RuntimeException("No object present at position " + position)); double distance = playerPos.distance(objectView.getPosition()); - if (distance < 1.5) { - if (object instanceof InteractiveGameObject) { - ((InteractiveGameObject) object).onAction(); - } - if (object instanceof CollectibleGameObject) { - popupController.openTextImagePopup("Picked up an item!", objectView.getImage(), getWindowCenterX(), getWindowCenterY()); - objectView.setVisible(false); - currentModel.removeGameObject(object); + if (distance < 1.5) { + MouseButton button = event.payload().getButton(); + if (button.equals(MouseButton.PRIMARY)) { + object.onLeftClick(); + } else if (button.equals(MouseButton.SECONDARY)) { + object.onRightClick(); } } @@ -190,6 +190,7 @@ public PlayerController getPlayerController() { return playerController; } + public static class Builder { private final Controller controller; @@ -203,6 +204,9 @@ public Controller build() { throw new IllegalStateException(validationResult.getErrorValue()); } + controller.tagToLocationModelMap.values().forEach(location -> location.setActionConsumer(controller)); + controller.playerController.getPlayer().setActionConsumer(controller); + return controller; } @@ -228,7 +232,4 @@ public void setPlayerController(PlayerController playerController) { controller.playerController = playerController; } } - public LocationModel getCurrentModel() { - return currentModel; - } } diff --git a/src/main/java/io/rpg/model/actions/Action.java b/src/main/java/io/rpg/model/actions/Action.java index 3b13b7d0..db9fdc09 100644 --- a/src/main/java/io/rpg/model/actions/Action.java +++ b/src/main/java/io/rpg/model/actions/Action.java @@ -4,4 +4,5 @@ * A marker interface for action classes. */ public interface Action { + Action VOID = new Action() {}; } diff --git a/src/main/java/io/rpg/model/actions/ActionConsumer.java b/src/main/java/io/rpg/model/actions/ActionConsumer.java new file mode 100644 index 00000000..991b7c6f --- /dev/null +++ b/src/main/java/io/rpg/model/actions/ActionConsumer.java @@ -0,0 +1,5 @@ +package io.rpg.model.actions; + +public interface ActionConsumer { + void consumeAction(Action action); +} diff --git a/src/main/java/io/rpg/model/actions/ActionEmitter.java b/src/main/java/io/rpg/model/actions/ActionEmitter.java new file mode 100644 index 00000000..e7db6837 --- /dev/null +++ b/src/main/java/io/rpg/model/actions/ActionEmitter.java @@ -0,0 +1,5 @@ +package io.rpg.model.actions; + +public interface ActionEmitter { + void setActionConsumer(ActionConsumer actionConsumer); +} diff --git a/src/main/java/io/rpg/model/actions/BaseActionEmitter.java b/src/main/java/io/rpg/model/actions/BaseActionEmitter.java new file mode 100644 index 00000000..f7364801 --- /dev/null +++ b/src/main/java/io/rpg/model/actions/BaseActionEmitter.java @@ -0,0 +1,14 @@ +package io.rpg.model.actions; + +public abstract class BaseActionEmitter implements ActionEmitter { + private ActionConsumer consumer = (a) -> {}; + + protected void emitAction(Action action) { + consumer.consumeAction(action); + } + + @Override + public void setActionConsumer(ActionConsumer actionConsumer) { + this.consumer = actionConsumer; + } +} diff --git a/src/main/java/io/rpg/model/location/LocationModel.java b/src/main/java/io/rpg/model/location/LocationModel.java index 73237b17..0a58fde2 100644 --- a/src/main/java/io/rpg/model/location/LocationModel.java +++ b/src/main/java/io/rpg/model/location/LocationModel.java @@ -1,5 +1,7 @@ package io.rpg.model.location; +import io.rpg.model.actions.ActionConsumer; +import io.rpg.model.actions.BaseActionEmitter; import io.rpg.model.data.LocationModelStateChange; import io.rpg.model.data.Position; import io.rpg.model.object.GameObject; @@ -18,15 +20,15 @@ /** * Represents single location in our game. */ -public class LocationModel implements LocationModelStateChange.Emitter { +public class LocationModel extends BaseActionEmitter implements LocationModelStateChange.Emitter { private String tag; private List gameObjects; private final HashMap> positionListeners; private final HashMap positionGameObjectMap; public final Point2D bounds; - private final Set locationModelStateChangeObservers; + public LocationModel(@NotNull String tag, @NotNull List gameObjects) { this(); this.tag = tag; @@ -158,6 +160,11 @@ public void emitLocationModelStateChange(LocationModelStateChange event) { }); } + @Override + public void setActionConsumer(ActionConsumer actionConsumer) { + super.setActionConsumer(actionConsumer); + gameObjects.forEach((g) -> g.setActionConsumer(actionConsumer)); + } public Result validate() { if (tag == null || gameObjects == null) { diff --git a/src/main/java/io/rpg/model/object/GameObject.java b/src/main/java/io/rpg/model/object/GameObject.java index 6aff9210..b4b4f4d3 100644 --- a/src/main/java/io/rpg/model/object/GameObject.java +++ b/src/main/java/io/rpg/model/object/GameObject.java @@ -1,12 +1,13 @@ package io.rpg.model.object; +import io.rpg.model.actions.Action; +import io.rpg.model.actions.BaseActionEmitter; import io.rpg.model.data.GameObjectStateChange; import io.rpg.model.data.Position; import java.lang.reflect.Field; import java.util.LinkedHashSet; import java.util.Optional; import java.util.Set; -import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.geometry.Point2D; @@ -17,12 +18,14 @@ * Class representing common state properties for all * objects appearing in the game. */ -public class GameObject implements GameObjectStateChange.Emitter { +public class GameObject extends BaseActionEmitter implements GameObjectStateChange.Emitter { /** * Position of game object in model's representation of location. */ private final SimpleObjectProperty exactPositionProperty; + private Action onRightClickAction; + private Action onLeftClickAction; /** * Unique identifier of this game object. @@ -47,6 +50,8 @@ public GameObject(@NotNull String tag, @NotNull Position position) { this.tag = tag; this.stateChangeObservers = new LinkedHashSet<>(); this.exactPositionProperty = new SimpleObjectProperty<>(new Point2D(position.col, position.row)); + this.onLeftClickAction = Action.VOID; + this.onRightClickAction = Action.VOID; } /** @@ -117,7 +122,21 @@ public void setPosition(Position playerPosition) { setExactPosition(new Point2D(playerPosition.col, playerPosition.row)); } + public void setOnRightClickAction(Action onRightClickAction) { + this.onRightClickAction = onRightClickAction; + } + + public void setOnLeftClickAction(Action onLeftClickAction) { + this.onLeftClickAction = onLeftClickAction; + } + public void onRightClick() { + emitAction(onRightClickAction); + } + + public void onLeftClick() { + emitAction(onLeftClickAction); + } public enum Type { NAVIGABLE("navigable"), diff --git a/src/test/java/io/rpg/model/location/LocationModelTest.java b/src/test/java/io/rpg/model/location/LocationModelTest.java new file mode 100644 index 00000000..a3432260 --- /dev/null +++ b/src/test/java/io/rpg/model/location/LocationModelTest.java @@ -0,0 +1,47 @@ +package io.rpg.model.location; + +import io.rpg.model.actions.Action; +import io.rpg.model.actions.ActionConsumer; +import io.rpg.model.data.Position; +import io.rpg.model.object.GameObject; +import java.util.ArrayList; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class LocationModelTest { + + private LocationModel locationModel; + + @BeforeEach + void setUp() { + LocationModel.Builder builder = new LocationModel.Builder(); + locationModel = builder + .setGameObjects(new ArrayList<>()) + .setTag("test") + .build(); + } + + + @Test + void whenCallingSetActionConsumer_shouldSetActionConsumerForGameObjects() { + GameObject object1 = new GameObject("obj1", new Position(1,1)); + GameObject object2 = new GameObject("obj2", new Position(2,1)); + + locationModel.addGameObject(object1); + locationModel.addGameObject(object2); + + ActionConsumer consumer = Mockito.mock(ActionConsumer.class); + locationModel.setActionConsumer(consumer); + + Action action = Action.VOID; + + object1.setOnRightClickAction(action); + object1.onRightClick(); + Mockito.verify(consumer, Mockito.times(1)).consumeAction(action); + + object2.setOnRightClickAction(action); + object2.onRightClick(); + Mockito.verify(consumer, Mockito.times(2)).consumeAction(action); + } +} \ No newline at end of file