Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: allow for before & after actions configuration #79

Merged
merged 14 commits into from
Jun 4, 2022
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
{
"tag": "key",
"onLeftClick": {"tag": "dialogue-action", "type": "dialogue", "statements": ["A key.,A, keyA, key.A, key.A, key.A, key.A, key.A, key.A key."], "assetPath": "assets/key.png"}
"onLeftClick": {
"tag": "dialogue-action",
"type": "dialogue",
"statements": ["A key.,A, keyA, key.A, key.A, key.A, key.A, key.A, key.A key."],
"assetPath": "assets/key.png",
"before": {
"tag": "message",
"type": "show-description",
"description": "Hurra, you are about to pickup a key!",
"assetPath": "assets/key.png"
},
"after": {
"tag": "followup-message",
"type": "show-description",
"description": "Hurra, the key was picked up!",
"assetPath": "assets/key.png"
}
}
}
6 changes: 3 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ following props:
* `locations` - List of location tags. All the locations specified in `locations` are loaded, however this field is left
for backward-compatibility.
* `player` - Player object specification. It follows the `GameObject` specification
rules. (TODO: add hyperlink to that section)
rules. See [game object configuration][#game-object-configuration]
* `rootLocation` - `tag` of first-to-be-displayed location.
* `textPopupBackground` - Url of a picture to be used as a background for all TextPopups
* `textPopupButton` - Url of a picture to be used as a button image for all TextPopups
Expand Down Expand Up @@ -181,9 +181,9 @@ common part first. Each action consists of following properties
* *(optional)* `condition` - Condition configuration. The action won't be executed until the condition is satisfied.
Aliases: `requires`. See [condition configuration](#condition-configuration)
* *(optional)* `before` - Configuration of action to be triggered before the proper action is executed.
e.g. you may want to change location before starting the dialogue. (TODO: Logic is not implemented as for now).
e.g. you may want to change location before starting the dialogue.
* *(optional)* `after` - Configuration of action to be triggered after the proper action is executed, e.g. you may want
give player some output or item. (TODO: Logic is not implemented as for now).
give player some output or item.

Let's look at specific action types:

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/io/rpg/config/model/ActionConfigBundle.java
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,20 @@ public Result<Void, Exception> validate() {
return result;
}

if (beforeAction != null) {
result = beforeAction.validate();
if (result.isErr()) {
return result;
}
}

if (afterAction != null) {
result = afterAction.validate();
if (result.isErr()) {
return result;
}
}

// actionType can not be null here, guaranteed by validateBasic method
//noinspection ConstantConditions
switch (actionType) {
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/io/rpg/config/model/GameObjectConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ public Result<Void, Exception> validate() {
builder.append("No position provided");
}

if (onRightClick != null) {
onRightClick.validate().ifErr(err -> builder.combine(err.getMessage()));
}
if (onLeftClick != null) {
onLeftClick.validate().ifErr(err -> builder.combine(err.getMessage()));
}
if (onApproach != null) {
onApproach.validate().ifErr(err -> builder.combine(err.getMessage()));
}

return builder.isEmpty() ? Result.ok() : Result.err(new Exception(builder.toString()));
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/io/rpg/controller/Controller.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ public void consumeAction(Action action) {
return;
}

logger.info("Consuming action");

action.acceptActionEngine(actionEngine);
}

Expand Down
60 changes: 38 additions & 22 deletions src/main/java/io/rpg/controller/PopupController.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,33 @@ public PopupController() {
});
}

public void openTextPopup(String text, int x, int y){
public void openTextPopup(String text, int x, int y) {
TextPopup popupScene = new TextPopup(text);
popupStage.setScene(popupScene);

popupStage.show();

popupStage.setX(x - popupScene.getWidth() / 2);
popupStage.setY(y - popupScene.getHeight() / 2);

popupScene.setButtonCallback(event -> popupStage.hide());

popupStage.onShownProperty().setValue(event -> {
popupStage.setX(x - popupScene.getWidth() / 2);
popupStage.setY(y - popupScene.getHeight() / 2);
});
if (!popupStage.isShowing()) {
popupStage.showAndWait();
}
}

public void openTextImagePopup(String text, Image image, int x, int y){
public void openTextImagePopup(String text, Image image, int x, int y) {
TextImagePopup popupScene = new TextImagePopup(text, image);
popupStage.setScene(popupScene);

popupStage.show();

popupStage.setX(x - popupScene.getWidth() / 2);
popupStage.setY(y - popupScene.getHeight() / 2);

popupScene.setButtonCallback(event -> popupStage.hide());
popupStage.onShownProperty().setValue(event -> {
popupStage.setX(x - popupScene.getWidth() / 2);
popupStage.setY(y - popupScene.getHeight() / 2);
});
if (!popupStage.isShowing()) {
popupStage.showAndWait();
}
}

public void openPointsPopup(int pointsCount, int x, int y) {
Expand All @@ -56,29 +61,40 @@ public void openQuestionPopup(Question question, int x, int y, Runnable successC
popupScene.setSuccessCallback(successCallback);
popupScene.setFailureCallback(failureCallback);
popupStage.setScene(popupScene);
popupStage.show();
popupStage.setX(x - popupScene.getWidth() / 2);
popupStage.setY(y - popupScene.getHeight() / 2);
popupStage.onShownProperty().setValue(event -> {
popupStage.setX(x - popupScene.getWidth() / 2);
popupStage.setY(y - popupScene.getHeight() / 2);
});
if (!popupStage.isShowing()) {
popupStage.showAndWait();
}
}

public void openQuestionPopup(Question question, int x, int y) {
QuestionPopup popupScene = new QuestionPopup(question);
popupStage.setScene(popupScene);
popupStage.show();
popupStage.setX(x - popupScene.getWidth() / 2);
popupStage.setY(y - popupScene.getHeight() / 2);
popupStage.onShownProperty().setValue(event -> {
popupStage.setX(x - popupScene.getWidth() / 2);
popupStage.setY(y - popupScene.getHeight() / 2);
});
if (!popupStage.isShowing()) {
popupStage.showAndWait();
}
}

public void openDialoguePopup(String text, Image npcImage, int x, int y) {
DialoguePopup popupScene = new DialoguePopup(text, npcImage);
popupStage.setScene(popupScene);

popupStage.show();

popupStage.setX(x - popupScene.getWidth() / 2);
popupStage.setY(y - popupScene.getHeight() / 2);
popupStage.onShownProperty().setValue(event -> {
popupStage.setX(x - popupScene.getWidth() / 2);
popupStage.setY(y - popupScene.getHeight() / 2);
});

popupScene.setCloseButtonCallback(event -> popupStage.hide());
if (!popupStage.isShowing()) {
popupStage.showAndWait();
}
}


Expand Down
31 changes: 31 additions & 0 deletions src/main/java/io/rpg/model/actions/Action.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import io.rpg.model.object.GameObject;
import org.jetbrains.annotations.Nullable;

import java.util.Optional;

/**
* A marker interface for action classes.
*/
Expand All @@ -22,6 +24,26 @@ public void setEmitter(GameObject emitter) {
public @Nullable GameObject getEmitter() {
return null;
}

@Override
public void setBeforeAction(Action action) {
/* noop */
}

@Override
public void setAfterAction(Action action) {
/* noop */
}

@Override
public Optional<Action> getBeforeAction() {
return Optional.empty();
}

@Override
public Optional<Action> getAfterAction() {
return Optional.empty();
}
};

void acceptActionEngine(final ActionEngine engine);
Expand All @@ -30,4 +52,13 @@ public void setEmitter(GameObject emitter) {

@Nullable
GameObject getEmitter();

void setBeforeAction(Action action);

void setAfterAction(Action action);

Optional<Action> getBeforeAction();

@Nullable
Optional<Action> getAfterAction();
}
2 changes: 1 addition & 1 deletion src/main/java/io/rpg/model/actions/ActionEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.rpg.model.object.Player;
import io.rpg.util.BattleResult;
import io.rpg.view.LocationView;
import javafx.application.Platform;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -83,7 +84,6 @@ public void acceptQuizResult(boolean correct, int pointsCount) {
var controller = controller();
if (correct) {
controller.getPlayerController().addPoints(pointsCount);
controller.getPopupController().hidePopup();
if (pointsCount > 0)
controller.getPopupController().openPointsPopup(pointsCount, controller.getWindowCenterX(), controller.getWindowCenterY());
} else {
Expand Down
29 changes: 28 additions & 1 deletion src/main/java/io/rpg/model/actions/BaseAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@

import io.rpg.model.actions.condition.Condition;
import io.rpg.model.object.GameObject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Optional;

public abstract class BaseAction implements Action {
@Nullable
private final Condition condition;

@Nullable
private GameObject emitter;

@Nullable
private Action afterAction;

@Nullable
private Action beforeAction;

public BaseAction(@Nullable Condition condition) {
this.condition = condition;
}
Expand All @@ -29,4 +36,24 @@ public void setEmitter(GameObject emitter) {
public GameObject getEmitter() {
return emitter;
}

@Override
public void setAfterAction(Action action) {
afterAction = action;
}

@Override
public void setBeforeAction(Action action) {
beforeAction = action;
}

@Override
public Optional<Action> getBeforeAction() {
return Optional.ofNullable(beforeAction);
}

@Override
public Optional<Action> getAfterAction() {
return Optional.ofNullable(afterAction);
}
}
2 changes: 2 additions & 0 deletions src/main/java/io/rpg/model/actions/BaseActionEmitter.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ public abstract class BaseActionEmitter implements ActionEmitter {
private ActionConsumer consumer = (a) -> {};

protected void emitAction(Action action) {
action.getBeforeAction().ifPresent(a -> consumer.consumeAction(a));
consumer.consumeAction(action);
action.getAfterAction().ifPresent(a -> consumer.consumeAction(a));
}

@Override
Expand Down
17 changes: 13 additions & 4 deletions src/main/java/io/rpg/model/object/GameObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.rpg.model.data.GameObjectStateChange;
import io.rpg.model.data.Position;
import io.rpg.util.DataObjectDescriptionProvider;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Point2D;
Expand Down Expand Up @@ -116,12 +117,12 @@ public void setPosition(Position playerPosition) {

public void setOnRightClickAction(@NotNull Action onRightClickAction) {
this.onRightClickAction = onRightClickAction;
this.onRightClickAction.setEmitter(this);
setThisAsEmitter(this.onRightClickAction);
}

public void setOnLeftClickAction(@NotNull Action onLeftClickAction) {
this.onLeftClickAction = onLeftClickAction;
this.onLeftClickAction.setEmitter(this);
setThisAsEmitter(this.onLeftClickAction);
}

public void onRightClick() {
Expand All @@ -141,11 +142,19 @@ public void onApproach() {
return;
}
wasOnApproachFired = true;
emitAction(onApproach);
Platform.runLater(() -> emitAction(onApproach));
}

public void setOnApproach(Action onApproach) {
this.onApproach = onApproach;
this.onApproach.setEmitter(this);
setThisAsEmitter(this.onApproach);
}

private void setThisAsEmitter(Action action) {
if (action != null) {
action.setEmitter(this);
action.getAfterAction().ifPresent(a -> a.setEmitter(this));
action.getBeforeAction().ifPresent(a -> a.setEmitter(this));
}
}
}
17 changes: 17 additions & 0 deletions src/main/java/io/rpg/util/ActionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ public static Action fromConfig(ActionConfigBundle config) {
assert config.getActionType() != null : "Null action type! Make sure to call validation " +
"method after the config object is inflated from JSON!";

Action result = actionByType(config);
initBeforeAndAfterActions(result, config);

return result;
}

private static Action actionByType(ActionConfigBundle config) {
switch (config.getActionType()) {
case Quiz -> {
return quizActionFromConfig(config);
Expand All @@ -43,6 +50,7 @@ public static Action fromConfig(ActionConfigBundle config) {
throw new IllegalArgumentException("Unexpected action type!");
}
}

}

private static QuizAction quizActionFromConfig(ActionConfigBundle config) {
Expand Down Expand Up @@ -75,4 +83,13 @@ private static BattleAction battleActionFromConfig(ActionConfigBundle config) {
return new BattleAction(config.getRewardPoints(),
ConditionFactory.fromConfig(config.getCondition()));
}

private static void initBeforeAndAfterActions(Action action, ActionConfigBundle config) {
if (config.getBeforeAction() != null) {
action.setBeforeAction(actionByType(config.getBeforeAction()));
}
if (config.getAfterAction() != null) {
action.setAfterAction(actionByType(config.getAfterAction()));
}
}
}