Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/game/StarActorMovementController.cpp
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
commit6352e8e3196f78388b6c771073f9e03eaa612673 (patch)
treee23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/StarActorMovementController.cpp
parent6741a057e5639280d85d0f88ba26f000baa58f61 (diff)
everything everywhere
all at once
Diffstat (limited to 'source/game/StarActorMovementController.cpp')
-rw-r--r--source/game/StarActorMovementController.cpp1473
1 files changed, 1473 insertions, 0 deletions
diff --git a/source/game/StarActorMovementController.cpp b/source/game/StarActorMovementController.cpp
new file mode 100644
index 0000000..915e7e0
--- /dev/null
+++ b/source/game/StarActorMovementController.cpp
@@ -0,0 +1,1473 @@
+#include "StarActorMovementController.hpp"
+#include "StarDataStreamExtra.hpp"
+#include "StarJsonExtra.hpp"
+#include "StarRoot.hpp"
+#include "StarAssets.hpp"
+#include "StarPlatformerAStar.hpp"
+#include "StarObject.hpp"
+
+namespace Star {
+
+ActorJumpProfile::ActorJumpProfile() {}
+
+ActorJumpProfile::ActorJumpProfile(Json const& config) {
+ jumpSpeed = config.optFloat("jumpSpeed");
+ jumpControlForce = config.optFloat("jumpControlForce");
+ jumpInitialPercentage = config.optFloat("jumpInitialPercentage");
+ jumpHoldTime = config.optFloat("jumpHoldTime");
+ jumpTotalHoldTime = config.optFloat("jumpTotalHoldTime");
+ multiJump = config.optBool("multiJump");
+ reJumpDelay = config.optFloat("reJumpDelay");
+ autoJump = config.optBool("autoJump");
+ collisionCancelled = config.optBool("collisionCancelled");
+}
+
+Json ActorJumpProfile::toJson() const {
+ return JsonObject{
+ {"jumpSpeed", jsonFromMaybe(jumpSpeed)},
+ {"jumpControlForce", jsonFromMaybe(jumpControlForce)},
+ {"jumpInitialPercentage", jsonFromMaybe(jumpInitialPercentage)},
+ {"jumpHoldTime", jsonFromMaybe(jumpHoldTime)},
+ {"jumpTotalHoldTime", jsonFromMaybe(jumpTotalHoldTime)},
+ {"multiJump", jsonFromMaybe(multiJump)},
+ {"reJumpDelay", jsonFromMaybe(reJumpDelay)},
+ {"autoJump", jsonFromMaybe(autoJump)},
+ {"collisionCancelled", jsonFromMaybe(collisionCancelled)}
+ };
+}
+
+ActorJumpProfile ActorJumpProfile::merge(ActorJumpProfile const& rhs) const {
+ ActorJumpProfile merged;
+
+ merged.jumpSpeed = rhs.jumpSpeed.orMaybe(jumpSpeed);
+ merged.jumpControlForce = rhs.jumpControlForce.orMaybe(jumpControlForce);
+ merged.jumpInitialPercentage = rhs.jumpInitialPercentage.orMaybe(jumpInitialPercentage);
+ merged.jumpHoldTime = rhs.jumpHoldTime.orMaybe(jumpHoldTime);
+ merged.jumpTotalHoldTime = rhs.jumpTotalHoldTime.orMaybe(jumpTotalHoldTime);
+ merged.multiJump = rhs.multiJump.orMaybe(multiJump);
+ merged.reJumpDelay = rhs.reJumpDelay.orMaybe(reJumpDelay);
+ merged.autoJump = rhs.autoJump.orMaybe(autoJump);
+ merged.collisionCancelled = rhs.collisionCancelled.orMaybe(collisionCancelled);
+
+ return merged;
+}
+
+DataStream& operator>>(DataStream& ds, ActorJumpProfile& movementParameters) {
+ ds.read(movementParameters.jumpSpeed);
+ ds.read(movementParameters.jumpControlForce);
+ ds.read(movementParameters.jumpInitialPercentage);
+ ds.read(movementParameters.jumpHoldTime);
+ ds.read(movementParameters.jumpTotalHoldTime);
+ ds.read(movementParameters.multiJump);
+ ds.read(movementParameters.reJumpDelay);
+ ds.read(movementParameters.autoJump);
+ ds.read(movementParameters.collisionCancelled);
+
+ return ds;
+}
+
+DataStream& operator<<(DataStream& ds, ActorJumpProfile const& movementParameters) {
+ ds.write(movementParameters.jumpSpeed);
+ ds.write(movementParameters.jumpControlForce);
+ ds.write(movementParameters.jumpInitialPercentage);
+ ds.write(movementParameters.jumpHoldTime);
+ ds.write(movementParameters.jumpTotalHoldTime);
+ ds.write(movementParameters.multiJump);
+ ds.write(movementParameters.reJumpDelay);
+ ds.write(movementParameters.autoJump);
+ ds.write(movementParameters.collisionCancelled);
+
+ return ds;
+}
+
+ActorMovementParameters ActorMovementParameters::sensibleDefaults() {
+ return ActorMovementParameters(Root::singleton().assets()->json("/default_actor_movement.config").toObject());
+}
+
+ActorMovementParameters::ActorMovementParameters(Json const& config) {
+ if (config.isNull())
+ return;
+
+ mass = config.optFloat("mass");
+ gravityMultiplier = config.optFloat("gravityMultiplier");
+ liquidBuoyancy = config.optFloat("liquidBuoyancy");
+ airBuoyancy = config.optFloat("airBuoyancy");
+ bounceFactor = config.optFloat("bounceFactor");
+ stopOnFirstBounce = config.optBool("stopOnFirstBounce");
+ enableSurfaceSlopeCorrection = config.optBool("enableSurfaceSlopeCorrection");
+ slopeSlidingFactor = config.optFloat("slopeSlidingFactor");
+ maxMovementPerStep = config.optFloat("maxMovementPerStep");
+ maximumCorrection = config.optFloat("maximumCorrection");
+ speedLimit = config.optFloat("speedLimit");
+
+ // "collisionPoly" is used as a synonym for setting both the standing and
+ // crouching polys.
+
+ auto collisionPolyConfig = config.get("collisionPoly", {});
+ auto standingPolyConfig = config.get("standingPoly", {});
+ auto crouchingPolyConfig = config.get("crouchingPoly", {});
+
+ if (!standingPolyConfig.isNull())
+ standingPoly = jsonToPolyF(standingPolyConfig);
+ else if (!collisionPolyConfig.isNull())
+ standingPoly = jsonToPolyF(collisionPolyConfig);
+
+ if (!crouchingPolyConfig.isNull())
+ crouchingPoly = jsonToPolyF(crouchingPolyConfig);
+ else if (!collisionPolyConfig.isNull())
+ crouchingPoly = jsonToPolyF(collisionPolyConfig);
+
+ stickyCollision = config.optBool("stickyCollision");
+ stickyForce = config.optFloat("stickyForce");
+
+ walkSpeed = config.optFloat("walkSpeed");
+ runSpeed = config.optFloat("runSpeed");
+ flySpeed = config.optFloat("flySpeed");
+ airFriction = config.optFloat("airFriction");
+ liquidFriction = config.optFloat("liquidFriction");
+ minimumLiquidPercentage = config.optFloat("minimumLiquidPercentage");
+ liquidImpedance = config.optFloat("liquidImpedance");
+ normalGroundFriction = config.optFloat("normalGroundFriction");
+ ambulatingGroundFriction = config.optFloat("ambulatingGroundFriction");
+ groundForce = config.optFloat("groundForce");
+ airForce = config.optFloat("airForce");
+ liquidForce = config.optFloat("liquidForce");
+
+ airJumpProfile = config.opt("airJumpProfile").apply(construct<ActorJumpProfile>()).value();
+ liquidJumpProfile = config.opt("liquidJumpProfile").apply(construct<ActorJumpProfile>()).value();
+
+ fallStatusSpeedMin = config.optFloat("fallStatusSpeedMin");
+ fallThroughSustainFrames = config.optInt("fallThroughSustainFrames");
+ maximumPlatformCorrection = config.optFloat("maximumPlatformCorrection");
+ maximumPlatformCorrectionVelocityFactor = config.optFloat("maximumPlatformCorrectionVelocityFactor");
+
+ physicsEffectCategories = config.opt("physicsEffectCategories").apply(jsonToStringSet);
+
+ groundMovementMinimumSustain = config.optFloat("groundMovementMinimumSustain");
+ groundMovementMaximumSustain = config.optFloat("groundMovementMaximumSustain");
+ groundMovementCheckDistance = config.optFloat("groundMovementCheckDistance");
+
+ collisionEnabled = config.optBool("collisionEnabled");
+ frictionEnabled = config.optBool("frictionEnabled");
+ gravityEnabled = config.optBool("gravityEnabled");
+
+ pathExploreRate = config.optFloat("pathExploreRate");
+}
+
+Json ActorMovementParameters::toJson() const {
+ return JsonObject{{"mass", jsonFromMaybe(mass)},
+ {"gravityMultiplier", jsonFromMaybe(gravityMultiplier)},
+ {"liquidBuoyancy", jsonFromMaybe(liquidBuoyancy)},
+ {"airBuoyancy", jsonFromMaybe(airBuoyancy)},
+ {"bounceFactor", jsonFromMaybe(bounceFactor)},
+ {"stopOnFirstBounce", jsonFromMaybe(stopOnFirstBounce)},
+ {"enableSurfaceSlopeCorrection", jsonFromMaybe(enableSurfaceSlopeCorrection)},
+ {"slopeSlidingFactor", jsonFromMaybe(slopeSlidingFactor)},
+ {"maxMovementPerStep", jsonFromMaybe(maxMovementPerStep)},
+ {"maximumCorrection", jsonFromMaybe(maximumCorrection)},
+ {"speedLimit", jsonFromMaybe(speedLimit)},
+ {"standingPoly", jsonFromMaybe(standingPoly, jsonFromPolyF)},
+ {"crouchingPoly", jsonFromMaybe(crouchingPoly, jsonFromPolyF)},
+ {"stickyCollision", jsonFromMaybe(stickyCollision)},
+ {"stickyForce", jsonFromMaybe(stickyForce)},
+ {"walkSpeed", jsonFromMaybe(walkSpeed)},
+ {"runSpeed", jsonFromMaybe(runSpeed)},
+ {"flySpeed", jsonFromMaybe(flySpeed)},
+ {"airFriction", jsonFromMaybe(airFriction)},
+ {"liquidFriction", jsonFromMaybe(liquidFriction)},
+ {"minimumLiquidPercentage", jsonFromMaybe(minimumLiquidPercentage)},
+ {"liquidImpedance", jsonFromMaybe(liquidImpedance)},
+ {"normalGroundFriction", jsonFromMaybe(normalGroundFriction)},
+ {"ambulatingGroundFriction", jsonFromMaybe(ambulatingGroundFriction)},
+ {"groundForce", jsonFromMaybe(groundForce)},
+ {"airForce", jsonFromMaybe(airForce)},
+ {"liquidForce", jsonFromMaybe(liquidForce)},
+ {"airJumpProfile", airJumpProfile.toJson()},
+ {"liquidJumpProfile", liquidJumpProfile.toJson()},
+ {"fallStatusSpeedMin", jsonFromMaybe(fallStatusSpeedMin)},
+ {"fallThroughSustainFrames", jsonFromMaybe(fallThroughSustainFrames)},
+ {"maximumPlatformCorrection", jsonFromMaybe(maximumPlatformCorrection)},
+ {"maximumPlatformCorrectionVelocityFactor", jsonFromMaybe(maximumPlatformCorrectionVelocityFactor)},
+ {"physicsEffectCategories", jsonFromMaybe(physicsEffectCategories, jsonFromStringSet)},
+ {"groundMovementMinimumSustain", jsonFromMaybe(groundMovementMinimumSustain)},
+ {"groundMovementMaximumSustain", jsonFromMaybe(groundMovementMaximumSustain)},
+ {"groundMovementCheckDistance", jsonFromMaybe(groundMovementCheckDistance)},
+ {"collisionEnabled", jsonFromMaybe(collisionEnabled)},
+ {"frictionEnabled", jsonFromMaybe(frictionEnabled)},
+ {"gravityEnabled", jsonFromMaybe(gravityEnabled)},
+ {"pathExploreRate", jsonFromMaybe(pathExploreRate)}};
+}
+
+ActorMovementParameters ActorMovementParameters::merge(ActorMovementParameters const& rhs) const {
+ ActorMovementParameters merged;
+
+ merged.mass = rhs.mass.orMaybe(mass);
+ merged.gravityMultiplier = rhs.gravityMultiplier.orMaybe(gravityMultiplier);
+ merged.liquidBuoyancy = rhs.liquidBuoyancy.orMaybe(liquidBuoyancy);
+ merged.airBuoyancy = rhs.airBuoyancy.orMaybe(airBuoyancy);
+ merged.bounceFactor = rhs.bounceFactor.orMaybe(bounceFactor);
+ merged.stopOnFirstBounce = rhs.stopOnFirstBounce.orMaybe(stopOnFirstBounce);
+ merged.enableSurfaceSlopeCorrection = rhs.enableSurfaceSlopeCorrection.orMaybe(enableSurfaceSlopeCorrection);
+ merged.slopeSlidingFactor = rhs.slopeSlidingFactor.orMaybe(slopeSlidingFactor);
+ merged.maxMovementPerStep = rhs.maxMovementPerStep.orMaybe(maxMovementPerStep);
+ merged.maximumCorrection = rhs.maximumCorrection.orMaybe(maximumCorrection);
+ merged.speedLimit = rhs.speedLimit.orMaybe(speedLimit);
+ merged.standingPoly = rhs.standingPoly.orMaybe(standingPoly);
+ merged.crouchingPoly = rhs.crouchingPoly.orMaybe(crouchingPoly);
+ merged.stickyCollision = rhs.stickyCollision.orMaybe(stickyCollision);
+ merged.stickyForce = rhs.stickyForce.orMaybe(stickyForce);
+ merged.walkSpeed = rhs.walkSpeed.orMaybe(walkSpeed);
+ merged.runSpeed = rhs.runSpeed.orMaybe(runSpeed);
+ merged.flySpeed = rhs.flySpeed.orMaybe(flySpeed);
+ merged.airFriction = rhs.airFriction.orMaybe(airFriction);
+ merged.liquidFriction = rhs.liquidFriction.orMaybe(liquidFriction);
+ merged.minimumLiquidPercentage = rhs.minimumLiquidPercentage.orMaybe(minimumLiquidPercentage);
+ merged.liquidImpedance = rhs.liquidImpedance.orMaybe(liquidImpedance);
+ merged.normalGroundFriction = rhs.normalGroundFriction.orMaybe(normalGroundFriction);
+ merged.ambulatingGroundFriction = rhs.ambulatingGroundFriction.orMaybe(ambulatingGroundFriction);
+ merged.groundForce = rhs.groundForce.orMaybe(groundForce);
+ merged.airForce = rhs.airForce.orMaybe(airForce);
+ merged.liquidForce = rhs.liquidForce.orMaybe(liquidForce);
+
+ merged.airJumpProfile = airJumpProfile.merge(rhs.airJumpProfile);
+ merged.liquidJumpProfile = liquidJumpProfile.merge(rhs.liquidJumpProfile);
+
+ merged.fallStatusSpeedMin = rhs.fallStatusSpeedMin.orMaybe(fallStatusSpeedMin);
+ merged.fallThroughSustainFrames = rhs.fallThroughSustainFrames.orMaybe(fallThroughSustainFrames);
+ merged.maximumPlatformCorrection = rhs.maximumPlatformCorrection.orMaybe(maximumPlatformCorrection);
+ merged.maximumPlatformCorrectionVelocityFactor = rhs.maximumPlatformCorrectionVelocityFactor.orMaybe(maximumPlatformCorrectionVelocityFactor);
+
+ merged.physicsEffectCategories = rhs.physicsEffectCategories.orMaybe(physicsEffectCategories);
+
+ merged.groundMovementMinimumSustain = rhs.groundMovementMinimumSustain.orMaybe(groundMovementMinimumSustain);
+ merged.groundMovementMaximumSustain = rhs.groundMovementMaximumSustain.orMaybe(groundMovementMaximumSustain);
+ merged.groundMovementCheckDistance = rhs.groundMovementCheckDistance.orMaybe(groundMovementCheckDistance);
+
+ merged.collisionEnabled = rhs.collisionEnabled.orMaybe(collisionEnabled);
+ merged.frictionEnabled = rhs.frictionEnabled.orMaybe(frictionEnabled);
+ merged.gravityEnabled = rhs.gravityEnabled.orMaybe(gravityEnabled);
+
+ merged.pathExploreRate = rhs.pathExploreRate.orMaybe(pathExploreRate);
+
+ return merged;
+}
+
+DataStream& operator>>(DataStream& ds, ActorMovementParameters& movementParameters) {
+ ds.read(movementParameters.mass);
+ ds.read(movementParameters.gravityMultiplier);
+ ds.read(movementParameters.liquidBuoyancy);
+ ds.read(movementParameters.airBuoyancy);
+ ds.read(movementParameters.bounceFactor);
+ ds.read(movementParameters.stopOnFirstBounce);
+ ds.read(movementParameters.enableSurfaceSlopeCorrection);
+ ds.read(movementParameters.slopeSlidingFactor);
+ ds.read(movementParameters.maxMovementPerStep);
+ ds.read(movementParameters.maximumCorrection);
+ ds.read(movementParameters.speedLimit);
+ ds.read(movementParameters.standingPoly);
+ ds.read(movementParameters.crouchingPoly);
+ ds.read(movementParameters.stickyCollision);
+ ds.read(movementParameters.stickyForce);
+ ds.read(movementParameters.walkSpeed);
+ ds.read(movementParameters.runSpeed);
+ ds.read(movementParameters.flySpeed);
+ ds.read(movementParameters.airFriction);
+ ds.read(movementParameters.liquidFriction);
+ ds.read(movementParameters.minimumLiquidPercentage);
+ ds.read(movementParameters.liquidImpedance);
+ ds.read(movementParameters.normalGroundFriction);
+ ds.read(movementParameters.ambulatingGroundFriction);
+ ds.read(movementParameters.groundForce);
+ ds.read(movementParameters.airForce);
+ ds.read(movementParameters.liquidForce);
+ ds.read(movementParameters.airJumpProfile);
+ ds.read(movementParameters.liquidJumpProfile);
+ ds.read(movementParameters.fallStatusSpeedMin);
+ ds.read(movementParameters.fallThroughSustainFrames);
+ ds.read(movementParameters.maximumPlatformCorrection);
+ ds.read(movementParameters.maximumPlatformCorrectionVelocityFactor);
+ ds.read(movementParameters.physicsEffectCategories);
+ ds.read(movementParameters.collisionEnabled);
+ ds.read(movementParameters.frictionEnabled);
+ ds.read(movementParameters.gravityEnabled);
+ ds.read(movementParameters.pathExploreRate);
+
+ return ds;
+}
+
+DataStream& operator<<(DataStream& ds, ActorMovementParameters const& movementParameters) {
+ ds.write(movementParameters.mass);
+ ds.write(movementParameters.gravityMultiplier);
+ ds.write(movementParameters.liquidBuoyancy);
+ ds.write(movementParameters.airBuoyancy);
+ ds.write(movementParameters.bounceFactor);
+ ds.write(movementParameters.stopOnFirstBounce);
+ ds.write(movementParameters.enableSurfaceSlopeCorrection);
+ ds.write(movementParameters.slopeSlidingFactor);
+ ds.write(movementParameters.maxMovementPerStep);
+ ds.write(movementParameters.maximumCorrection);
+ ds.write(movementParameters.speedLimit);
+ ds.write(movementParameters.standingPoly);
+ ds.write(movementParameters.crouchingPoly);
+ ds.write(movementParameters.stickyCollision);
+ ds.write(movementParameters.stickyForce);
+ ds.write(movementParameters.walkSpeed);
+ ds.write(movementParameters.runSpeed);
+ ds.write(movementParameters.flySpeed);
+ ds.write(movementParameters.airFriction);
+ ds.write(movementParameters.liquidFriction);
+ ds.write(movementParameters.minimumLiquidPercentage);
+ ds.write(movementParameters.liquidImpedance);
+ ds.write(movementParameters.normalGroundFriction);
+ ds.write(movementParameters.ambulatingGroundFriction);
+ ds.write(movementParameters.groundForce);
+ ds.write(movementParameters.airForce);
+ ds.write(movementParameters.liquidForce);
+ ds.write(movementParameters.airJumpProfile);
+ ds.write(movementParameters.liquidJumpProfile);
+ ds.write(movementParameters.fallStatusSpeedMin);
+ ds.write(movementParameters.fallThroughSustainFrames);
+ ds.write(movementParameters.maximumPlatformCorrection);
+ ds.write(movementParameters.maximumPlatformCorrectionVelocityFactor);
+ ds.write(movementParameters.physicsEffectCategories);
+ ds.write(movementParameters.collisionEnabled);
+ ds.write(movementParameters.frictionEnabled);
+ ds.write(movementParameters.gravityEnabled);
+ ds.write(movementParameters.pathExploreRate);
+
+ return ds;
+}
+
+ActorMovementModifiers::ActorMovementModifiers(Json const& config) {
+ groundMovementModifier = 1.0f;
+ liquidMovementModifier = 1.0f;
+ speedModifier = 1.0f;
+ airJumpModifier = 1.0f;
+ liquidJumpModifier = 1.0f;
+ runningSuppressed = false;
+ jumpingSuppressed = false;
+ facingSuppressed = false;
+ movementSuppressed = false;
+
+ if (!config.isNull()) {
+ groundMovementModifier = config.getFloat("groundMovementModifier", 1.0f);
+ liquidMovementModifier = config.getFloat("liquidMovementModifier", 1.0f);
+ speedModifier = config.getFloat("speedModifier", 1.0f);
+ airJumpModifier = config.getFloat("airJumpModifier", 1.0f);
+ liquidJumpModifier = config.getFloat("liquidJumpModifier", 1.0f);
+ runningSuppressed = config.getBool("runningSuppressed", false);
+ jumpingSuppressed = config.getBool("jumpingSuppressed", false);
+ facingSuppressed = config.getBool("facingSuppressed", false);
+ movementSuppressed = config.getBool("movementSuppressed", false);
+ }
+}
+
+Json ActorMovementModifiers::toJson() const {
+ return JsonObject{
+ {"groundMovementModifier", groundMovementModifier},
+ {"liquidMovementModifier", liquidMovementModifier},
+ {"speedModifier", speedModifier},
+ {"airJumpModifier", airJumpModifier},
+ {"liquidJumpModifier", liquidJumpModifier},
+ {"runningSuppressed", runningSuppressed},
+ {"jumpingSuppressed", jumpingSuppressed},
+ {"facingSuppressed", facingSuppressed},
+ {"movementSuppressed", movementSuppressed}
+ };
+}
+
+ActorMovementModifiers ActorMovementModifiers::combine(ActorMovementModifiers const& rhs) const {
+ ActorMovementModifiers res = *this;
+
+ res.groundMovementModifier *= rhs.groundMovementModifier;
+ res.liquidMovementModifier *= rhs.liquidMovementModifier;
+ res.speedModifier *= rhs.speedModifier;
+ res.airJumpModifier *= rhs.airJumpModifier;
+ res.liquidJumpModifier *= rhs.liquidJumpModifier;
+ res.runningSuppressed = res.runningSuppressed || rhs.runningSuppressed;
+ res.jumpingSuppressed = res.jumpingSuppressed || rhs.jumpingSuppressed;
+ res.facingSuppressed = res.facingSuppressed || rhs.facingSuppressed;
+ res.movementSuppressed = res.movementSuppressed || rhs.movementSuppressed;
+
+ return res;
+}
+
+DataStream& operator>>(DataStream& ds, ActorMovementModifiers& movementModifiers) {
+ ds.read(movementModifiers.groundMovementModifier);
+ ds.read(movementModifiers.liquidMovementModifier);
+ ds.read(movementModifiers.speedModifier);
+ ds.read(movementModifiers.airJumpModifier);
+ ds.read(movementModifiers.liquidJumpModifier);
+ ds.read(movementModifiers.runningSuppressed);
+ ds.read(movementModifiers.jumpingSuppressed);
+ ds.read(movementModifiers.facingSuppressed);
+ ds.read(movementModifiers.movementSuppressed);
+
+ return ds;
+}
+
+DataStream& operator<<(DataStream& ds, ActorMovementModifiers const& movementModifiers) {
+ ds.write(movementModifiers.groundMovementModifier);
+ ds.write(movementModifiers.liquidMovementModifier);
+ ds.write(movementModifiers.speedModifier);
+ ds.write(movementModifiers.airJumpModifier);
+ ds.write(movementModifiers.liquidJumpModifier);
+ ds.write(movementModifiers.runningSuppressed);
+ ds.write(movementModifiers.jumpingSuppressed);
+ ds.write(movementModifiers.facingSuppressed);
+ ds.write(movementModifiers.movementSuppressed);
+
+ return ds;
+}
+
+ActorMovementController::ActorMovementController(ActorMovementParameters const& parameters) {
+ m_controlRotationRate = 0.0f;
+ m_controlRun = false;
+ m_controlCrouch = false;
+ m_controlDown = false;
+ m_controlJump = false;
+ m_controlJumpAnyway = false;
+ m_controlPathMove = {};
+
+ addNetElement(&m_walking);
+ addNetElement(&m_running);
+ addNetElement(&m_movingDirection);
+ addNetElement(&m_facingDirection);
+ addNetElement(&m_crouching);
+ addNetElement(&m_flying);
+ addNetElement(&m_falling);
+ addNetElement(&m_canJump);
+ addNetElement(&m_jumping);
+ addNetElement(&m_groundMovement);
+ addNetElement(&m_liquidMovement);
+ addNetElement(&m_anchorState);
+
+ m_fallThroughSustain = 0;
+ m_lastControlJump = false;
+ m_lastControlDown = false;
+ m_targetHorizontalAmbulatingVelocity = 0.0f;
+
+ resetBaseParameters(parameters);
+}
+
+ActorMovementParameters const& ActorMovementController::baseParameters() const {
+ return m_baseParameters;
+}
+
+void ActorMovementController::updateBaseParameters(ActorMovementParameters const& parameters) {
+ m_baseParameters = m_baseParameters.merge(parameters);
+ applyMCParameters(m_baseParameters);
+}
+
+void ActorMovementController::resetBaseParameters(ActorMovementParameters const& parameters) {
+ m_baseParameters = ActorMovementParameters::sensibleDefaults().merge(parameters);
+ applyMCParameters(m_baseParameters);
+}
+
+ActorMovementModifiers const& ActorMovementController::baseModifiers() const {
+ return m_baseModifiers;
+}
+
+void ActorMovementController::updateBaseModifiers(ActorMovementModifiers const& modifiers) {
+ m_baseModifiers = m_baseModifiers.combine(modifiers);
+}
+
+void ActorMovementController::resetBaseModifiers(ActorMovementModifiers const& modifiers) {
+ m_baseModifiers = modifiers;
+}
+
+Json ActorMovementController::storeState() const {
+ return JsonObject{{"position", jsonFromVec2F(MovementController::position())},
+ {"velocity", jsonFromVec2F(velocity())},
+ {"rotation", MovementController::rotation()},
+ {"movingDirection", DirectionNames.getRight(m_movingDirection.get())},
+ {"facingDirection", DirectionNames.getRight(m_facingDirection.get())},
+ {"crouching", m_crouching.get()}};
+}
+
+void ActorMovementController::loadState(Json const& state) {
+ setPosition(jsonToVec2F(state.get("position")));
+ setVelocity(jsonToVec2F(state.get("velocity")));
+ setRotation(state.getFloat("rotation"));
+ m_movingDirection.set(DirectionNames.getLeft(state.getString("movingDirection")));
+ m_facingDirection.set(DirectionNames.getLeft(state.getString("facingDirection")));
+ m_crouching.set(state.getBool("crouching"));
+}
+
+void ActorMovementController::setAnchorState(EntityAnchorState anchorState) {
+ doSetAnchorState(anchorState);
+}
+
+void ActorMovementController::resetAnchorState() {
+ doSetAnchorState({});
+}
+
+Maybe<EntityAnchorState> ActorMovementController::anchorState() const {
+ return m_anchorState.get();
+}
+
+EntityAnchorConstPtr ActorMovementController::entityAnchor() const {
+ return m_entityAnchor;
+}
+
+Vec2F ActorMovementController::position() const {
+ if (m_entityAnchor)
+ return m_entityAnchor->position;
+ return MovementController::position();
+}
+
+float ActorMovementController::xPosition() const {
+ return position()[0];
+}
+
+float ActorMovementController::yPosition() const {
+ return position()[1];
+}
+
+float ActorMovementController::rotation() const {
+ if (m_entityAnchor)
+ return m_entityAnchor->angle;
+ return MovementController::rotation();
+}
+
+PolyF ActorMovementController::collisionBody() const {
+ auto collisionBody = MovementController::collisionPoly();
+ collisionBody.rotate(rotation());
+ collisionBody.translate(position());
+ return collisionBody;
+}
+
+RectF ActorMovementController::localBoundBox() const {
+ auto poly = MovementController::collisionPoly();
+ poly.rotate(rotation());
+ return poly.boundBox();
+}
+
+RectF ActorMovementController::collisionBoundBox() const {
+ return collisionBody().boundBox();
+}
+
+bool ActorMovementController::walking() const {
+ return m_walking.get();
+}
+
+bool ActorMovementController::running() const {
+ return m_running.get();
+}
+
+Direction ActorMovementController::movingDirection() const {
+ return m_movingDirection.get();
+}
+
+Direction ActorMovementController::facingDirection() const {
+ if (m_entityAnchor)
+ return m_entityAnchor->direction;
+ return m_facingDirection.get();
+}
+
+bool ActorMovementController::crouching() const {
+ return m_crouching.get();
+}
+
+bool ActorMovementController::flying() const {
+ return m_flying.get();
+}
+
+bool ActorMovementController::falling() const {
+ return m_falling.get();
+}
+
+bool ActorMovementController::canJump() const {
+ return m_canJump.get();
+}
+
+bool ActorMovementController::jumping() const {
+ return m_jumping.get();
+}
+
+bool ActorMovementController::groundMovement() const {
+ return m_groundMovement.get();
+}
+
+bool ActorMovementController::liquidMovement() const {
+ return m_liquidMovement.get();
+}
+
+bool ActorMovementController::pathfinding() const {
+ if (m_pathController)
+ return m_pathController->pathfinding();
+ else
+ return false;
+}
+
+void ActorMovementController::controlRotation(float rotationRate) {
+ m_controlRotationRate += rotationRate;
+}
+
+void ActorMovementController::controlAcceleration(Vec2F const& acceleration) {
+ m_controlAcceleration += acceleration;
+}
+
+void ActorMovementController::controlForce(Vec2F const& force) {
+ m_controlForce += force;
+}
+
+void ActorMovementController::controlApproachVelocity(Vec2F const& targetVelocity, float maxControlForce) {
+ m_controlApproachVelocities.append({targetVelocity, maxControlForce});
+}
+
+void ActorMovementController::controlApproachVelocityAlongAngle(
+ float angle, float targetVelocity, float maxControlForce, bool positiveOnly) {
+ m_controlApproachVelocityAlongAngles.append({angle, targetVelocity, maxControlForce, positiveOnly});
+}
+
+void ActorMovementController::controlApproachXVelocity(float targetXVelocity, float maxControlForce) {
+ controlApproachVelocityAlongAngle(0, targetXVelocity, maxControlForce);
+}
+
+void ActorMovementController::controlApproachYVelocity(float targetYVelocity, float maxControlForce) {
+ controlApproachVelocityAlongAngle(Constants::pi / 2, targetYVelocity, maxControlForce);
+}
+
+void ActorMovementController::controlParameters(ActorMovementParameters const& parameters) {
+ m_controlParameters = m_controlParameters.merge(parameters);
+}
+
+void ActorMovementController::controlModifiers(ActorMovementModifiers const& modifiers) {
+ m_controlModifiers = m_controlModifiers.combine(modifiers);
+}
+
+void ActorMovementController::controlMove(Direction direction, bool run) {
+ m_controlMove = direction;
+ m_controlRun = run;
+}
+
+void ActorMovementController::controlFace(Direction direction) {
+ m_controlFace = direction;
+}
+
+void ActorMovementController::controlDown() {
+ m_controlDown = true;
+}
+
+void ActorMovementController::controlCrouch() {
+ m_controlCrouch = true;
+}
+
+void ActorMovementController::controlJump(bool jumpEvenIfUnable) {
+ m_controlJump = true;
+ m_controlJumpAnyway |= jumpEvenIfUnable;
+}
+
+void ActorMovementController::controlFly(Vec2F const& velocity) {
+ m_controlFly = velocity;
+}
+
+Maybe<pair<Vec2F, bool>> ActorMovementController::pathMove(Vec2F const& position, bool run, Maybe<PlatformerAStar::Parameters> const& parameters) {
+ if (!m_pathController)
+ m_pathController = make_shared<PathController>(world());
+
+ // set new parameters if they have changed
+ if (m_pathController->targetPosition().isNothing() || (parameters && m_pathController->parameters() != *parameters)) {
+ if (parameters)
+ m_pathController->setParameters(*parameters);
+ m_pathMoveResult = m_pathController->findPath(*this, position).apply([position](bool result) { return pair<Vec2F, bool>(position, result); });
+ }
+
+ // update target position if it has changed
+ if (m_pathController) {
+ m_pathController->findPath(*this, position);
+ }
+
+ if (m_pathMoveResult.isValid()) {
+ // path controller failed or succeeded, return the result and reset the controller
+ m_pathController->reset();
+ }
+
+ return take(m_pathMoveResult);
+}
+
+Maybe<pair<Vec2F, bool>> ActorMovementController::controlPathMove(Vec2F const& position, bool run, Maybe<PlatformerAStar::Parameters> const& parameters) {
+ auto result = pathMove(position, run, parameters);
+
+ if (result.isNothing())
+ m_controlPathMove = pair<Vec2F, bool>(position, run);
+
+ return result;
+}
+
+void ActorMovementController::clearControls() {
+ m_controlRotationRate = 0.0f;
+ m_controlAcceleration = Vec2F();
+ m_controlForce = Vec2F();
+ m_controlApproachVelocities.clear();
+ m_controlApproachVelocityAlongAngles.clear();
+ m_controlMove = {};
+ m_controlFace = {};
+ m_controlRun = false;
+ m_controlCrouch = false;
+ m_controlDown = false;
+ m_controlJump = false;
+ m_controlJumpAnyway = false;
+ m_controlFly = {};
+ m_controlPathMove = {};
+ m_controlParameters = ActorMovementParameters();
+ m_controlModifiers = ActorMovementModifiers();
+}
+
+void ActorMovementController::tickMaster() {
+ EntityAnchorConstPtr newAnchor;
+ if (auto anchorState = m_anchorState.get()) {
+ if (auto anchorableEntity = as<AnchorableEntity>(world()->entity(anchorState->entityId)))
+ newAnchor = anchorableEntity->anchor(anchorState->positionIndex);
+ }
+
+ if (newAnchor)
+ m_entityAnchor = newAnchor;
+ else
+ resetAnchorState();
+
+ if (m_entityAnchor) {
+ m_walking.set(false);
+ m_running.set(false);
+ m_crouching.set(false);
+ m_flying.set(false);
+ m_falling.set(false);
+ m_canJump.set(false);
+ m_jumping.set(false);
+ m_groundMovement.set(false);
+ m_liquidMovement.set(false);
+
+ setVelocity((m_entityAnchor->position - MovementController::position()) / WorldTimestep);
+ MovementController::tickMaster();
+ setPosition(m_entityAnchor->position);
+ } else {
+ auto activeParameters = m_baseParameters.merge(m_controlParameters);
+ auto activeModifiers = m_baseModifiers.combine(m_controlModifiers);
+
+ if (activeModifiers.movementSuppressed) {
+ m_controlMove = {};
+ m_controlRun = false;
+ m_controlCrouch = false;
+ m_controlDown = false;
+ m_controlJump = false;
+ m_controlFly = {};
+ m_controlPathMove = {};
+ }
+
+ if (m_controlMove.isValid()
+ || m_controlCrouch
+ || m_controlDown
+ || m_controlJump
+ || m_controlFly.isValid()
+ || !m_controlApproachVelocities.empty()
+ || !m_controlApproachVelocityAlongAngles.empty()) {
+ // controlling any other movement overrides the pathing
+ m_controlPathMove.reset();
+ }
+
+ if (m_controlPathMove && m_pathMoveResult.isNothing()) {
+ if (appliedForceRegion()) {
+ m_pathController->reset();
+ } else if (!m_pathController->pathfinding()) {
+ m_pathMoveResult = m_pathController->move(*this, activeParameters, activeModifiers, m_controlPathMove->second, WorldTimestep)
+ .apply([this](bool result) { return pair<Vec2F, bool>(m_controlPathMove->first, result); });
+
+ auto action = m_pathController->curAction();
+ bool onGround = false;
+ if (auto a = action) {
+ using namespace PlatformerAStar;
+ m_walking.set(a == Action::Walk && !m_controlPathMove->second);
+ m_running.set(a == Action::Walk && m_controlPathMove->second);
+ m_flying.set(a == Action::Fly || a == Action::Swim);
+ m_falling.set((a == Action::Arc && yVelocity() < 0.0f) || a == Action::Drop);
+ m_jumping.set(a == Action::Arc && yVelocity() >= 0.0f);
+
+ onGround = a == Action::Walk || a == Action::Drop || a == Action::Jump;
+
+ if (a == Action::Land || a== Action::Jump) {
+ auto inLiquid = liquidPercentage() >= activeParameters.minimumLiquidPercentage.value(1.0);
+ m_liquidMovement.set(inLiquid);
+ m_groundMovement.set(!inLiquid);
+ onGround = !inLiquid && onGround;
+ } else {
+ m_liquidMovement.set(a == Action::Swim);
+ m_groundMovement.set(a != Action::Arc && a != Action::Swim);
+ }
+ } else {
+ m_walking.set(false);
+ m_running.set(false);
+ }
+ auto facing = m_controlFace.orMaybe(m_pathController->facing()).value(m_facingDirection.get());
+ m_facingDirection.set(facing);
+ m_movingDirection.set(m_pathController->facing().value(m_facingDirection.get()));
+
+ applyMCParameters(activeParameters);
+
+ // MovementController still handles updating liquid percentage and updating force regions
+ updateLiquidPercentage();
+ updateForceRegions();
+ // onGround flag needs to be manually set, won't be set by MovementController::tickMaster
+ setOnGround(onGround);
+ clearControls();
+ return;
+ } else {
+ m_pathMoveResult = m_pathController->findPath(*this, m_controlPathMove->first).apply([this](bool result) {
+ return pair<Vec2F, bool>(m_controlPathMove->first, result);
+ });
+ }
+ } else {
+ m_pathController = {};
+ }
+
+ // Do some basic movement consistency checks.
+ if (m_controlFly)
+ m_controlMove = {};
+
+ if ((m_controlDown && !m_lastControlDown) || m_controlFly)
+ m_fallThroughSustain = *activeParameters.fallThroughSustainFrames;
+ else if (m_fallThroughSustain > 0)
+ --m_fallThroughSustain;
+
+ applyMCParameters(activeParameters);
+
+ m_targetHorizontalAmbulatingVelocity = 0.0f;
+
+ rotate(m_controlRotationRate);
+ accelerate(m_controlAcceleration);
+ force(m_controlForce);
+
+ for (auto const& approach : m_controlApproachVelocities)
+ approachVelocity(approach.targetVelocity * activeModifiers.speedModifier, approach.maxControlForce);
+
+ for (auto const& approach : m_controlApproachVelocityAlongAngles)
+ approachVelocityAlongAngle(approach.alongAngle, approach.targetVelocity * activeModifiers.speedModifier, approach.maxControlForce, approach.positiveOnly);
+
+ m_liquidMovement.set(liquidPercentage() >= *activeParameters.minimumLiquidPercentage);
+ float liquidImpedance = *activeParameters.liquidImpedance * liquidPercentage();
+
+ Maybe<Direction> updatedMovingDirection;
+ bool running = m_controlRun && !activeModifiers.runningSuppressed;
+
+ if (m_controlFly) {
+ Vec2F flyVelocity = *m_controlFly;
+ if (flyVelocity.magnitudeSquared() != 0)
+ flyVelocity = flyVelocity.normalized() * *activeParameters.flySpeed;
+
+ if (m_liquidMovement.get())
+ approachVelocity(flyVelocity * (1.0f - liquidImpedance) * activeModifiers.speedModifier, *activeParameters.liquidForce * activeModifiers.liquidMovementModifier);
+ else
+ approachVelocity(flyVelocity * activeModifiers.speedModifier, *activeParameters.airForce);
+
+ if (flyVelocity[0] > 0)
+ updatedMovingDirection = Direction::Right;
+ else if (flyVelocity[0] < 0)
+ updatedMovingDirection = Direction::Left;
+
+ m_groundMovementSustainTimer = GameTimer(0);
+
+ } else {
+ float jumpModifier;
+ ActorJumpProfile jumpProfile;
+ if (m_liquidMovement.get()) {
+ jumpModifier = activeModifiers.liquidJumpModifier;
+ jumpProfile = activeParameters.liquidJumpProfile;
+ *jumpProfile.jumpSpeed *= (1.0f - liquidImpedance);
+ } else {
+ jumpModifier = activeModifiers.airJumpModifier;
+ jumpProfile = activeParameters.airJumpProfile;
+ }
+
+ bool startJump = false;
+ bool holdJump = false;
+
+ // If we are on the ground, then reset the ground movement sustain timer
+ // to the maximum. If we are not on the ground or near the ground
+ // according to the nearGroundCheckDistance, and we are past the minimum
+ // sustain time, then go ahead and immediately clear the ground movement
+ // sustain timer.
+ float minGroundSustain = *activeParameters.groundMovementMinimumSustain;
+ float maxGroundSustain = *activeParameters.groundMovementMaximumSustain;
+ float groundCheckDistance = *activeParameters.groundMovementCheckDistance;
+ m_groundMovementSustainTimer.tick();
+ if (onGround()) {
+ m_groundMovementSustainTimer = GameTimer(maxGroundSustain);
+ } else if (!m_groundMovementSustainTimer.ready() && groundCheckDistance > 0.0f && maxGroundSustain - m_groundMovementSustainTimer.timer > minGroundSustain) {
+ PolyF collisionBody = MovementController::collisionBody();
+ collisionBody.translate(Vec2F(0, -groundCheckDistance));
+ if (!world()->polyCollision(collisionBody, {CollisionKind::Block, CollisionKind::Dynamic, CollisionKind::Platform, CollisionKind::Slippery}))
+ m_groundMovementSustainTimer = GameTimer(0);
+ }
+
+ bool standingJumpable = !m_groundMovementSustainTimer.ready();
+ bool controlJump = m_controlJump && (!activeModifiers.jumpingSuppressed || m_controlJumpAnyway);
+
+ // We are doing a jump if m_reJumpTimer has run out and there has been a
+ // new m_controlJump command which was just recently triggered. If
+ // jumpProfile.autoJump is set, then we don't care whether it is a new
+ // m_controlJump command, m_controlJump can be held.
+ if (m_reJumpTimer.ready() && controlJump && (*jumpProfile.autoJump || !m_lastControlJump)) {
+ if (standingJumpable || *jumpProfile.multiJump || m_controlJumpAnyway)
+ startJump = true;
+ } else if (m_jumping.get() && controlJump && (!m_jumpHoldTimer || !m_jumpHoldTimer->ready())) {
+ if (!*jumpProfile.collisionCancelled || collisionCorrection()[1] >= 0.0f)
+ holdJump = true;
+ }
+
+ if (startJump) {
+ m_jumping.set(true);
+
+ m_reJumpTimer = GameTimer(*jumpProfile.reJumpDelay);
+ if (*jumpProfile.jumpHoldTime >= 0.0f)
+ m_jumpHoldTimer = GameTimer(*jumpProfile.jumpHoldTime);
+ else
+ m_jumpHoldTimer = {};
+
+ setYVelocity(yVelocity() + *jumpProfile.jumpSpeed * *jumpProfile.jumpInitialPercentage * jumpModifier);
+
+ m_groundMovementSustainTimer = GameTimer(0);
+
+ } else if (holdJump) {
+ m_reJumpTimer.tick();
+ if (m_jumpHoldTimer)
+ m_jumpHoldTimer->tick();
+
+ approachYVelocity(*jumpProfile.jumpSpeed * jumpModifier, *jumpProfile.jumpControlForce * jumpModifier);
+
+ } else {
+ m_jumping.set(false);
+ m_reJumpTimer.tick();
+ }
+
+ if (m_controlMove == Direction::Left) {
+ updatedMovingDirection = Direction::Left;
+ m_targetHorizontalAmbulatingVelocity =
+ -1.0f * (running ? *activeParameters.runSpeed * activeModifiers.speedModifier
+ : *activeParameters.walkSpeed * activeModifiers.speedModifier);
+ } else if (m_controlMove == Direction::Right) {
+ updatedMovingDirection = Direction::Right;
+ m_targetHorizontalAmbulatingVelocity =
+ 1.0f * (running ? *activeParameters.runSpeed * activeModifiers.speedModifier
+ : *activeParameters.walkSpeed * activeModifiers.speedModifier);
+ }
+
+ if (m_liquidMovement.get())
+ m_targetHorizontalAmbulatingVelocity *= (1.0f - liquidImpedance);
+
+ Vec2F surfaceVelocity = MovementController::surfaceVelocity();
+
+ // don't ambulate if we're already moving faster than the target velocity in the direction of ambulation
+ bool ambulationWouldAccelerate = abs(m_targetHorizontalAmbulatingVelocity + surfaceVelocity[0]) > abs(xVelocity())
+ || (m_targetHorizontalAmbulatingVelocity < 0) != (xVelocity() < 0);
+
+ if (m_targetHorizontalAmbulatingVelocity != 0.0f && ambulationWouldAccelerate) {
+ float ambulatingAccel;
+ if (onGround())
+ ambulatingAccel = *activeParameters.groundForce * activeModifiers.groundMovementModifier;
+ else if (m_liquidMovement.get())
+ ambulatingAccel = *activeParameters.liquidForce * activeModifiers.liquidMovementModifier;
+ else
+ ambulatingAccel = *activeParameters.airForce;
+
+ approachXVelocity(m_targetHorizontalAmbulatingVelocity + surfaceVelocity[0], ambulatingAccel);
+ }
+ }
+
+ if (updatedMovingDirection)
+ m_movingDirection.set(*updatedMovingDirection);
+
+ if (!activeModifiers.facingSuppressed) {
+ if (m_controlFace)
+ m_facingDirection.set(*m_controlFace);
+ else if (updatedMovingDirection)
+ m_facingDirection.set(*updatedMovingDirection);
+ else if (m_controlPathMove && m_pathController && m_pathController->facing())
+ m_facingDirection.set(*m_pathController->facing());
+ }
+
+ m_groundMovement.set(!m_groundMovementSustainTimer.ready());
+ if (m_groundMovement.get()) {
+ m_running.set(running && m_controlMove);
+ m_walking.set(!running && m_controlMove);
+ m_crouching.set(m_controlCrouch && !m_controlMove);
+ }
+ m_flying.set((bool)m_controlFly);
+
+ bool falling = (yVelocity() < *activeParameters.fallStatusSpeedMin) && !m_groundMovement.get();
+ m_falling.set(falling);
+
+ MovementController::tickMaster();
+
+ m_lastControlJump = m_controlJump;
+ m_lastControlDown = m_controlDown;
+
+ if (m_liquidMovement.get())
+ m_canJump.set(m_reJumpTimer.ready() && (!m_groundMovementSustainTimer.ready() || *activeParameters.liquidJumpProfile.multiJump));
+ else
+ m_canJump.set(m_reJumpTimer.ready() && (!m_groundMovementSustainTimer.ready() || *activeParameters.airJumpProfile.multiJump));
+ }
+
+ clearControls();
+}
+
+void ActorMovementController::tickSlave() {
+ MovementController::tickSlave();
+
+ m_entityAnchor.reset();
+ if (auto anchorState = m_anchorState.get()) {
+ if (auto anchorableEntity = as<AnchorableEntity>(world()->entity(anchorState->entityId)))
+ m_entityAnchor = anchorableEntity->anchor(anchorState->positionIndex);
+ }
+}
+
+void ActorMovementController::applyMCParameters(ActorMovementParameters const& parameters) {
+ MovementParameters mcParameters;
+
+ mcParameters.mass = parameters.mass;
+
+ if (!onGround() && yVelocity() < 0)
+ mcParameters.gravityMultiplier = *parameters.gravityMultiplier;
+ else
+ mcParameters.gravityMultiplier = parameters.gravityMultiplier;
+
+ mcParameters.liquidBuoyancy = parameters.liquidBuoyancy;
+ mcParameters.airBuoyancy = parameters.airBuoyancy;
+ mcParameters.bounceFactor = parameters.bounceFactor;
+ mcParameters.stopOnFirstBounce = parameters.stopOnFirstBounce;
+ mcParameters.enableSurfaceSlopeCorrection = parameters.enableSurfaceSlopeCorrection;
+ mcParameters.slopeSlidingFactor = parameters.slopeSlidingFactor;
+ mcParameters.maxMovementPerStep = parameters.maxMovementPerStep;
+
+ if (m_crouching.get())
+ mcParameters.collisionPoly = *parameters.crouchingPoly;
+ else
+ mcParameters.collisionPoly = *parameters.standingPoly;
+
+ mcParameters.stickyCollision = parameters.stickyCollision;
+ mcParameters.stickyForce = parameters.stickyForce;
+
+ mcParameters.airFriction = parameters.airFriction;
+ mcParameters.liquidFriction = parameters.liquidFriction;
+
+ // If we are traveling in the correct direction while in a movement mode that
+ // requires contact with the ground (ambulating i.e. walking or running), and
+ // not traveling faster than our target horizontal movement, then apply the
+ // special 'ambulatingGroundFriction'.
+ float relativeXVelocity = xVelocity() - surfaceVelocity()[0];
+ bool useAmbulatingGroundFriction = (m_walking.get() || m_running.get())
+ && copysign(1, m_targetHorizontalAmbulatingVelocity) == copysign(1, relativeXVelocity)
+ && fabs(relativeXVelocity) <= fabs(m_targetHorizontalAmbulatingVelocity);
+
+ if (useAmbulatingGroundFriction)
+ mcParameters.groundFriction = parameters.ambulatingGroundFriction;
+ else
+ mcParameters.groundFriction = parameters.normalGroundFriction;
+
+ mcParameters.collisionEnabled = parameters.collisionEnabled;
+ mcParameters.frictionEnabled = parameters.frictionEnabled;
+ mcParameters.gravityEnabled = parameters.gravityEnabled;
+
+ mcParameters.ignorePlatformCollision = m_fallThroughSustain > 0 || m_controlFly || m_controlDown;
+ mcParameters.maximumPlatformCorrection = parameters.maximumPlatformCorrection;
+ mcParameters.maximumPlatformCorrectionVelocityFactor = parameters.maximumPlatformCorrectionVelocityFactor;
+
+ mcParameters.physicsEffectCategories = parameters.physicsEffectCategories;
+
+ mcParameters.maximumCorrection = parameters.maximumCorrection;
+ mcParameters.speedLimit = parameters.speedLimit;
+
+ MovementController::applyParameters(mcParameters);
+}
+
+void ActorMovementController::doSetAnchorState(Maybe<EntityAnchorState> anchorState) {
+ EntityAnchorConstPtr entityAnchor;
+ if (anchorState) {
+ auto anchorableEntity = as<AnchorableEntity>(world()->entity(anchorState->entityId));
+ if (!anchorableEntity)
+ throw ActorMovementControllerException::format("No such anchorable entity id %s in ActorMovementController::setAnchorState", anchorState->entityId);
+ entityAnchor = anchorableEntity->anchor(anchorState->positionIndex);
+ if (!entityAnchor)
+ throw ActorMovementControllerException::format("Anchor position %s is disabled ActorMovementController::setAnchorState", anchorState->positionIndex);
+ }
+
+ if (!entityAnchor && m_entityAnchor && m_entityAnchor->exitBottomPosition) {
+ auto boundBox = MovementController::localBoundBox();
+ Vec2F bottomMid = {boundBox.center()[0], boundBox.yMin()};
+ setPosition(*m_entityAnchor->exitBottomPosition - bottomMid);
+ }
+
+ m_anchorState.set(anchorState);
+ m_entityAnchor = move(entityAnchor);
+
+ if (m_entityAnchor)
+ setPosition(m_entityAnchor->position);
+}
+
+
+PathController::PathController(World* world)
+ : m_world(world), m_edgeTimer(0.0) { }
+
+PlatformerAStar::Parameters const& PathController::parameters() {
+ return m_parameters;
+}
+
+void PathController::setParameters(PlatformerAStar::Parameters const& parameters) {
+ m_parameters = parameters;
+}
+
+void PathController::reset() {
+ m_startPosition = {};
+ m_targetPosition = {};
+ m_controlFace = {};
+ m_pathFinder = {};
+ m_path = {};
+ m_edgeIndex = 0;
+ m_edgeTimer = 0.0;
+}
+
+bool PathController::pathfinding() const {
+ return m_path.isNothing();
+}
+
+Maybe<Vec2F> PathController::targetPosition() const {
+ return m_targetPosition;
+}
+
+Maybe<Direction> PathController::facing() const {
+ return m_controlFace;
+}
+
+Maybe<PlatformerAStar::Action> PathController::curAction() const {
+ if (m_path && m_edgeIndex < m_path->size()) {
+ return m_path->at(m_edgeIndex).action;
+ }
+ return {};
+}
+
+Maybe<bool> PathController::findPath(ActorMovementController& movementController, Vec2F const& targetPosition) {
+ using namespace PlatformerAStar;
+
+ // reached the end of the last path and we have a new target position to move toward
+ if (m_path && m_edgeIndex == m_path->size() && m_world->geometry().diff(*m_targetPosition, targetPosition).magnitude() > 0.001) {
+ reset();
+ m_targetPosition = targetPosition;
+ }
+
+ // starting a new path, or the target position moved by more than 2 blocks
+ if (!m_targetPosition || (!m_path && !m_pathFinder) || m_world->geometry().diff(*m_targetPosition, targetPosition).magnitude() > 2.0) {
+ auto grounded = movementController.onGround();
+ if (m_path) {
+ // if already moving on a path, collision will be disabled and we can't use MovementController::onGround() to check for ground collision
+ auto const groundCollision = CollisionSet{ CollisionKind::Null, CollisionKind::Block, CollisionKind::Slippery, CollisionKind::Platform };
+ grounded = onGround(movementController, movementController.position(), groundCollision);
+ }
+ if (*movementController.parameters().gravityEnabled && !grounded && !movementController.liquidMovement()) {
+ return {};
+ }
+ m_startPosition = movementController.position();
+ m_targetPosition = targetPosition;
+ m_pathFinder = make_shared<PathFinder>(m_world, movementController.position(), *m_targetPosition, movementController.baseParameters(), m_parameters);
+ }
+
+ if (!m_pathFinder && m_path && m_edgeIndex == m_path->size())
+ return true; // Reached goal
+
+ if (m_pathFinder) {
+ auto explored = m_pathFinder->explore(movementController.baseParameters().pathExploreRate.value(100.0));
+ if (explored) {
+ auto pathfinder = m_pathFinder;
+ m_pathFinder = {};
+
+ if (*explored) {
+ auto path = *pathfinder->result();
+
+ float newEdgeTimer = 0.0;
+ float newEdgeIndex = 0.0;
+
+ // if we have a path already, see if our paths can be merged either by splicing or fast forwarding
+ bool merged = false;
+ if (m_path && !path.empty()) {
+ // try to fast forward on the new path
+ size_t i = 0;
+ // fast forward from current edge, or the last edge of the path
+ auto curEdgeIndex = min(m_edgeIndex, m_path->size() - 1);
+ auto& curEdge = m_path->at(curEdgeIndex);
+ for (auto& edge : path) {
+ if (curEdge.action == edge.action && curEdge.source.position == edge.source.position && edge.target.position == edge.target.position) {
+ // fast forward on the new path
+ newEdgeTimer = m_edgeTimer;
+ newEdgeIndex = i;
+
+ merged = true;
+ break;
+ }
+ i++;
+ }
+
+ if (!merged) {
+ // try to splice the new path onto the current path
+ auto& newPathStart = path.at(0);
+ for (size_t i = m_edgeIndex; i < m_path->size(); ++i) {
+ auto& edge = m_path->at(i);
+ if (edge.target.position == newPathStart.source.position) {
+ // splice the new path onto our current path up to this index
+ auto newPath = m_path->slice(0, i + 1);
+ newPath.appendAll(path);
+ path = newPath;
+
+ newEdgeTimer = m_edgeTimer;
+ newEdgeIndex = m_edgeIndex;
+
+ merged = true;
+ break;
+ }
+ i++;
+ }
+ }
+ }
+
+ if (!merged && movementController.position() != *m_startPosition) {
+ // merging the paths failed, and the entity has moved from the path start position
+ // try to bridge the gap from the current position to the new path
+ auto bridgePathFinder = make_shared<PathFinder>(m_world, movementController.position(), *m_startPosition, movementController.baseParameters(), m_parameters);
+ auto explored = bridgePathFinder->explore(movementController.baseParameters().pathExploreRate.value(100.0));
+
+ if (explored && *explored) {
+ // concatenate the bridge path with the new path
+ auto newPath = *bridgePathFinder->result();
+ newPath.appendAll(path);
+ path = newPath;
+ } else {
+ // if the gap isn't bridged in a single tick, reset and start over
+ reset();
+ return {};
+ }
+ }
+
+ if(!path.empty() && !validateEdge(movementController, path[0])) {
+ // reset if the first edge is invalid
+ reset();
+ return false;
+ }
+
+ m_edgeTimer = newEdgeTimer;
+ m_edgeIndex = newEdgeIndex;
+ m_path = path;
+ if (m_path->empty())
+ return true;
+ } else {
+ reset();
+ return false;
+ }
+ }
+ }
+ return {};
+}
+
+Maybe<bool> PathController::move(ActorMovementController& movementController, ActorMovementParameters const& parameters, ActorMovementModifiers const& modifiers, bool run, float dt) {
+ using namespace PlatformerAStar;
+
+ // pathfind to a new target position in the background while moving on the current path
+ if (m_pathFinder && m_targetPosition)
+ findPath(movementController, *m_targetPosition);
+
+ if (!m_path)
+ return {};
+
+ m_controlFace = {};
+
+ while (m_edgeIndex < m_path->size()) {
+ auto& edge = m_path->at(m_edgeIndex);
+ Vec2F delta = m_world->geometry().diff(edge.target.position, edge.source.position);
+
+ Vec2F sourceVelocity;
+ Vec2F targetVelocity;
+ switch (edge.action) {
+ case Action::Jump:
+ if (modifiers.jumpingSuppressed) {
+ reset();
+ return {};
+ }
+ break;
+ case Action::Arc:
+ sourceVelocity = edge.source.velocity.value(Vec2F());
+ targetVelocity = edge.target.velocity.value(Vec2F());
+ break;
+ case Action::Drop:
+ targetVelocity = edge.target.velocity.value(Vec2F());
+ break;
+ case Action::Fly:
+ {
+ // accelerate along path using airForce
+ float angleFactor = movementController.velocity().normalized() * delta.normalized();
+ float speedAlongAngle = angleFactor * movementController.velocity().magnitude();
+ auto acc = parameters.airForce.value(0.0) / movementController.mass();
+ sourceVelocity = delta.normalized() * fmin(parameters.flySpeed.value(0.0), speedAlongAngle + acc * WorldTimestep);;
+ targetVelocity = sourceVelocity;
+ }
+ break;
+ case Action::Swim:
+ sourceVelocity = targetVelocity = delta.normalized() * parameters.flySpeed.value(0.0f) * (1.0f - parameters.liquidImpedance.value(0.0f));
+ break;
+ case Action::Walk:
+ sourceVelocity = delta.normalized() * (run ? parameters.runSpeed.value(0.0f) : parameters.walkSpeed.value(0.0f));
+ sourceVelocity *= modifiers.speedModifier;
+ targetVelocity = sourceVelocity;
+ break;
+ default: {}
+ }
+
+ Vec2F avgVelocity = (sourceVelocity + targetVelocity) / 2.0;
+ float avgSpeed = avgVelocity.magnitude();
+ auto edgeTime = avgSpeed > 0.0f ? delta.magnitude() / avgSpeed : 0.2;
+
+ auto edgeProgress = m_edgeTimer / edgeTime;
+ if (edgeProgress > 1.0f) {
+ m_edgeTimer -= edgeTime;
+ m_edgeIndex++;
+ if (m_edgeIndex < m_path->size()) {
+ if (!validateEdge(movementController, m_path->at(m_edgeIndex))) {
+ // Logger::info("Path invalidated on %s %s %s", ActionNames.getRight(nextEdge.action), nextEdge.source.position, nextEdge.target.position);
+ reset();
+ return {};
+ }
+ }
+ continue;
+ }
+
+ auto curVelocity = sourceVelocity + ((targetVelocity - sourceVelocity) * edgeProgress);
+ movementController.setVelocity(curVelocity);
+ auto movement = (curVelocity + sourceVelocity) / 2.0 * m_edgeTimer;
+ movementController.setPosition(edge.source.position + movement);
+
+ // Shows path and current step
+ // for (size_t i = 0; i < m_path->size(); i++) {
+ // auto debugEdge = m_path->get(i);
+ // SpatialLogger::logPoint("world", debugEdge.source.position, Color::Blue.toRgba());
+ // SpatialLogger::logPoint("world", debugEdge.target.position, Color::Blue.toRgba());
+ // SpatialLogger::logLine("world", debugEdge.source.position, debugEdge.target.position, Color::Blue.toRgba());
+
+ // if (i == m_edgeIndex) {
+ // Vec2F velocity = debugEdge.source.velocity.orMaybe(debugEdge.target.velocity).value({ 0.0, 0.0 });
+ // SpatialLogger::logPoint("world", debugEdge.source.position, Color::Yellow.toRgba());
+ // SpatialLogger::logLine("world", debugEdge.source.position, debugEdge.target.position, Color::Yellow.toRgba());
+ // SpatialLogger::logText("world", strf("%s %s", ActionNames.getRight(debugEdge.action), curVelocity), debugEdge.source.position, Color::Yellow.toRgba());
+ // }
+ // }
+
+ if (auto direction = directionOf(delta[0]))
+ m_controlFace = direction;
+
+ m_edgeTimer += dt;
+ return {};
+ }
+
+ if (auto lastEdge = m_path->maybeLast()) {
+ movementController.setPosition(lastEdge->target.position);
+ movementController.setVelocity(Vec2F());
+ }
+
+ // reached the end of the path, success unless we're also currently pathfinding to a new position
+ if (m_pathFinder)
+ return {};
+ else
+ return true;
+}
+
+bool PathController::validateEdge(ActorMovementController& movementController, PlatformerAStar::Edge const& edge) {
+ using namespace PlatformerAStar;
+
+ auto const groundCollision = CollisionSet{ CollisionKind::Null, CollisionKind::Block, CollisionKind::Slippery, CollisionKind::Platform };
+ auto const solidCollision = CollisionSet{ CollisionKind::Null, CollisionKind::Block, CollisionKind::Slippery };
+
+ auto const openDoors = [&](RectF const& bounds) {
+ auto objects = m_world->entityQuery(bounds, entityTypeFilter<Object>());
+ auto opened = objects.filtered([&](EntityPtr const& e) -> bool {
+ if (auto object = as<Object>(e)) {
+ if (object->isMaster()) {
+ auto arg = m_world->luaRoot()->luaEngine().createString("closedDoor");
+ auto res = object->callScript("hasCapability", LuaVariadic<LuaValue>{arg});
+ if (res && res->is<LuaBoolean>() && res->get<LuaBoolean>()){
+ m_world->sendEntityMessage(e->entityId(), "openDoor");
+ return true;
+ }
+ }
+ }
+ return false;
+ });
+ return !opened.empty();
+ };
+
+ auto poly = movementController.collisionPoly();
+ poly.translate(edge.target.position);
+ if (m_world->polyCollision(poly) || movingCollision(movementController, poly)) {
+ auto bounds = RectI::integral(poly.boundBox());
+ // for (auto line : bounds.edges()) {
+ // SpatialLogger::logLine("world", Line2F(line), Color::Magenta.toRgba());
+ // }
+ if (m_world->rectTileCollision(bounds) && !m_world->rectTileCollision(bounds, solidCollision)) {
+ if (!openDoors(poly.boundBox())) {
+ // SpatialLogger::logPoly("world", poly, Color::Yellow.toRgba());
+ return false;
+ }
+ } else {
+ // SpatialLogger::logPoly("world", poly, Color::Red.toRgba());
+ return false;
+ }
+ }
+ //SpatialLogger::logPoly("world", poly, Color::Blue.toRgba());
+
+ auto inLiquid = [&](Vec2F const& position) -> bool {
+ auto bounds = movementController.localBoundBox().translated(position);
+ auto liquidLevel = m_world->liquidLevel(bounds);
+ if (liquidLevel.level >= movementController.baseParameters().minimumLiquidPercentage.value(1.0)) {
+ // for (auto line : bounds.edges()) {
+ // SpatialLogger::logLine("world", line, Color::Blue.toRgba());
+ // }
+ return true;
+ } else {
+ // for (auto line : bounds.edges()) {
+ // SpatialLogger::logLine("world", line, Color::Red.toRgba());
+ // }
+ return false;
+ }
+ };
+
+ switch (edge.action) {
+ case Action::Walk:
+ return onGround(movementController, edge.source.position, groundCollision);
+ case Action::Swim:
+ return inLiquid(edge.target.position);
+ case Action::Land:
+ return onGround(movementController, edge.target.position, groundCollision) || inLiquid(edge.target.position);
+ case Action::Drop:
+ return onGround(movementController, edge.source.position, groundCollision) && !onGround(movementController, edge.source.position, solidCollision);
+ default:
+ return true;
+ }
+}
+
+bool PathController::movingCollision(ActorMovementController& movementController, PolyF const& collisionPoly) {
+ bool collided = false;
+ movementController.forEachMovingCollision(collisionPoly.boundBox(), [&](MovingCollisionId id, PhysicsMovingCollision mc, PolyF poly, RectF bounds) {
+ if (poly.intersects(collisionPoly)) {
+ // set collided and stop iterating
+ collided = true;
+ return false;
+ }
+ return true;
+ });
+ return collided;
+}
+
+bool PathController::onGround(ActorMovementController const& movementController, Vec2F const& position, CollisionSet const& collisionSet) const {
+ auto bounds = RectI::integral(movementController.localBoundBox().translated(position));
+ Vec2I min = Vec2I(bounds.xMin(), bounds.yMin() - 1);
+ Vec2I max = Vec2I(bounds.xMax(), bounds.yMin());
+ // for (auto line : RectF(Vec2F(min), Vec2F(max)).edges()) {
+ // SpatialLogger::logLine("world", line, m_world->rectTileCollision(RectI(min, max), collisionSet) ? Color::Blue.toRgba() : Color::Red.toRgba());
+ // }
+ return m_world->rectTileCollision(RectI(min, max), collisionSet);
+}
+
+}