From f1501b758bb54e4db0a7e1dcbaff88ae214b9cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Przemys=C5=82aw=20Pluta?= Date: Thu, 1 Sep 2022 11:36:07 +0200 Subject: [PATCH] Create MapObject world utility class --- .../base/util/path/PathExecutor.java | 5 + .../base/util/world/MapObject.java | 221 ++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 api/src/main/java/com/bartlomiejpluta/base/util/world/MapObject.java diff --git a/api/src/main/java/com/bartlomiejpluta/base/util/path/PathExecutor.java b/api/src/main/java/com/bartlomiejpluta/base/util/path/PathExecutor.java index 6e0dfbe0..ea475b19 100644 --- a/api/src/main/java/com/bartlomiejpluta/base/util/path/PathExecutor.java +++ b/api/src/main/java/com/bartlomiejpluta/base/util/path/PathExecutor.java @@ -5,6 +5,7 @@ import com.bartlomiejpluta.base.api.move.Movable; import java.util.List; +import static java.lang.Math.max; import static java.util.Collections.emptyList; public class PathExecutor { @@ -62,6 +63,10 @@ public class PathExecutor { return result; } + public void setCurrentSegment(int segmentNumber) { + this.current = max(0, segmentNumber - 1); + } + public void reset() { iteration = 0; current = 0; diff --git a/api/src/main/java/com/bartlomiejpluta/base/util/world/MapObject.java b/api/src/main/java/com/bartlomiejpluta/base/util/world/MapObject.java new file mode 100644 index 00000000..e5611ac6 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/util/world/MapObject.java @@ -0,0 +1,221 @@ +package com.bartlomiejpluta.base.util.world; + + +import com.bartlomiejpluta.base.api.character.Character; +import com.bartlomiejpluta.base.lib.character.CharacterDelegate; +import com.bartlomiejpluta.base.util.path.CharacterPath; +import com.bartlomiejpluta.base.util.path.PathExecutor; +import lombok.NonNull; +import lombok.SneakyThrows; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +import static com.bartlomiejpluta.base.api.move.Direction.*; +import static java.util.concurrent.CompletableFuture.completedFuture; + +public abstract class MapObject extends CharacterDelegate { + protected final PathExecutor pathExecutor = new PathExecutor<>(this); + private final short frame; + private int pathLength; + + protected boolean interacting = false; + private CompletableFuture condition; + private CompletableFuture future; + + private float step = 0.05f; + private float cooldown = 0.5f; + + private Supplier> triggerCondition; + private Supplier> interactionCondition; + private Supplier> completionCondition; + + private Supplier> beforeAll; + private Supplier> before; + private Supplier> after; + private Supplier> afterAll; + + + public MapObject(@NonNull Character character, short frame) { + super(character); + + this.frame = frame; + setBlocking(true); + disableAnimation(); + setAnimationFrame(frame); + pathExecutor.setRepeat(1); + + initPath(); + } + + private void initPath() { + var path = new CharacterPath() + .run(this::initTriggerCondition) + .suspend(this::checkCondition) + .run(this::startInteraction) + .suspend(this::waitForFuture) + .turn(LEFT, frame) + .wait(step) + .turn(RIGHT, frame) + .wait(step) + .turn(UP, frame) + .wait(step) + .run(this::initInteractionCondition) + .suspend(this::checkCondition) + .run(this::runInteraction) + .suspend(this::waitForFuture) + .run(this::initCompletionCondition) + .suspend(this::checkCondition) + .wait(step) + .turn(RIGHT, frame) + .wait(step) + .turn(LEFT, frame) + .wait(step) + .turn(DOWN, frame) + .run(this::finishInteraction) + .suspend(this::waitForFuture) + .wait(cooldown) + .run(this::completeInteraction); + + pathLength = path.getPath().size(); + + pathExecutor.setPath(path); + } + + private void initTriggerCondition() { + this.condition = triggerCondition != null ? triggerCondition.get() : completedFuture(true); + } + + @SneakyThrows + private boolean checkCondition(MapObject object) { + if (!condition.isDone()) { + return false; + } + + if (!condition.get()) { + // We are interested in the last before last segment which is wait(cooldown) + // to put some cooldown after aborting interaction so that + // it won't be immediately triggered again by accident + pathExecutor.setCurrentSegment(pathLength - 2); + } + + return true; + } + + private void initInteractionCondition() { + this.condition = interactionCondition != null ? interactionCondition.get() : completedFuture(true); + } + + private void initCompletionCondition() { + this.condition = completionCondition != null ? completionCondition.get() : completedFuture(true); + } + + public MapObject animationStepDuration(float duration) { + this.step = duration; + initPath(); + return this; + } + + public MapObject interactionCooldown(float cooldown) { + this.cooldown = cooldown; + initPath(); + return this; + } + + public MapObject triggerCondition(@NonNull Supplier> condition) { + this.triggerCondition = condition; + return this; + } + + public MapObject interactionCondition(@NonNull Supplier> condition) { + this.interactionCondition = condition; + return this; + } + + public MapObject completionCondition(@NonNull Supplier> condition) { + this.completionCondition = condition; + return this; + } + + public MapObject beforeAll(@NonNull Supplier> action) { + this.beforeAll = action; + return this; + } + + public MapObject before(@NonNull Supplier> action) { + this.before = action; + return this; + } + + public MapObject after(@NonNull Supplier> action) { + this.after = action; + return this; + } + + public MapObject afterAll(@NonNull Supplier> action) { + this.afterAll = action; + return this; + } + + public void triggerInteraction() { + if (interacting) { + return; + } + + pathExecutor.reset(); + + if (beforeAll != null) { + beforeAll.get().thenRun(() -> interacting = true); + } else { + interacting = true; + } + } + + private void startInteraction() { + this.future = onInteractionBegin(); + } + + private CompletableFuture waitForFuture() { + return future; + } + + private void runInteraction() { + this.future = (before != null ? before.get() : completedFuture(null)) + .thenCompose(v -> interact()) + .thenCompose(v -> (after != null ? after.get() : completedFuture(null))); + } + + private void finishInteraction() { + this.future = onIntegrationEnd().thenCompose(v -> afterAll != null ? afterAll.get() : completedFuture(null)); + } + + private void completeInteraction() { + interacting = false; + reset(); + } + + @Override + public void update(float dt) { + if (interacting) { + pathExecutor.execute(getLayer(), dt); + } + } + + protected final void reset() { + setFaceDirection(DOWN); + pathExecutor.reset(); + interacting = false; + } + + @NonNull + protected CompletableFuture onInteractionBegin() { + return completedFuture(null); + } + + @NonNull + protected CompletableFuture onIntegrationEnd() { + return completedFuture(null); + } + + protected abstract CompletableFuture interact(); +} \ No newline at end of file