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<AppliedVelocity> appliedVelocities = new ObjectArrayList<AppliedVelocity>();
protected boolean isObstructed;
protected NavState navState;
protected double throttleDuration;
protected double targetDeltaSquared;
protected boolean recomputePath;
protected final Vector3d worldNormal;
protected final Vector3d worldAntiNormal;
protected final Vector3d componentSelector;
protected final Vector3d planarComponentSelector;
protected boolean enableTriggers;
protected boolean enableBlockDamage;
protected boolean isReceivingBlockDamage;
protected boolean isAvoidingBlockDamage;
protected boolean requiresPreciseMovement;
protected boolean requiresDepthProbing;
protected boolean havePreciseMovementTarget;
@Nonnull
protected Vector3d preciseMovementTarget;
protected boolean isRelaxedMoveConstraints;
protected boolean isBlendingHeading;
protected double blendHeading;
protected boolean haveBlendHeadingPosition;
@Nonnull
protected Vector3d blendHeadingPosition;
protected double blendLevelAtTargetPosition;
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;
private final Vector3d beforeTriggerPosition;
private boolean processTriggersHasMoved;
protected MovementSettings movementSettings;
public MotionControllerBase(@Nonnull BuilderSupport builderSupport, @Nonnull BuilderMotionControllerBase builder) {
this.worldNormal = Vector3d.UP;
this.worldAntiNormal = Vector3d.DOWN;
this.componentSelector = new Vector3d(1.0, 0.0, 1.0);
this.planarComponentSelector = new Vector3d(1.0, 0.0, 1.0);
this.enableTriggers = true;
this.enableBlockDamage = true;
this.isAvoidingBlockDamage = true;
this.preciseMovementTarget = new Vector3d();
this.blendHeadingPosition = new Vector3d();
this.blendLevelAtTargetPosition = 0.5;
this.beforeTriggerForce = new Vector3d();
this.beforeTriggerPosition = new Vector3d();
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);
}
public Role getRole() {
return this.role;
}
public void setRole(Role role) {
this.role = role;
}
public void setInertia(double inertia) {
this.inertia = Math.max(inertia, 1.0E-4);
}
public void setKnockbackScale(double knockbackScale) {
this.knockbackScale = Math.max(0.0, knockbackScale);
}
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 = (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 = (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 = ((EntityStore)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 = (WorldChunk)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;
}
}
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 = (NPCEntity)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 = (NPCEntity)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 = (NPCEntity)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;
}
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 = ((EntityStore)componentAccessor.getExternalData()).getWorld();
NPCEntity npcComponent = (NPCEntity)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 = (HeadRotation)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) {
AppliedVelocity entry = (AppliedVelocity)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) {
float var10000;
switch (entry.config.getStyle()) {
case Linear:
float len = (float)entry.velocity.length();
if (len < entry.config.getThreshold()) {
float mul = len / entry.config.getThreshold();
var10000 = min * mul + max * (1.0F - mul);
} else {
var10000 = min;
}
break;
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());
var10000 = min * mul + max * (1.0F - mul);
} else {
var10000 = min;
}
break;
default:
throw new MatchException((String)null, (Throwable)null);
}
resistance = var10000;
}
double resistanceScale = Math.pow((double)resistance, rate);
Vector3d var32 = entry.velocity;
var32.x *= resistanceScale;
var32 = entry.velocity;
var32.z *= resistanceScale;
if (dampenY) {
var32 = entry.velocity;
var32.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 = (HeadRotation)componentAccessor.getComponent(ref, HeadRotation.getComponentType());
assert headRotationComponent != null;
ModelComponent 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 = (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 = (HeadRotation)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 = (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 = ((EntityStore)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;
}
}
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) {
}
public boolean isInProgress() {
return false;
}
public boolean isObstructed() {
return this.isObstructed;
}
public NavState getNavState() {
return this.navState;
}
public double getThrottleDuration() {
return this.throttleDuration;
}
public double getTargetDeltaSquared() {
return this.targetDeltaSquared;
}
public void setNavState(NavState navState, double throttleDuration, double targetDeltaSquared) {
this.navState = navState;
this.throttleDuration = throttleDuration;
this.targetDeltaSquared = targetDeltaSquared;
}
public boolean isForceRecomputePath() {
return this.recomputePath;
}
public void setForceRecomputePath(boolean recomputePath) {
this.recomputePath = recomputePath;
}
public void beforeInstructionSensorsAndActions(double physicsTickDuration) {
this.recomputePath = false;
}
public void beforeInstructionMotion(double physicsTickDuration) {
this.resetNavState();
}
public boolean isHorizontalIdle(double speed) {
return speed == 0.0;
}
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 = (InteractionManager)componentAccessor.getComponent(ref, InteractionModule.get().getInteractionManagerComponent());
if (interactionManager != null) {
Boolean movementBlocked = (Boolean)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
public Vector3d getForce() {
return this.forceVelocity;
}
public void addForce(@Nonnull Vector3d force, VelocityConfig velocityConfig) {
double scale = this.knockbackScale;
if (!SplitVelocity.SHOULD_MODIFY_VELOCITY && velocityConfig != null) {
this.appliedVelocities.add(new 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;
}
}
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 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);
String var10000 = String.valueOf(blockRotation.yaw());
rotation = var10000 + " " + String.valueOf(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;
}
public boolean willReceiveBlockDamage() {
return this.isReceivingBlockDamage;
}
public void setAvoidingBlockDamage(boolean avoid) {
this.isAvoidingBlockDamage = avoid;
}
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 = (InteractionManager)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());
}
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;
}
public void deactivate() {
}
public double getEpsilonSpeed() {
return this.epsilonSpeed;
}
public float getEpsilonAngle() {
return this.epsilonAngle;
}
@Nonnull
public Vector3d getComponentSelector() {
return this.componentSelector;
}
@Nonnull
public Vector3d getPlanarComponentSelector() {
return this.planarComponentSelector;
}
public void setComponentSelector(@Nonnull Vector3d componentSelector) {
this.componentSelector.assign(componentSelector);
}
public Vector3d getWorldNormal() {
return this.worldNormal;
}
public Vector3d getWorldAntiNormal() {
return this.worldAntiNormal;
}
public double waypointDistance(@Nonnull Vector3d p, @Nonnull Vector3d q) {
return Math.sqrt(this.waypointDistanceSquared(p, q));
}
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;
}
public double waypointDistance(@Nonnull Ref<EntityStore> ref, @Nonnull Vector3d p, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
return Math.sqrt(this.waypointDistanceSquared(ref, p, componentAccessor));
}
public double waypointDistanceSquared(@Nonnull Ref<EntityStore> ref, @Nonnull Vector3d p, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
TransformComponent 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;
}
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 = ((EntityStore)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(), (Object)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;
}
public double getGravity() {
return this.gravity;
}
public void setGravity(double gravity) {
this.gravity = gravity;
}
public boolean translateToAccessiblePosition(Vector3d position, Box boundingBox, double minYValue, double maxYValue, ComponentAccessor<EntityStore> componentAccessor) {
return true;
}
public boolean standingOnBlockOfType(int blockSet) {
return false;
}
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;
}
public void requireDepthProbing() {
this.requiresDepthProbing = true;
}
public void clearRequireDepthProbing() {
this.requiresDepthProbing = false;
}
public boolean isRequiresDepthProbing() {
return this.requiresDepthProbing;
}
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;
}
public void enableHeadingBlending() {
this.enableHeadingBlending(0.0 / 0.0, (Vector3d)null, 0.0);
}
public void clearBlendHeading() {
this.isBlendingHeading = false;
this.haveBlendHeadingPosition = false;
}
public void setRelaxedMoveConstraints(boolean relax) {
this.isRelaxedMoveConstraints = relax;
}
public boolean isRelaxedMoveConstraints() {
return this.isRelaxedMoveConstraints;
}
public void updatePhysicsValues(PhysicsValues values) {
this.movementSettings = (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;
}
}
}