HyCodeYourTale
classpublicabstractPriority 3

MotionControllerBase

com.hypixel.hytale.server.npc.movement.controllers.MotionControllerBase

implements MotionController

68

Methods

68

Public Methods

56

Fields

1

Constructors

Constants

doubleBISECT_DIST= 0.05
booleanDEBUG_APPLIED_FORCES= false
doubleDEFAULT_BLOCK_DRAG= 0.82
doubleDOT_PRODUCT_EPSILON= 0.001
doubleFILTER_COEFFICIENT= 0.7
doubleFORCE_SCALE= 5.0
HytaleLoggerLOGGER= NPCPlugin.get().getLogger()

Constructors

public
MotionControllerBase(BuilderSupport builderSupport, BuilderMotionControllerBase builder)

Methods

Public Methods (68)

public
void activate()
@Override
public
void addForce(Vector3d force, VelocityConfig velocityConfig)
@Override
public
void beforeInstructionMotion(double physicsTickDuration)
@Override
public
void beforeInstructionSensorsAndActions(double physicsTickDuration)
@Override
public
boolean canAct(Ref<EntityStore> ref, ComponentAccessor<EntityStore> componentAccessor)
@Override
public
void clearBlendHeading()
public
void clearForce()
public
void clearRequireDepthProbing()
public
void clearRequirePreciseMovement()
public
void deactivate()
@Override
public
void enableHeadingBlending(double heading, Vector3d targetPosition, double blendLevel)
@Override
public
void enableHeadingBlending()
@Override
public
void forceVelocity(Vector3d velocity, VelocityConfig velocityConfig, boolean ignoreDamping)
@Override
public
Vector3d getComponentSelector()
@Nonnull@Override
public
float getEpsilonAngle()
public
double getEpsilonSpeed()
public
Vector3d getForce()
@Nonnull@Override
public
double getGravity()
@Override
public
int getInvalidOverlapMaterials()
public
MotionKind getMotionKind()
public
NavState getNavState()
@Override
public
float getPitch()
public
Vector3d getPlanarComponentSelector()
@Nonnull@Override
public
Role getRole()
@Override
public
float getRoll()
public
double getTargetDeltaSquared()
@Override
public
double getThrottleDuration()
@Override
public
Vector3d getWorldAntiNormal()
@Override
public
Vector3d getWorldNormal()
@Override
public
float getYaw()
public
boolean isAvoidingBlockDamage()
@Override
public
boolean isForceRecomputePath()
@Override
public
boolean isHorizontalIdle(double speed)
public
boolean isInProgress()
@Override
public
boolean isMovementBlocked(Ref<EntityStore> ref, ComponentAccessor<EntityStore> componentAccessor)
public
boolean isObstructed()
@Override
public
boolean isProcessTriggersHasMoved()
public
boolean isRelaxedMoveConstraints()
@Override
public
boolean isRequiresDepthProbing()
public
boolean isRequiresPreciseMovement()
public
boolean isValidPosition(Vector3d position, ComponentAccessor<EntityStore> componentAccessor)
@Override
public
void moveEntity(Ref<EntityStore> ref, double dt, ComponentAccessor<EntityStore> componentAccessor)
public
void postReadPosition(Ref<EntityStore> ref, ComponentAccessor<EntityStore> componentAccessor)
public
void requireDepthProbing()
@Override
public
void requirePreciseMovement(Vector3d positionHint)
@Override
public
void resetNavState()
public
void resetObstructedFlags()
public
void setAvoidingBlockDamage(boolean avoid)
@Override
public
void setComponentSelector(Vector3d componentSelector)
@Override
public
void setEnableBlockDamage(boolean enableBlockDamage)
public
void setEnableTriggers(boolean enableTriggers)
public
void setForceRecomputePath(boolean recomputePath)
@Override
public
void setGravity(double gravity)
public
void setInertia(double inertia)
@Override
public
void setKnockbackScale(double knockbackScale)
@Override
public
void setMotionKind(MotionKind motionKind)
public
void setNavState(NavState navState, double throttleDuration, double targetDeltaSquared)
@Override
public
void setRelaxedMoveConstraints(boolean relax)
@Override
public
void setRole(Role role)
@Override
public
boolean standingOnBlockOfType(int blockSet)
@Override
public
boolean touchesWater(boolean defaultValue, ComponentAccessor<EntityStore> componentAccessor)
public
void updateModelParameters(Ref<EntityStore> ref, Model model, Box boundingBox, ComponentAccessor<EntityStore> componentAccessor)
@Override
public
void updatePhysicsValues(PhysicsValues values)
@Override
public
double waypointDistance(Vector3d p, Vector3d q)
@Override
public
double waypointDistance(Ref<EntityStore> ref, Vector3d p, ComponentAccessor<EntityStore> componentAccessor)
@Override
public
double waypointDistanceSquared(Vector3d p, Vector3d q)
@Override
public
double waypointDistanceSquared(Ref<EntityStore> ref, Vector3d p, ComponentAccessor<EntityStore> componentAccessor)
@Override
public
boolean willReceiveBlockDamage()
@Override

Fields

Protected Fields (52)

protecteddouble blendHeading
protecteddouble blendLevelAtTargetPosition
protectedboolean cachedMovementBlocked
protectedboolean debugModeBlockCollisions
protectedboolean debugModeCollisions
protectedboolean debugModeMove
protectedboolean debugModeOverlaps
protectedboolean debugModeProbeBlockCollisions
protectedboolean debugModeSteer
protectedboolean debugModeValidateMath
protectedboolean debugModeValidatePositions
protecteddouble effectHorizontalSpeedMultiplier
protectedboolean enableBlockDamage
protectedboolean enableTriggers
protectedNPCEntity entity
protectedfloat epsilonAngle
protecteddouble epsilonSpeed
protectedboolean fastMotionKind
protecteddouble fastMotionThreshold
protecteddouble fastMotionThresholdRange
protecteddouble forceVelocityDamping
protecteddouble gravity
protectedboolean haveBlendHeadingPosition
protectedboolean havePreciseMovementTarget
protectedboolean horizontalIdleKind
protectedboolean idleMotionKind
protectedboolean ignoreDamping
protecteddouble inertia
protectedboolean isAvoidingBlockDamage
protectedboolean isBlendingHeading
protectedboolean isObstructed
protectedboolean isReceivingBlockDamage
protectedboolean isRelaxedMoveConstraints
protecteddouble knockbackScale
protectedMotionKind lastMovementStateUpdatedMotionKind
protectedfloat maxHeadRotationSpeed
protecteddouble maxHorizontalSpeed
protectedMotionKind motionKind
protecteddouble moveSpeed
protectedMovementSettings movementSettings
protectedNavState navState
protectedMotionKind previousMotionKind
protecteddouble previousSpeed
protectedboolean recomputePath
protectedboolean requiresDepthProbing
protectedboolean requiresPreciseMovement
protectedRole role
protecteddouble targetDeltaSquared
protecteddouble throttleDuration
protectedString type
protectedVector3d worldAntiNormal
protectedVector3d worldNormal

Private/Package Fields (4)

privatefloat pitch
privateboolean processTriggersHasMoved
privatefloat roll
privatefloat yaw

Inheritance

Parent
Current
Interface
Child

Use mouse wheel to zoom, drag to pan. Click nodes to navigate.

Related Classes

Source Code

package com.hypixel.hytale.server.npc.movement.controllers;

import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.protocol.GameMode;
import com.hypixel.hytale.protocol.MovementSettings;
import com.hypixel.hytale.protocol.MovementStates;
import com.hypixel.hytale.protocol.Rangef;
import com.hypixel.hytale.server.core.asset.modifiers.MovementEffects;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple;
import com.hypixel.hytale.server.core.asset.type.model.config.Model;
import com.hypixel.hytale.server.core.asset.type.model.config.camera.CameraSettings;
import com.hypixel.hytale.server.core.entity.InteractionManager;
import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager;
import com.hypixel.hytale.server.core.modules.collision.BlockCollisionData;
import com.hypixel.hytale.server.core.modules.collision.CollisionModule;
import com.hypixel.hytale.server.core.modules.collision.CollisionModuleConfig;
import com.hypixel.hytale.server.core.modules.collision.CollisionResult;
import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation;
import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.damage.Damage;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems;
import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent;
import com.hypixel.hytale.server.core.modules.interaction.InteractionModule;
import com.hypixel.hytale.server.core.modules.physics.component.PhysicsValues;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath;
import com.hypixel.hytale.server.core.modules.splitvelocity.SplitVelocity;
import com.hypixel.hytale.server.core.modules.splitvelocity.VelocityConfig;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.npc.movement.MotionKind;
import com.hypixel.hytale.server.npc.movement.NavState;
import com.hypixel.hytale.server.npc.movement.Steering;
import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerBase;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.npc.role.RoleDebugFlags;
import com.hypixel.hytale.server.npc.util.NPCPhysicsMath;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public abstract class MotionControllerBase implements MotionController {
   public static final double FORCE_SCALE = 5.0;
   public static final double BISECT_DIST = 0.05;
   public static final double FILTER_COEFFICIENT = 0.7;
   public static final double DOT_PRODUCT_EPSILON = 0.001;
   public static final double DEFAULT_BLOCK_DRAG = 0.82;
   protected static final HytaleLogger LOGGER = NPCPlugin.get().getLogger();
   public static final boolean DEBUG_APPLIED_FORCES = false;
   @Nonnull
   protected final NPCEntity entity;
   protected final String type;
   protected final double epsilonSpeed;
   protected final float epsilonAngle;
   protected final double forceVelocityDamping;
   protected final double maxHorizontalSpeed;
   protected final double fastMotionThreshold;
   protected final double fastMotionThresholdRange;
   protected final float maxHeadRotationSpeed;
   protected Role role;
   protected double inertia;
   protected double knockbackScale;
   protected double gravity;
   protected boolean debugModeSteer;
   protected boolean debugModeMove;
   protected boolean debugModeCollisions;
   protected boolean debugModeBlockCollisions;
   protected boolean debugModeProbeBlockCollisions;
   protected boolean debugModeValidatePositions;
   protected boolean debugModeOverlaps;
   protected boolean debugModeValidateMath;
   protected final Vector3d position = new Vector3d();
   protected final Box collisionBoundingBox = new Box();
   protected final CollisionResult collisionResult = new CollisionResult();
   protected final Vector3d translation = new Vector3d();
   protected final Vector3d bisectValidPosition = new Vector3d();
   protected final Vector3d bisectInvalidPosition = new Vector3d();
   protected final Vector3d lastValidPosition = new Vector3d();
   protected final Vector3d forceVelocity = new Vector3d();
   protected final Vector3d appliedForce = new Vector3d();
   protected boolean ignoreDamping;
   protected final List<MotionControllerBase.AppliedVelocity> appliedVelocities = new ObjectArrayList();
   protected boolean isObstructed;
   protected NavState navState;
   protected double throttleDuration;
   protected double targetDeltaSquared;
   protected boolean recomputePath;
   protected final Vector3d worldNormal = Vector3d.UP;
   protected final Vector3d worldAntiNormal = Vector3d.DOWN;
   protected final Vector3d componentSelector = new Vector3d(1.0, 0.0, 1.0);
   protected final Vector3d planarComponentSelector = new Vector3d(1.0, 0.0, 1.0);
   protected boolean enableTriggers = true;
   protected boolean enableBlockDamage = true;
   protected boolean isReceivingBlockDamage;
   protected boolean isAvoidingBlockDamage = true;
   protected boolean requiresPreciseMovement;
   protected boolean requiresDepthProbing;
   protected boolean havePreciseMovementTarget;
   @Nonnull
   protected Vector3d preciseMovementTarget = new Vector3d();
   protected boolean isRelaxedMoveConstraints;
   protected boolean isBlendingHeading;
   protected double blendHeading;
   protected boolean haveBlendHeadingPosition;
   @Nonnull
   protected Vector3d blendHeadingPosition = new Vector3d();
   protected double blendLevelAtTargetPosition = 0.5;
   protected boolean fastMotionKind;
   protected boolean idleMotionKind;
   protected boolean horizontalIdleKind;
   protected double moveSpeed;
   protected double previousSpeed;
   protected MotionKind motionKind;
   protected MotionKind lastMovementStateUpdatedMotionKind;
   protected MotionKind previousMotionKind;
   protected double effectHorizontalSpeedMultiplier;
   protected boolean cachedMovementBlocked;
   private float yaw;
   private float pitch;
   private float roll;
   private final Vector3d beforeTriggerForce = new Vector3d();
   private final Vector3d beforeTriggerPosition = new Vector3d();
   private boolean processTriggersHasMoved;
   protected MovementSettings movementSettings;

   public MotionControllerBase(@Nonnull BuilderSupport builderSupport, @Nonnull BuilderMotionControllerBase builder) {
      this.entity = builderSupport.getEntity();
      this.type = builder.getType();
      this.epsilonSpeed = builder.getEpsilonSpeed();
      this.epsilonAngle = builder.getEpsilonAngle();
      this.forceVelocityDamping = builder.getForceVelocityDamping();
      this.maxHorizontalSpeed = builder.getMaxHorizontalSpeed(builderSupport);
      this.fastMotionThreshold = builder.getFastHorizontalThreshold(builderSupport);
      this.fastMotionThresholdRange = builder.getFastHorizontalThresholdRange();
      this.maxHeadRotationSpeed = builder.getMaxHeadRotationSpeed(builderSupport);
      this.setInertia(1.0);
      this.setKnockbackScale(1.0);
      this.setGravity(10.0);
   }

   @Override
   public Role getRole() {
      return this.role;
   }

   @Override
   public void setRole(Role role) {
      this.role = role;
   }

   @Override
   public void setInertia(double inertia) {
      this.inertia = Math.max(inertia, 1.0E-4);
   }

   @Override
   public void setKnockbackScale(double knockbackScale) {
      this.knockbackScale = Math.max(0.0, knockbackScale);
   }

   @Override
   public void updateModelParameters(Ref<EntityStore> ref, Model model, @Nonnull Box boundingBox, ComponentAccessor<EntityStore> componentAccessor) {
      Objects.requireNonNull(boundingBox, "updateModelParameters: MotionController needs a bounding box");
      this.collisionBoundingBox.assign(boundingBox);
   }

   protected void readEntityPosition(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
      TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());

      assert transformComponent != null;

      Vector3f bodyRotation = transformComponent.getRotation();
      this.position.assign(transformComponent.getPosition());
      this.yaw = bodyRotation.getY();
      this.pitch = bodyRotation.getPitch();
      this.roll = bodyRotation.getRoll();
      this.adjustReadPosition(ref, componentAccessor);
      this.postReadPosition(ref, componentAccessor);
   }

   public void postReadPosition(Ref<EntityStore> ref, ComponentAccessor<EntityStore> componentAccessor) {
   }

   public void moveEntity(@Nonnull Ref<EntityStore> ref, double dt, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
      TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());

      assert transformComponent != null;

      this.adjustWritePosition(ref, dt, componentAccessor);
      Vector3f bodyRotation = transformComponent.getRotation();
      bodyRotation.setYaw(this.yaw);
      bodyRotation.setPitch(this.pitch);
      bodyRotation.setRoll(this.roll);
      this.entity.moveTo(ref, this.position.x, this.position.y, this.position.z, componentAccessor);
   }

   public float getYaw() {
      return this.yaw;
   }

   public float getPitch() {
      return this.pitch;
   }

   public float getRoll() {
      return this.roll;
   }

   public boolean touchesWater(boolean defaultValue, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
      World world = componentAccessor.getExternalData().getWorld();
      ChunkStore chunkStore = world.getChunkStore();
      long chunkIndex = ChunkUtil.indexChunkFromBlock(this.position.getX(), this.position.getZ());
      Ref<ChunkStore> chunkRef = chunkStore.getChunkReference(chunkIndex);
      if (chunkRef != null && chunkRef.isValid()) {
         WorldChunk worldChunkComponent = chunkStore.getStore().getComponent(chunkRef, WorldChunk.getComponentType());

         assert worldChunkComponent != null;

         int blockX = MathUtil.floor(this.position.getX());
         int blockY = MathUtil.floor(this.position.getY() + this.collisionBoundingBox.min.y);
         int blockZ = MathUtil.floor(this.position.getZ());
         int fluidId = worldChunkComponent.getFluidId(blockX, blockY, blockZ);
         return fluidId != 0;
      } else {
         return defaultValue;
      }
   }

   @Override
   public void updateMovementState(
      @Nonnull Ref<EntityStore> ref,
      @Nonnull MovementStates movementStates,
      @Nonnull Steering steering,
      @Nonnull Vector3d velocity,
      @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      boolean lastFastMotion = movementStates.running;
      movementStates.climbing = false;
      movementStates.swimJumping = false;
      movementStates.inFluid = this.touchesWater(movementStates.inFluid, componentAccessor);
      movementStates.onGround = this.role.isOnGround();
      double speed = this.waypointDistance(Vector3d.ZERO, velocity);
      speed = 0.7 * this.previousSpeed + 0.30000000000000004 * speed;
      this.previousSpeed = speed;
      this.fastMotionKind = this.isFastMotionKind(speed);
      this.idleMotionKind = steering.getTranslation().equals(Vector3d.ZERO);
      this.horizontalIdleKind = this.isHorizontalIdle(speed);
      if (this.motionKind != this.lastMovementStateUpdatedMotionKind
         || lastFastMotion != this.fastMotionKind
         || movementStates.idle != this.idleMotionKind
         || movementStates.horizontalIdle != this.horizontalIdleKind) {
         switch (this.motionKind) {
            case FLYING:
               this.updateFlyingStates(movementStates, this.idleMotionKind, this.fastMotionKind);
               break;
            case SWIMMING:
               this.updateSwimmingStates(movementStates, this.idleMotionKind, this.fastMotionKind, this.horizontalIdleKind);
               break;
            case SWIMMING_TURNING:
               this.updateSwimmingStates(movementStates, false, true, false);
               break;
            case ASCENDING:
               this.updateAscendingStates(ref, movementStates, this.fastMotionKind, this.horizontalIdleKind, componentAccessor);
               break;
            case MOVING:
               updateMovingStates(ref, movementStates, this.fastMotionKind, componentAccessor);
               break;
            case DESCENDING:
               NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType());

               assert npcComponent != null;

               this.updateDescendingStates(ref, movementStates, this.fastMotionKind, npcComponent.getHoverHeight() > 0.0, componentAccessor);
               break;
            case DROPPING:
               this.updateDroppingStates(movementStates);
               break;
            case STANDING:
            default:
               NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType());

               assert npcComponent != null;

               this.updateStandingStates(movementStates, this.motionKind, npcComponent.getHoverHeight() > 0.0);
         }
      }

      this.lastMovementStateUpdatedMotionKind = this.motionKind;
   }

   protected abstract boolean isFastMotionKind(double var1);

   protected void updateFlyingStates(@Nonnull MovementStates movementStates, boolean idle, boolean fastMotionKind) {
      movementStates.flying = true;
      movementStates.idle = idle;
      movementStates.horizontalIdle = false;
      movementStates.walking = !fastMotionKind;
      movementStates.running = fastMotionKind;
      movementStates.falling = false;
      movementStates.swimming = false;
      movementStates.jumping = false;
   }

   protected void updateSwimmingStates(@Nonnull MovementStates movementStates, boolean idle, boolean fastMotionKind, boolean horizontalIdleKind) {
      movementStates.flying = false;
      movementStates.idle = idle;
      movementStates.horizontalIdle = horizontalIdleKind;
      movementStates.walking = !fastMotionKind;
      movementStates.running = fastMotionKind;
      movementStates.falling = false;
      movementStates.swimming = true;
      movementStates.jumping = false;
   }

   protected static void updateMovingStates(
      @Nonnull Ref<EntityStore> ref, @Nonnull MovementStates movementStates, boolean fastMotionKind, @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType());

      assert npcComponent != null;

      movementStates.flying = npcComponent.getHoverHeight() > 0.0;
      movementStates.idle = false;
      movementStates.horizontalIdle = false;
      movementStates.falling = false;
      movementStates.walking = !fastMotionKind;
      movementStates.running = fastMotionKind;
      movementStates.swimming = false;
      movementStates.jumping = false;
   }

   protected void updateAscendingStates(
      @Nonnull Ref<EntityStore> ref,
      @Nonnull MovementStates movementStates,
      boolean fastMotionKind,
      boolean horizontalIdleKind,
      @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      updateMovingStates(ref, movementStates, fastMotionKind, componentAccessor);
   }

   protected void updateDescendingStates(
      @Nonnull Ref<EntityStore> ref,
      @Nonnull MovementStates movementStates,
      boolean fastMotionKind,
      boolean hovering,
      @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      updateMovingStates(ref, movementStates, fastMotionKind, componentAccessor);
   }

   protected void updateDroppingStates(@Nonnull MovementStates movementStates) {
      movementStates.falling = true;
   }

   protected void updateStandingStates(@Nonnull MovementStates movementStates, @Nonnull MotionKind motionKind, boolean hovering) {
      movementStates.flying = hovering;
      movementStates.idle = true;
      movementStates.horizontalIdle = true;
      movementStates.walking = false;
      movementStates.running = false;
      movementStates.falling = false;
      movementStates.swimming = false;
      movementStates.jumping = false;
   }

   @Override
   public double steer(
      @Nonnull Ref<EntityStore> ref,
      @Nonnull Role role,
      @Nonnull Steering bodySteering,
      @Nonnull Steering headSteering,
      double interval,
      @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      this.readEntityPosition(ref, componentAccessor);
      if (this.debugModeSteer) {
         double dx = this.position.x;
         double dz = this.position.z;
         double st = this.steer0(ref, role, bodySteering, headSteering, interval, componentAccessor);
         double t = interval - st;
         dx = this.position.x - dx;
         dz = this.position.z - dz;
         double l = Math.sqrt(dx * dx + dz * dz);
         double v = t > 0.0 ? l / t : 0.0;
         LOGGER.at(Level.INFO)
            .log(
               "==  Steer %s  = t =%.4f dt=%.4f h =%.4f l =%.4f v =%.4f motion=%s",
               this.getType(),
               interval,
               t,
               57.295776F * this.yaw,
               l,
               v,
               role.getSteeringMotionName()
            );
         return st;
      } else {
         return this.steer0(ref, role, bodySteering, headSteering, interval, componentAccessor);
      }
   }

   public double steer0(
      @Nonnull Ref<EntityStore> ref,
      @Nonnull Role role,
      @Nonnull Steering bodySteering,
      @Nonnull Steering headSteering,
      double interval,
      @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      World world = componentAccessor.getExternalData().getWorld();
      NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType());

      assert npcComponent != null;

      this.effectHorizontalSpeedMultiplier = (double)npcComponent.getCurrentHorizontalSpeedMultiplier(ref, componentAccessor);
      this.setAvoidingBlockDamage(this.isAvoidingBlockDamage && !this.isReceivingBlockDamage);
      this.translation.assign(0.0);
      this.cachedMovementBlocked = this.isMovementBlocked(ref, componentAccessor);
      this.computeMove(ref, role, bodySteering, interval, this.translation, componentAccessor);
      if (this.debugModeValidateMath && !NPCPhysicsMath.isValid(this.translation)) {
         throw new IllegalArgumentException(String.valueOf(this.translation));
      } else {
         if (this.translation.squaredLength() > 1000000.0) {
            if (this.debugModeValidateMath) {
               LOGGER.at(Level.WARNING)
                  .log("NPC with role %s has abnormal high speed! (Distance=%s, MotionController=%s)", role.getRoleName(), this.translation.length(), this.type);
            }

            this.translation.assign(Vector3d.ZERO);
         }

         this.executeMove(ref, role, interval, this.translation, componentAccessor);
         this.postExecuteMove();
         this.clearRequirePreciseMovement();
         this.clearRequireDepthProbing();
         this.clearBlendHeading();
         this.setAvoidingBlockDamage(!this.isReceivingBlockDamage);
         this.setRelaxedMoveConstraints(false);
         float maxBodyRotation = (float)(interval * this.getCurrentMaxBodyRotationSpeed() * bodySteering.getRelativeTurnSpeed());
         float maxHeadRotation = (float)(
            interval * (double)this.maxHeadRotationSpeed * headSteering.getRelativeTurnSpeed() * this.effectHorizontalSpeedMultiplier
         );
         this.calculateYaw(ref, bodySteering, headSteering, maxHeadRotation, maxBodyRotation, componentAccessor);
         this.calculatePitch(ref, bodySteering, headSteering, maxHeadRotation, componentAccessor);
         this.calculateRoll(bodySteering, headSteering);
         this.moveEntity(ref, interval, componentAccessor);
         HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType());

         assert headRotationComponent != null;

         Vector3f headRotation = headRotationComponent.getRotation();
         headRotation.setYaw(headSteering.getYaw());
         headRotation.setPitch(headSteering.getPitch());
         headRotation.setRoll(headSteering.getRoll());
         if (!this.forceVelocity.equals(Vector3d.ZERO) && !this.ignoreDamping) {
            double movementThresholdSquared = 1.0000000000000002E-10;
            if (this.forceVelocity.squaredLength() >= movementThresholdSquared) {
               this.dampForceVelocity(this.forceVelocity, this.forceVelocityDamping, interval, componentAccessor);
            } else {
               this.forceVelocity.assign(Vector3d.ZERO);
            }
         }

         double clientTps = 60.0;
         int serverTps = world.getTps();
         double rate = clientTps / (double)serverTps;
         boolean dampenY = this.shouldDampenAppliedVelocitiesY();
         boolean useGroundResistance = this.shouldAlwaysUseGroundResistance() || this.onGround();

         for (int i = 0; i < this.appliedVelocities.size(); i++) {
            MotionControllerBase.AppliedVelocity entry = this.appliedVelocities.get(i);
            float min;
            float max;
            if (useGroundResistance) {
               min = entry.config.getGroundResistance();
               max = entry.config.getGroundResistanceMax();
            } else {
               min = entry.config.getAirResistance();
               max = entry.config.getAirResistanceMax();
            }

            float resistance = min;
            if (max >= 0.0F) {
               resistance = switch (entry.config.getStyle()) {
                  case Linear -> {
                     float len = (float)entry.velocity.length();
                     if (len < entry.config.getThreshold()) {
                        float mul = len / entry.config.getThreshold();
                        yield min * mul + max * (1.0F - mul);
                     } else {
                        yield min;
                     }
                  }
                  case Exp -> {
                     float len = (float)entry.velocity.squaredLength();
                     if (len < entry.config.getThreshold() * entry.config.getThreshold()) {
                        float mul = len / (entry.config.getThreshold() * entry.config.getThreshold());
                        yield min * mul + max * (1.0F - mul);
                     } else {
                        yield min;
                     }
                  }
               };
            }

            double resistanceScale = Math.pow((double)resistance, rate);
            entry.velocity.x *= resistanceScale;
            entry.velocity.z *= resistanceScale;
            if (dampenY) {
               entry.velocity.y *= resistanceScale;
            }
         }

         this.appliedVelocities.removeIf(v -> v.velocity.squaredLength() < 0.001);
         return interval;
      }
   }

   protected boolean shouldDampenAppliedVelocitiesY() {
      return false;
   }

   protected boolean shouldAlwaysUseGroundResistance() {
      return false;
   }

   protected void calculateYaw(
      @Nonnull Ref<EntityStore> ref,
      @Nonnull Steering bodySteering,
      @Nonnull Steering headSteering,
      float maxHeadRotation,
      float maxBodyRotation,
      @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      if (bodySteering.hasYaw()) {
         this.yaw = bodySteering.getYaw();
      } else if (NPCPhysicsMath.dotProduct(this.translation.x, 0.0, this.translation.z) > 0.001) {
         this.yaw = PhysicsMath.headingFromDirection(this.translation.x, this.translation.z);
      }

      boolean hasHeadSteering = headSteering.hasYaw();
      if (!hasHeadSteering) {
         headSteering.setYaw(this.yaw);
      }

      HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType());

      assert headRotationComponent != null;

      ModelComponent modelComponent = componentAccessor.getComponent(ref, ModelComponent.getComponentType());

      assert modelComponent != null;

      Vector3f headRotation = headRotationComponent.getRotation();
      float currentYaw = headRotation.getYaw();
      float targetYaw = headSteering.getYaw();
      float turnAngle = MathUtil.clamp(NPCPhysicsMath.turnAngle(currentYaw, targetYaw), -maxHeadRotation, maxHeadRotation);
      headSteering.setYaw(PhysicsMath.normalizeTurnAngle(currentYaw + turnAngle));
      if (hasHeadSteering) {
         float yawOffset = MathUtil.wrapAngle(headSteering.getYaw() - this.yaw);
         CameraSettings headRotationRestrictions = modelComponent.getModel().getCamera();
         float yawMin;
         float yawMax;
         if (headRotationRestrictions != null && headRotationRestrictions.getYaw() != null && headRotationRestrictions.getYaw().getAngleRange() != null) {
            Rangef yawRange = headRotationRestrictions.getYaw().getAngleRange();
            yawMin = yawRange.min * 0.017453292F;
            yawMax = yawRange.max * 0.017453292F;
         } else {
            yawMin = -0.7853982F;
            yawMax = 0.7853982F;
         }

         if (yawOffset > yawMax) {
            float initialBodyYaw = this.yaw;
            if (!bodySteering.hasYaw()) {
               this.yaw = this.blendBodyYaw(ref, yawOffset, maxBodyRotation, componentAccessor);
            }

            headSteering.setYaw(MathUtil.wrapAngle(initialBodyYaw + yawMax));
         } else if (yawOffset < yawMin) {
            float initialBodyYaw = this.yaw;
            if (!bodySteering.hasYaw()) {
               this.yaw = this.blendBodyYaw(ref, yawOffset, maxBodyRotation, componentAccessor);
            }

            headSteering.setYaw(MathUtil.wrapAngle(initialBodyYaw + yawMin));
         }
      }
   }

   protected float blendBodyYaw(
      @Nonnull Ref<EntityStore> ref, float yawOffset, float maxBodyRotation, @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());

      assert transformComponent != null;

      Vector3f bodyRotation = transformComponent.getRotation();
      float currentBodyYaw = bodyRotation.getYaw();
      float targetBodyYaw = MathUtil.wrapAngle(this.yaw + yawOffset);
      float bodyTurnAngle = MathUtil.clamp(NPCPhysicsMath.turnAngle(currentBodyYaw, targetBodyYaw), -maxBodyRotation, maxBodyRotation);
      return MathUtil.wrapAngle(this.yaw + bodyTurnAngle);
   }

   protected void calculatePitch(
      @Nonnull Ref<EntityStore> ref,
      @Nonnull Steering bodySteering,
      @Nonnull Steering headSteering,
      float maxHeadRotation,
      @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      if (bodySteering.hasPitch()) {
         this.pitch = bodySteering.getPitch();
      } else if (NPCPhysicsMath.dotProduct(this.translation.x, this.translation.y, this.translation.z) > 0.001) {
         this.pitch = PhysicsMath.pitchFromDirection(this.translation.x, this.translation.y, this.translation.z);
      }

      boolean hasHeadSteering = headSteering.hasPitch();
      if (!hasHeadSteering) {
         headSteering.setPitch(this.pitch);
      }

      HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType());

      assert headRotationComponent != null;

      Vector3f headRotation = headRotationComponent.getRotation();
      float currentPitch = headRotation.getPitch();
      float targetPitch = headSteering.getPitch();
      float turnAngle = MathUtil.clamp(NPCPhysicsMath.turnAngle(currentPitch, targetPitch), -maxHeadRotation, maxHeadRotation);
      headSteering.setPitch(PhysicsMath.normalizeTurnAngle(currentPitch + turnAngle));
      if (hasHeadSteering) {
         ModelComponent modelComponent = componentAccessor.getComponent(ref, ModelComponent.getComponentType());

         assert modelComponent != null;

         float bodyPitch = this.pitch;
         float pitchOffset = MathUtil.wrapAngle(headSteering.getPitch() - bodyPitch);
         CameraSettings headRotationRestrictions = modelComponent.getModel().getCamera();
         float pitchMin;
         float pitchMax;
         if (headRotationRestrictions != null && headRotationRestrictions.getPitch() != null && headRotationRestrictions.getPitch().getAngleRange() != null) {
            Rangef pitchRange = headRotationRestrictions.getPitch().getAngleRange();
            pitchMin = pitchRange.min * 0.017453292F;
            pitchMax = pitchRange.max * 0.017453292F;
         } else {
            pitchMin = -0.7853982F;
            pitchMax = 0.7853982F;
         }

         if (pitchOffset > pitchMax) {
            headSteering.setPitch(MathUtil.wrapAngle(bodyPitch + pitchMax));
         } else if (pitchOffset < pitchMin) {
            headSteering.setPitch(MathUtil.wrapAngle(bodyPitch + pitchMin));
         }
      }
   }

   protected void calculateRoll(@Nonnull Steering bodySteering, @Nonnull Steering headSteering) {
      if (bodySteering.hasRoll()) {
         this.roll = bodySteering.getRoll();
      }

      if (!headSteering.hasRoll()) {
         headSteering.setRoll(this.roll);
      }
   }

   protected void dampForceVelocity(
      @Nonnull Vector3d forceVelocity, double forceVelocityDamping, double interval, ComponentAccessor<EntityStore> componentAccessor
   ) {
      World world = componentAccessor.getExternalData().getWorld();
      double drag = 0.0;
      if (this.motionKind != MotionKind.FLYING) {
         if (!this.onGround() && this.motionKind != MotionKind.SWIMMING && this.motionKind != MotionKind.SWIMMING_TURNING) {
            double horizontalSpeed = Math.sqrt(forceVelocity.x * forceVelocity.x + forceVelocity.z * forceVelocity.z);
            drag = convertToNewRange(
               horizontalSpeed,
               (double)this.movementSettings.airDragMinSpeed,
               (double)this.movementSettings.airDragMaxSpeed,
               (double)this.movementSettings.airDragMin,
               (double)this.movementSettings.airDragMax
            );
         } else {
            drag = 0.82;
         }
      }

      double clientTps = 60.0;
      int serverTps = world.getTps();
      double rate = 60.0 / (double)serverTps;
      drag = Math.pow(drag, rate);
      forceVelocity.x *= drag;
      forceVelocity.z *= drag;
      float velocityEpsilon = 0.1F;
      if (Math.abs(forceVelocity.x) <= (double)velocityEpsilon) {
         forceVelocity.x = 0.0;
      }

      if (Math.abs(forceVelocity.y) <= (double)velocityEpsilon) {
         forceVelocity.y = 0.0;
      }

      if (Math.abs(forceVelocity.z) <= (double)velocityEpsilon) {
         forceVelocity.z = 0.0;
      }
   }

   private static double convertToNewRange(double value, double oldMinRange, double oldMaxRange, double newMinRange, double newMaxRange) {
      if (newMinRange != newMaxRange && oldMinRange != oldMaxRange) {
         double newValue = (value - oldMinRange) * (newMaxRange - newMinRange) / (oldMaxRange - oldMinRange) + newMinRange;
         return MathUtil.clamp(newValue, Math.min(newMinRange, newMaxRange), Math.max(newMinRange, newMaxRange));
      } else {
         return newMinRange;
      }
   }

   @Override
   public double probeMove(
      @Nonnull Ref<EntityStore> ref,
      @Nonnull Vector3d position,
      @Nonnull Vector3d direction,
      @Nonnull ProbeMoveData probeMoveData,
      @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      probeMoveData.setPosition(position).setDirection(direction);
      return this.probeMove(ref, probeMoveData, componentAccessor);
   }

   protected void postExecuteMove() {
   }

   protected void adjustReadPosition(Ref<EntityStore> ref, ComponentAccessor<EntityStore> componentAccessor) {
   }

   protected void adjustWritePosition(Ref<EntityStore> ref, double dt, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
   }

   @Override
   public boolean isInProgress() {
      return false;
   }

   @Override
   public boolean isObstructed() {
      return this.isObstructed;
   }

   @Override
   public NavState getNavState() {
      return this.navState;
   }

   @Override
   public double getThrottleDuration() {
      return this.throttleDuration;
   }

   @Override
   public double getTargetDeltaSquared() {
      return this.targetDeltaSquared;
   }

   @Override
   public void setNavState(NavState navState, double throttleDuration, double targetDeltaSquared) {
      this.navState = navState;
      this.throttleDuration = throttleDuration;
      this.targetDeltaSquared = targetDeltaSquared;
   }

   @Override
   public boolean isForceRecomputePath() {
      return this.recomputePath;
   }

   @Override
   public void setForceRecomputePath(boolean recomputePath) {
      this.recomputePath = recomputePath;
   }

   @Override
   public void beforeInstructionSensorsAndActions(double physicsTickDuration) {
      this.recomputePath = false;
   }

   @Override
   public void beforeInstructionMotion(double physicsTickDuration) {
      this.resetNavState();
   }

   public boolean isHorizontalIdle(double speed) {
      return speed == 0.0;
   }

   @Override
   public boolean canAct(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
      return this.isAlive(ref, componentAccessor)
         && this.role.couldBreatheCached()
         && this.forceVelocity.equals(Vector3d.ZERO)
         && this.appliedVelocities.isEmpty();
   }

   public boolean isMovementBlocked(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
      InteractionManager interactionManager = componentAccessor.getComponent(ref, InteractionModule.get().getInteractionManagerComponent());
      if (interactionManager != null) {
         Boolean movementBlocked = interactionManager.forEachInteraction((chain, interaction, val) -> {
            if (val) {
               return Boolean.TRUE;
            } else {
               MovementEffects movementEffects = interaction.getEffects().getMovementEffects();
               return movementEffects != null ? movementEffects.isDisableAll() : Boolean.FALSE;
            }
         }, Boolean.FALSE);
         return movementBlocked;
      } else {
         return false;
      }
   }

   protected abstract double computeMove(
      @Nonnull Ref<EntityStore> var1, @Nonnull Role var2, Steering var3, double var4, Vector3d var6, @Nonnull ComponentAccessor<EntityStore> var7
   );

   protected abstract double executeMove(
      @Nonnull Ref<EntityStore> var1, @Nonnull Role var2, double var3, Vector3d var5, @Nonnull ComponentAccessor<EntityStore> var6
   );

   public <T> double bisect(
      @Nonnull Vector3d validPosition, @Nonnull Vector3d invalidPosition, @Nonnull T t, @Nonnull BiPredicate<T, Vector3d> validate, @Nonnull Vector3d result
   ) {
      return this.bisect(validPosition, invalidPosition, t, validate, 0.05, result);
   }

   public <T> double bisect(
      @Nonnull Vector3d validPosition,
      @Nonnull Vector3d invalidPosition,
      @Nonnull T t,
      @Nonnull BiPredicate<T, Vector3d> validate,
      double maxDistance,
      @Nonnull Vector3d result
   ) {
      double validDistance = 0.0;
      double invalidDistance = 1.0;
      this.bisectValidPosition.assign(validPosition);
      this.bisectInvalidPosition.assign(invalidPosition);
      maxDistance *= maxDistance;
      double validWeight = 0.1;
      double invalidWeight = 0.9;

      while (this.bisectValidPosition.distanceSquaredTo(this.bisectInvalidPosition) > maxDistance) {
         double distance = validWeight * validDistance + invalidWeight * invalidDistance;
         result.x = validWeight * this.bisectValidPosition.x + invalidWeight * this.bisectInvalidPosition.x;
         result.y = validWeight * this.bisectValidPosition.y + invalidWeight * this.bisectInvalidPosition.y;
         result.z = validWeight * this.bisectValidPosition.z + invalidWeight * this.bisectInvalidPosition.z;
         if (validate.test(t, result)) {
            validDistance = distance;
            this.bisectValidPosition.assign(result);
         } else {
            invalidDistance = distance;
            this.bisectInvalidPosition.assign(result);
            validWeight = 0.5;
            invalidWeight = 0.5;
         }
      }

      result.assign(this.bisectValidPosition);
      return validDistance;
   }

   @Nonnull
   @Override
   public Vector3d getForce() {
      return this.forceVelocity;
   }

   @Override
   public void addForce(@Nonnull Vector3d force, VelocityConfig velocityConfig) {
      double scale = this.knockbackScale;
      if (!SplitVelocity.SHOULD_MODIFY_VELOCITY && velocityConfig != null) {
         this.appliedVelocities.add(new MotionControllerBase.AppliedVelocity(new Vector3d(force.x * scale, force.y * scale, force.z * scale), velocityConfig));
      } else {
         double horzMul = 0.18000000000000005 * (double)this.movementSettings.velocityResistance;
         this.forceVelocity.add(force.x * scale * horzMul, force.y * scale, force.z * scale * horzMul);
         this.appliedForce.assign(this.forceVelocity);
         this.ignoreDamping = false;
      }
   }

   @Override
   public void forceVelocity(@Nonnull Vector3d velocity, @Nullable VelocityConfig velocityConfig, boolean ignoreDamping) {
      if (!SplitVelocity.SHOULD_MODIFY_VELOCITY && velocityConfig != null) {
         this.appliedVelocities.clear();
         this.appliedVelocities.add(new MotionControllerBase.AppliedVelocity(velocity.clone(), velocityConfig));
      } else {
         this.forceVelocity.assign(velocity);
         this.ignoreDamping = ignoreDamping;
      }
   }

   public void clearForce() {
      this.forceVelocity.assign(Vector3d.ZERO);
   }

   protected void dumpCollisionResults() {
      String slideString = "";
      if (this.collisionResult.isSliding) {
         slideString = String.format("SLIDE: start/end=%f/%f", this.collisionResult.slideStart, this.collisionResult.slideEnd);
      }

      LOGGER.at(Level.INFO)
         .log(
            "CollRes: pos=%s yaw=%f count=%d %s",
            Vector3d.formatShortString(this.position),
            57.295776F * this.yaw,
            this.collisionResult.getBlockCollisionCount(),
            slideString
         );
      if (this.collisionResult.getBlockCollisionCount() > 0) {
         for (int i = 0; i < this.collisionResult.getBlockCollisionCount(); i++) {
            BlockCollisionData cd = this.collisionResult.getBlockCollision(i);
            String materialName = cd.blockMaterial != null ? cd.blockMaterial.name() : "none";
            String typeName = cd.blockType != null ? cd.blockType.getId() : "none";
            String hitboxName = cd.blockType != null ? cd.blockType.getHitboxType() : "none";
            String rotation;
            if (cd.blockType != null) {
               RotationTuple blockRotation = RotationTuple.get(cd.rotation);
               rotation = blockRotation.yaw() + " " + blockRotation.pitch();
            } else {
               rotation = "none";
            }

            LOGGER.at(Level.INFO)
               .log(
                  "   COLL: blk=%s/%s/%s start=%f norm=%s pos=%s mat=%s block=%s hitbox=%s rot=%s",
                  cd.x,
                  cd.y,
                  cd.z,
                  cd.collisionStart,
                  Vector3d.formatShortString(cd.collisionNormal),
                  Vector3d.formatShortString(cd.collisionPoint),
                  materialName,
                  typeName,
                  hitboxName,
                  rotation
               );
         }
      }
   }

   public void setEnableTriggers(boolean enableTriggers) {
      this.enableTriggers = enableTriggers;
   }

   public void setEnableBlockDamage(boolean enableBlockDamage) {
      this.enableBlockDamage = enableBlockDamage;
   }

   @Override
   public boolean willReceiveBlockDamage() {
      return this.isReceivingBlockDamage;
   }

   @Override
   public void setAvoidingBlockDamage(boolean avoid) {
      this.isAvoidingBlockDamage = avoid;
   }

   @Override
   public boolean isAvoidingBlockDamage() {
      return this.isAvoidingBlockDamage;
   }

   public void processTriggers(
      @Nonnull Ref<EntityStore> ref, @Nonnull CollisionResult collisionResult, double t, @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      this.processTriggersHasMoved = false;
      this.isReceivingBlockDamage = false;
      if (this.enableTriggers || this.enableBlockDamage) {
         collisionResult.pruneTriggerBlocks(t);
         int count = collisionResult.getTriggerBlocks().size();
         if (count != 0) {
            if (this.enableTriggers) {
               this.beforeTriggerForce.assign(this.getForce());
               this.beforeTriggerPosition.assign(this.position);
            }

            this.moveEntity(ref, 0.0, componentAccessor);
            InteractionManager interactionManagerComponent = componentAccessor.getComponent(ref, InteractionModule.get().getInteractionManagerComponent());

            assert interactionManagerComponent != null;

            int damageToEntity = collisionResult.defaultTriggerBlocksProcessing(
               interactionManagerComponent, this.entity, ref, this.enableTriggers, componentAccessor
            );
            if (this.enableBlockDamage && damageToEntity > 0) {
               Damage damage = new Damage(Damage.NULL_SOURCE, DamageCause.ENVIRONMENT, (float)damageToEntity);
               DamageSystems.executeDamage(ref, componentAccessor, damage);
               this.isReceivingBlockDamage = true;
            }

            this.readEntityPosition(ref, componentAccessor);
            if (this.enableTriggers) {
               this.processTriggersHasMoved = !this.beforeTriggerForce.equals(this.getForce()) || !this.beforeTriggerPosition.equals(this.position);
            }
         }
      }
   }

   protected boolean isDebugMode(RoleDebugFlags mode) {
      return this.getRole() != null && this.getRole().getDebugSupport().getDebugFlags().contains(mode);
   }

   public boolean isProcessTriggersHasMoved() {
      return this.processTriggersHasMoved;
   }

   protected boolean isAlive(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
      return !componentAccessor.getArchetype(ref).contains(DeathComponent.getComponentType());
   }

   @Override
   public void activate() {
      this.debugModeSteer = this.isDebugMode(RoleDebugFlags.MotionControllerSteer);
      this.debugModeMove = this.isDebugMode(RoleDebugFlags.MotionControllerMove);
      this.debugModeCollisions = this.isDebugMode(RoleDebugFlags.Collisions);
      this.debugModeBlockCollisions = this.isDebugMode(RoleDebugFlags.BlockCollisions);
      this.debugModeProbeBlockCollisions = this.isDebugMode(RoleDebugFlags.ProbeBlockCollisions);
      this.debugModeValidatePositions = this.isDebugMode(RoleDebugFlags.ValidatePositions);
      this.debugModeOverlaps = this.isDebugMode(RoleDebugFlags.Overlaps);
      this.debugModeValidateMath = this.isDebugMode(RoleDebugFlags.ValidateMath);
      this.resetObstructedFlags();
      this.resetNavState();
   }

   public void resetNavState() {
      this.navState = NavState.AT_GOAL;
      this.throttleDuration = 0.0;
      this.targetDeltaSquared = 0.0;
   }

   public void resetObstructedFlags() {
      this.isObstructed = false;
   }

   @Override
   public void deactivate() {
   }

   public double getEpsilonSpeed() {
      return this.epsilonSpeed;
   }

   public float getEpsilonAngle() {
      return this.epsilonAngle;
   }

   @Nonnull
   @Override
   public Vector3d getComponentSelector() {
      return this.componentSelector;
   }

   @Nonnull
   @Override
   public Vector3d getPlanarComponentSelector() {
      return this.planarComponentSelector;
   }

   @Override
   public void setComponentSelector(@Nonnull Vector3d componentSelector) {
      this.componentSelector.assign(componentSelector);
   }

   @Override
   public Vector3d getWorldNormal() {
      return this.worldNormal;
   }

   @Override
   public Vector3d getWorldAntiNormal() {
      return this.worldAntiNormal;
   }

   @Override
   public double waypointDistance(@Nonnull Vector3d p, @Nonnull Vector3d q) {
      return Math.sqrt(this.waypointDistanceSquared(p, q));
   }

   @Override
   public double waypointDistanceSquared(@Nonnull Vector3d p, @Nonnull Vector3d q) {
      double dx = (p.x - q.x) * this.getComponentSelector().x;
      double dy = (p.y - q.y) * this.getComponentSelector().y;
      double dz = (p.z - q.z) * this.getComponentSelector().z;
      return dx * dx + dy * dy + dz * dz;
   }

   @Override
   public double waypointDistance(@Nonnull Ref<EntityStore> ref, @Nonnull Vector3d p, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
      return Math.sqrt(this.waypointDistanceSquared(ref, p, componentAccessor));
   }

   @Override
   public double waypointDistanceSquared(@Nonnull Ref<EntityStore> ref, @Nonnull Vector3d p, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
      TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());

      assert transformComponent != null;

      Vector3d position = transformComponent.getPosition();
      double dx = (p.x - position.getX()) * this.getComponentSelector().x;
      double dy = (p.y - position.getY()) * this.getComponentSelector().y;
      double dz = (p.z - position.getZ()) * this.getComponentSelector().z;
      return dx * dx + dy * dy + dz * dz;
   }

   @Override
   public boolean isValidPosition(@Nonnull Vector3d position, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
      return this.isValidPosition(position, this.collisionResult, componentAccessor);
   }

   public boolean isValidPosition(
      @Nonnull Vector3d position, @Nonnull CollisionResult collisionResult, @Nonnull ComponentAccessor<EntityStore> componentAccessor
   ) {
      World world = componentAccessor.getExternalData().getWorld();
      CollisionModule module = CollisionModule.get();
      CollisionModuleConfig config = module.getConfig();
      boolean saveDebugModeOverlaps = config.isDumpInvalidBlocks();
      config.setDumpInvalidBlocks(this.debugModeOverlaps);
      boolean isValid = module.validatePosition(
            world,
            this.collisionBoundingBox,
            position,
            this.getInvalidOverlapMaterials(),
            null,
            (_this, collisionCode, collision, collisionConfig) -> collisionConfig.blockId != -2147483648,
            collisionResult
         )
         != -1;
      config.setDumpInvalidBlocks(saveDebugModeOverlaps);
      return isValid;
   }

   public int getInvalidOverlapMaterials() {
      return 4;
   }

   protected void saveMotionKind() {
      this.previousMotionKind = this.getMotionKind();
   }

   protected boolean switchedToMotionKind(MotionKind motionKind) {
      return this.getMotionKind() == motionKind && this.previousMotionKind != motionKind;
   }

   public MotionKind getMotionKind() {
      return this.motionKind;
   }

   public void setMotionKind(MotionKind motionKind) {
      this.motionKind = motionKind;
   }

   @Override
   public double getGravity() {
      return this.gravity;
   }

   public void setGravity(double gravity) {
      this.gravity = gravity;
   }

   @Override
   public boolean translateToAccessiblePosition(
      Vector3d position, Box boundingBox, double minYValue, double maxYValue, ComponentAccessor<EntityStore> componentAccessor
   ) {
      return true;
   }

   @Override
   public boolean standingOnBlockOfType(int blockSet) {
      return false;
   }

   @Override
   public void requirePreciseMovement(@Nullable Vector3d positionHint) {
      this.requiresPreciseMovement = true;
      this.havePreciseMovementTarget = positionHint != null;
      if (this.havePreciseMovementTarget) {
         this.preciseMovementTarget.assign(positionHint);
      }
   }

   public void clearRequirePreciseMovement() {
      this.requiresPreciseMovement = false;
      this.havePreciseMovementTarget = false;
   }

   public boolean isRequiresPreciseMovement() {
      return this.requiresPreciseMovement;
   }

   @Override
   public void requireDepthProbing() {
      this.requiresDepthProbing = true;
   }

   public void clearRequireDepthProbing() {
      this.requiresDepthProbing = false;
   }

   public boolean isRequiresDepthProbing() {
      return this.requiresDepthProbing;
   }

   @Override
   public void enableHeadingBlending(double heading, @Nullable Vector3d targetPosition, double blendLevel) {
      this.isBlendingHeading = true;
      this.blendHeading = heading;
      this.haveBlendHeadingPosition = targetPosition != null;
      if (this.haveBlendHeadingPosition) {
         this.blendHeadingPosition.assign(targetPosition);
      }

      this.blendLevelAtTargetPosition = blendLevel;
   }

   @Override
   public void enableHeadingBlending() {
      this.enableHeadingBlending(0.0 / 0.0, null, 0.0);
   }

   public void clearBlendHeading() {
      this.isBlendingHeading = false;
      this.haveBlendHeadingPosition = false;
   }

   @Override
   public void setRelaxedMoveConstraints(boolean relax) {
      this.isRelaxedMoveConstraints = relax;
   }

   @Override
   public boolean isRelaxedMoveConstraints() {
      return this.isRelaxedMoveConstraints;
   }

   @Override
   public void updatePhysicsValues(PhysicsValues values) {
      this.movementSettings = MovementManager.MASTER_DEFAULT.apply(values, GameMode.Adventure);
   }

   protected static class AppliedVelocity {
      protected final Vector3d velocity;
      protected final VelocityConfig config;
      protected boolean canClear;

      public AppliedVelocity(Vector3d velocity, VelocityConfig config) {
         this.velocity = velocity;
         this.config = config;
      }
   }
}