// 
// Decompiled by Procyon v0.6.0
// 

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

import com.hypixel.hytale.server.core.modules.collision.BlockCollisionData;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.modules.collision.CollisionModule;
import java.util.logging.Level;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.modules.collision.WorldUtil;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath;
import com.hypixel.hytale.server.npc.util.NPCPhysicsMath;
import com.hypixel.hytale.server.npc.movement.MotionKind;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.npc.movement.Steering;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.util.TrigMathUtil;
import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerBase;
import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerFly;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import javax.annotation.Nullable;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.npc.util.PositionProbeAir;

public class MotionControllerFly extends MotionControllerBase
{
    public static final String TYPE = "Fly";
    public static final double DAMPING_FACTOR = 20.0;
    public static final int COLLISION_MATERIALS_PASSIVE = 4;
    public static final int COLLISION_MATERIALS_ACTIVE = 6;
    protected final double minAirSpeed;
    protected final double maxClimbSpeed;
    protected final double maxSinkSpeed;
    protected final double maxFallSpeed;
    protected final double maxSinkSpeedFluid;
    protected final float maxClimbAngle;
    protected final float maxSinkAngle;
    protected final double acceleration;
    protected final double deceleration;
    protected final double sinkRatio = 0.5;
    protected final double desiredAltitudeWeight;
    protected final float maxTurnSpeed;
    protected final float maxRollAngle;
    protected final float maxRollSpeed;
    protected final float rollDamping;
    protected final double fastFlyThreshold;
    protected final double minHeightOverGround;
    protected final double maxHeightOverGround;
    protected final boolean autoLevel;
    protected final double sinMaxClimbAngle;
    protected final double sinMaxSinkAngle;
    protected final MotionController.VerticalRange verticalRange;
    protected final PositionProbeAir moveProbe;
    protected final PositionProbeAir probeMoveProbe;
    protected int lastVerticalPositionX;
    protected int lastVerticalPositionZ;
    protected final Vector3d lastVelocity;
    protected double lastSpeed;
    protected float lastRoll;
    protected double currentRelativeSpeed;
    protected double minSpeedAfterForceSquared;
    @Nullable
    protected double[] desiredAltitudeOverride;
    
    public MotionControllerFly(@Nonnull final BuilderSupport builderSupport, @Nonnull final BuilderMotionControllerFly builder) {
        super(builderSupport, builder);
        this.verticalRange = new MotionController.VerticalRange();
        this.moveProbe = new PositionProbeAir();
        this.probeMoveProbe = new PositionProbeAir();
        this.lastVerticalPositionX = Integer.MIN_VALUE;
        this.lastVerticalPositionZ = Integer.MIN_VALUE;
        this.lastVelocity = new Vector3d();
        this.setGravity(builder.getGravity());
        this.componentSelector.assign(1.0, 1.0, 1.0);
        this.minAirSpeed = builder.getMinAirSpeed();
        this.maxClimbSpeed = builder.getMaxClimbSpeed();
        this.maxSinkSpeed = builder.getMaxSinkSpeed();
        this.maxFallSpeed = builder.getMaxFallSpeed();
        this.maxSinkSpeedFluid = builder.getMaxSinkSpeedFluid();
        this.maxClimbAngle = builder.getMaxClimbAngle();
        this.sinMaxClimbAngle = TrigMathUtil.sin(this.maxClimbAngle);
        this.maxSinkAngle = builder.getMaxSinkAngle();
        this.sinMaxSinkAngle = -TrigMathUtil.sin(this.maxSinkAngle);
        this.acceleration = builder.getAcceleration();
        this.deceleration = builder.getDeceleration();
        this.maxTurnSpeed = builder.getMaxTurnSpeed();
        this.maxRollAngle = builder.getMaxRollAngle();
        this.minHeightOverGround = builder.getMinHeightOverGround(builderSupport);
        this.maxHeightOverGround = builder.getMaxHeightOverGround(builderSupport);
        this.maxRollSpeed = builder.getMaxRollSpeed();
        this.rollDamping = builder.getRollDamping();
        this.fastFlyThreshold = builder.getFastFlyThreshold();
        this.autoLevel = builder.isAutoLevel();
        this.desiredAltitudeWeight = builder.getDesiredAltitudeWeight();
        this.minSpeedAfterForceSquared = MathUtil.minValue(this.maxHorizontalSpeed, this.maxSinkSpeed, this.maxClimbSpeed);
        this.minSpeedAfterForceSquared *= this.minSpeedAfterForceSquared;
    }
    
    @Nonnull
    @Override
    public String getType() {
        return "Fly";
    }
    
    @Override
    protected double computeMove(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, @Nonnull final Steering steering, final double dt, @Nonnull final Vector3d translation, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        this.saveMotionKind();
        this.setMotionKind(this.inWater() ? MotionKind.MOVING : MotionKind.FLYING);
        this.moveProbe.probePosition(ref, this.collisionBoundingBox, this.position, this.collisionResult, componentAccessor);
        this.currentRelativeSpeed = steering.getSpeed();
        if (!this.isAlive(ref, componentAccessor)) {
            this.forceVelocity.assign(Vector3d.ZERO);
            this.appliedVelocities.clear();
        }
        final double maxFallSpeed = this.moveProbe.isInWater() ? this.maxSinkSpeedFluid : this.maxFallSpeed;
        final boolean onGround = this.onGround();
        if (this.forceVelocity.equals(Vector3d.ZERO) && this.appliedVelocities.isEmpty()) {
            if (NPCPhysicsMath.near(this.lastVelocity, Vector3d.ZERO)) {
                PhysicsMath.vectorFromAngles(this.getYaw(), this.getPitch(), this.lastVelocity);
                this.lastSpeed = 0.0;
            }
            if (this.canAct(ref, componentAccessor)) {
                translation.assign(steering.getTranslation());
                double steeringSpeed = steering.hasTranslation() ? translation.length() : 0.0;
                final float yaw = PhysicsMath.normalizeAngle(this.getYaw());
                final float pitch = PhysicsMath.normalizeTurnAngle(this.getPitch());
                final double dirX = translation.x;
                final double dirZ = translation.z;
                final double dotXZ = dirX * dirX + dirZ * dirZ;
                float expYaw;
                float expPitch;
                if (dotXZ >= 1.0E-12) {
                    expYaw = PhysicsMath.headingFromDirection(dirX, dirZ);
                    expPitch = TrigMathUtil.atan2(translation.y, Math.sqrt(dotXZ));
                }
                else {
                    expYaw = (steering.hasYaw() ? steering.getYaw() : yaw);
                    expPitch = (steering.hasPitch() ? steering.getPitch() : (this.autoLevel ? 0.0f : pitch));
                }
                steering.clearYaw();
                steering.clearPitch();
                expPitch = MathUtil.clamp(expPitch, -this.maxSinkAngle, this.maxClimbAngle);
                float turnYaw = NPCPhysicsMath.turnAngle(yaw, expYaw);
                float turnPitch = NPCPhysicsMath.turnAngle(pitch, expPitch);
                final float maxRotationAngle = (float)(this.getCurrentMaxBodyRotationSpeed() * dt);
                turnYaw = NPCPhysicsMath.clampRotation(turnYaw, maxRotationAngle);
                turnPitch = NPCPhysicsMath.clampRotation(turnPitch, maxRotationAngle);
                final float newYaw = PhysicsMath.normalizeAngle(yaw + turnYaw);
                final float newPitch = PhysicsMath.normalizeTurnAngle(pitch + turnPitch);
                final double speedLimit = this.computeMaxSpeedFromPitch(pitch);
                steeringSpeed *= speedLimit;
                final double minSpeed = Math.max(this.minAirSpeed, this.lastSpeed - this.deceleration * dt);
                final double maxSpeed = this.lastSpeed + this.acceleration * dt;
                steeringSpeed = ((maxSpeed < minSpeed) ? minSpeed : MathUtil.clamp(steeringSpeed, minSpeed, maxSpeed));
                PhysicsMath.vectorFromAngles(newYaw, newPitch, translation);
                translation.normalize();
                final double mX = this.lastVelocity.z;
                final double mZ = -this.lastVelocity.x;
                final double mL = Math.sqrt(mX * mX + mZ * mZ);
                final float rollTurnCosine = (float)(NPCPhysicsMath.dotProduct(mX, 0.0, mZ, translation.x, translation.y, translation.z) / mL);
                final float maxRollTurnAngle = (float)(this.maxTurnSpeed * dt);
                final float maxRollTurnCosine = TrigMathUtil.sin(maxRollTurnAngle);
                final float rollTurnStrength = rollTurnCosine / maxRollTurnCosine;
                final double speedFactor = steeringSpeed / speedLimit;
                final float desiredRoll = this.maxRollAngle * MathUtil.clamp(rollTurnStrength, -1.0f, 1.0f) * MathUtil.clamp((float)speedFactor, 0.0f, 1.0f);
                final float dampedRoll = MathUtil.clamp(this.rollDamping * this.lastRoll + (1.0f - this.rollDamping) * desiredRoll, -this.maxRollAngle, this.maxRollAngle);
                final float deltaRoll = (float)(this.maxRollSpeed * dt);
                final float constrainedRoll = MathUtil.clamp(dampedRoll, this.lastRoll - deltaRoll, this.lastRoll + deltaRoll);
                this.lastRoll = constrainedRoll;
                steering.setYaw(newYaw);
                steering.setPitch(newPitch);
                steering.setRoll(constrainedRoll);
                if (steeringSpeed == 0.0) {
                    translation.assign(Vector3d.ZERO);
                }
                else {
                    translation.scale(steeringSpeed * this.effectHorizontalSpeedMultiplier);
                }
                this.lastVelocity.assign(translation);
                this.lastSpeed = steeringSpeed;
                translation.scale(dt);
                if (this.debugModeValidateMath && !NPCPhysicsMath.isValid(translation)) {
                    throw new IllegalArgumentException(String.valueOf(translation));
                }
            }
            else {
                steering.setYaw(this.getYaw());
                steering.setPitch(this.getPitch());
                steering.setRoll(this.getRoll());
                if (onGround) {
                    this.setMotionKind(MotionKind.STANDING);
                    this.lastVelocity.assign(Vector3d.ZERO);
                    this.lastSpeed = 0.0;
                    return dt;
                }
                this.setMotionKind(MotionKind.DROPPING);
                translation.y = NPCPhysicsMath.gravityDrag(this.lastVelocity.y, this.gravity, dt, maxFallSpeed);
                final double diffSpeed = maxFallSpeed - translation.y;
                if (diffSpeed <= 0.0 || this.isObstructed()) {
                    translation.x = 0.0;
                    translation.z = 0.0;
                }
                else {
                    double scale = translation.x * translation.x + translation.z * translation.z;
                    if (diffSpeed * diffSpeed < scale) {
                        scale = Math.sqrt(scale / diffSpeed);
                        translation.x = this.lastVelocity.x * scale;
                        translation.z = this.lastVelocity.z * scale;
                    }
                    else {
                        translation.x = this.lastVelocity.x;
                        translation.z = this.lastVelocity.z;
                    }
                }
                this.lastVelocity.assign(translation);
                this.lastSpeed = this.lastVelocity.length();
                translation.scale(dt);
                if (this.debugModeValidateMath && !NPCPhysicsMath.isValid(translation)) {
                    throw new IllegalArgumentException(String.valueOf(translation));
                }
            }
            if (this.lastSpeed > 1.0E-6 && this.isAlive(ref, componentAccessor)) {
                this.setDirectionFromTranslation(steering, translation);
            }
            return dt;
        }
        steering.setYaw(this.getYaw());
        steering.setPitch(this.getPitch());
        steering.setRoll(this.getRoll());
        if (!this.isObstructed()) {
            translation.assign(this.forceVelocity);
            for (int i = 0; i < this.appliedVelocities.size(); ++i) {
                final AppliedVelocity entry = this.appliedVelocities.get(i);
                if (entry.velocity.y + this.forceVelocity.y <= 0.0 || entry.velocity.y < 0.0) {
                    entry.canClear = true;
                }
                if (onGround && entry.canClear) {
                    entry.velocity.y = 0.0;
                }
                translation.add(entry.velocity);
            }
        }
        else {
            translation.assign(Vector3d.ZERO);
            this.appliedVelocities.clear();
            this.forceVelocity.assign(Vector3d.ZERO);
        }
        if (!onGround) {
            translation.y = NPCPhysicsMath.accelerateDrag(translation.y, -this.gravity, dt, maxFallSpeed);
        }
        this.lastVelocity.assign(translation);
        this.lastSpeed = this.lastVelocity.length();
        translation.scale(dt);
        if (this.debugModeValidateMath && !NPCPhysicsMath.isValid(translation)) {
            throw new IllegalArgumentException(String.valueOf(translation));
        }
        return dt;
    }
    
    private void setDirectionFromTranslation(@Nonnull final Steering steering, @Nonnull final Vector3d translation) {
        if (!steering.hasYaw()) {
            if (translation.x * translation.x + translation.z * translation.z > 1.0E-6) {
                steering.setYaw(PhysicsMath.headingFromDirection(translation.x, translation.z));
            }
            else {
                steering.setYaw(this.getYaw());
            }
        }
        if (!steering.hasPitch()) {
            steering.setPitch(PhysicsMath.pitchFromDirection(translation.x, translation.y, translation.z));
        }
    }
    
    @Override
    public double probeMove(@Nonnull final Ref<EntityStore> ref, @Nonnull final ProbeMoveData probeMoveData, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        return probeMoveData.probeDirection.length() * this.doMove(ref, probeMoveData.probePosition, probeMoveData.probeDirection, this.probeMoveProbe, probeMoveData, componentAccessor);
    }
    
    public boolean isFastMotionKind(final double speed) {
        return this.lastVelocity.y < -1.0E-6 || (this.currentRelativeSpeed > this.fastFlyThreshold && this.lastVelocity.y <= 1.0E-6);
    }
    
    @Override
    public MotionController.VerticalRange getDesiredVerticalRange(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        final Vector3d position = transformComponent.getPosition();
        final int ix = MathUtil.floor(position.getX());
        final int iz = MathUtil.floor(position.getZ());
        if (ix == this.lastVerticalPositionX && iz == this.lastVerticalPositionZ) {
            return this.verticalRange;
        }
        this.lastVerticalPositionX = ix;
        this.lastVerticalPositionZ = iz;
        final double y = position.getY();
        final World world = componentAccessor.getExternalData().getWorld();
        final ChunkStore chunkStore = world.getChunkStore();
        final long chunkIndex = ChunkUtil.indexChunkFromBlock(ix, iz);
        final Ref<ChunkStore> chunkRef = chunkStore.getChunkReference(chunkIndex);
        if (chunkRef == null || !chunkRef.isValid()) {
            this.verticalRange.assign(y, y, y);
            return this.verticalRange;
        }
        final Store<ChunkStore> chunkStoreAccessor = chunkStore.getStore();
        final ChunkColumn chunkColumnComponent = chunkStoreAccessor.getComponent(chunkRef, ChunkColumn.getComponentType());
        final BlockChunk blockChunkComponent = chunkStoreAccessor.getComponent(chunkRef, BlockChunk.getComponentType());
        if (chunkColumnComponent == null || blockChunkComponent == null) {
            this.verticalRange.assign(y, y, y);
            return this.verticalRange;
        }
        double minHeightOverGround;
        double maxHeightOverGround;
        if (this.desiredAltitudeOverride != null) {
            minHeightOverGround = this.desiredAltitudeOverride[0];
            maxHeightOverGround = this.desiredAltitudeOverride[1];
        }
        else {
            minHeightOverGround = this.minHeightOverGround;
            maxHeightOverGround = this.maxHeightOverGround;
        }
        final int iy = MathUtil.floor(y);
        final double below = WorldUtil.findFarthestEmptySpaceBelow(chunkStoreAccessor, chunkColumnComponent, blockChunkComponent, ix, iy, iz, iy) + this.collisionBoundingBox.min.y;
        final double above = WorldUtil.findFarthestEmptySpaceAbove(chunkStoreAccessor, chunkColumnComponent, blockChunkComponent, ix, iy, iz, iy) - this.collisionBoundingBox.max.y + 1.0;
        double minY = below + minHeightOverGround;
        double maxY = Math.min(below + maxHeightOverGround, above);
        if (minY > maxY) {
            minY = y;
            maxY = y;
        }
        this.verticalRange.assign(y, minY, maxY);
        return this.verticalRange;
    }
    
    @Override
    public double getWanderVerticalMovementRatio() {
        return 0.5;
    }
    
    protected double doMove(@Nonnull final Ref<EntityStore> ref, @Nonnull final Vector3d position, @Nonnull final Vector3d translation, @Nonnull final PositionProbeAir moveProbe, @Nullable final ProbeMoveData probeMoveData, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final boolean probeOnly = probeMoveData != null;
        final boolean saveSegments = probeOnly && probeMoveData.startProbing();
        final boolean canAct = probeOnly || this.canAct(ref, componentAccessor);
        final String debugPrefix = probeOnly ? "Probe" : "Move";
        if (this.debugModeMove) {
            MotionControllerFly.LOGGER.at(Level.INFO).log("%s - Fly: Execute pos=%s vel=%s onGround=%s blocked=%s ", debugPrefix, Vector3d.formatShortString(position), Vector3d.formatShortString(translation), this.onGround(), this.isObstructed);
        }
        if (this.debugModeValidatePositions && !this.isValidPosition(position, this.collisionResult, componentAccessor)) {
            throw new IllegalStateException("Invalid position");
        }
        if (saveSegments) {
            probeMoveData.addStartSegment(position, false);
        }
        if (!probeOnly) {
            this.isObstructed = false;
            if (this.debugModeBlockCollisions) {
                this.collisionResult.setLogger(MotionControllerFly.LOGGER);
            }
        }
        this.collisionResult.setCollisionByMaterial(canAct ? 6 : 4);
        CollisionModule.get();
        CollisionModule.findCollisions(this.collisionBoundingBox, position, translation, this.collisionResult, componentAccessor);
        if (this.debugModeBlockCollisions) {
            this.collisionResult.setLogger(null);
        }
        if (this.debugModeCollisions) {
            this.dumpCollisionResults();
        }
        final BlockCollisionData collision = this.collisionResult.getFirstBlockCollision();
        this.lastValidPosition.assign(position);
        double distanceFactor;
        if (collision == null) {
            position.add(translation);
            distanceFactor = 1.0;
            if (this.debugModeMove) {
                MotionControllerFly.LOGGER.at(Level.INFO).log("%s - Fly: No collision pos=%s vel=%s onGround=%s blocked=%s ", debugPrefix, Vector3d.formatShortString(position), Vector3d.formatShortString(translation), this.onGround(), this.isObstructed);
            }
            if (this.debugModeValidatePositions && !this.isValidPosition(position, this.collisionResult, componentAccessor)) {
                throw new IllegalStateException("Invalid position");
            }
        }
        else {
            position.assign(collision.collisionPoint);
            distanceFactor = collision.collisionStart;
            if (!probeOnly) {
                this.isObstructed = true;
            }
            if (this.debugModeMove) {
                MotionControllerFly.LOGGER.at(Level.INFO).log("%s - Fly: Collision pos=%s collStart=%s vel=%s onGround=%s blocked=%s ", debugPrefix, Vector3d.formatShortString(position), distanceFactor, Vector3d.formatShortString(translation), this.onGround(), this.isObstructed);
            }
            if (this.debugModeValidatePositions && !this.isValidPosition(position, this.collisionResult, componentAccessor)) {
                throw new IllegalStateException("Invalid position");
            }
        }
        if (!moveProbe.probePosition(ref, this.collisionBoundingBox, position, this.collisionResult, componentAccessor)) {
            final double adjust = this.bisect(this.lastValidPosition, position, this, (_this, newPosition) -> _this.moveProbe.probePosition(ref, _this.collisionBoundingBox, newPosition, _this.collisionResult, componentAccessor), position);
            distanceFactor *= adjust;
            if (this.debugModeMove) {
                MotionControllerFly.LOGGER.at(Level.INFO).log("%s - Fly: Bisect step pos=%s distanceFactor=%s adjust=%s", debugPrefix, Vector3d.formatShortString(position), distanceFactor, adjust);
            }
        }
        if (!probeOnly) {
            this.processTriggers(ref, this.collisionResult, distanceFactor, componentAccessor);
        }
        else if (saveSegments) {
            final double distance = this.waypointDistance(probeMoveData.initialPosition, position);
            if (collision == null) {
                probeMoveData.addMoveSegment(position, false, distance);
            }
            else if (this.getWorldNormal().equals(collision.collisionNormal)) {
                probeMoveData.addHitGroundSegment(position, distance, collision.collisionNormal, collision.blockId);
            }
            else {
                probeMoveData.addHitWallSegment(position, false, distance, collision.collisionNormal, collision.blockId);
            }
            probeMoveData.addEndSegment(position, true, distance);
        }
        return distanceFactor;
    }
    
    @Override
    protected double executeMove(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, double dt, @Nonnull final Vector3d translation, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final double scale = this.doMove(ref, this.position, translation, this.moveProbe, null, componentAccessor);
        if (scale < 1.0) {
            dt *= scale;
            this.lastSpeed *= scale;
            this.lastVelocity.scale(scale);
        }
        return dt;
    }
    
    @Override
    public void constrainRotations(final Role role, final TransformComponent transform) {
    }
    
    @Override
    public double getCurrentMaxBodyRotationSpeed() {
        return this.maxTurnSpeed * this.effectHorizontalSpeedMultiplier;
    }
    
    @Override
    protected void dampForceVelocity(@Nonnull final Vector3d forceVelocity, final double forceVelocityDamping, final double interval, final ComponentAccessor<EntityStore> componentAccessor) {
        if (forceVelocity.squaredLength() < this.minSpeedAfterForceSquared) {
            forceVelocity.assign(Vector3d.ZERO);
            return;
        }
        NPCPhysicsMath.deccelerateToStop(forceVelocity, this.getDampingDeceleration(), interval);
    }
    
    @Override
    protected boolean shouldDampenAppliedVelocitiesY() {
        return true;
    }
    
    @Override
    protected boolean shouldAlwaysUseGroundResistance() {
        return true;
    }
    
    @Override
    public void spawned() {
    }
    
    @Override
    public boolean canAct(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        return super.canAct(ref, componentAccessor) && this.moveProbe.isInAir() && this.effectHorizontalSpeedMultiplier != 0.0;
    }
    
    @Override
    public boolean inAir() {
        return !this.onGround();
    }
    
    @Override
    public boolean onGround() {
        return this.moveProbe.isOnGround();
    }
    
    @Override
    public boolean inWater() {
        return this.moveProbe.isInWater();
    }
    
    @Override
    public double getCurrentSpeed() {
        return 0.0;
    }
    
    @Override
    public double getCurrentTurnRadius() {
        return this.lastSpeed / this.maxTurnSpeed;
    }
    
    @Override
    public float getMaxClimbAngle() {
        return this.maxClimbAngle;
    }
    
    @Override
    public float getMaxSinkAngle() {
        return this.maxSinkAngle;
    }
    
    @Override
    public double getMaximumSpeed() {
        return MathUtil.maxValue(this.maxClimbSpeed, this.maxHorizontalSpeed, this.maxSinkSpeed) * this.effectHorizontalSpeedMultiplier;
    }
    
    @Override
    public boolean is2D() {
        return false;
    }
    
    @Override
    public boolean canRestAtPlace() {
        return false;
    }
    
    @Override
    public double getDesiredAltitudeWeight() {
        return this.desiredAltitudeWeight;
    }
    
    @Override
    public double getHeightOverGround() {
        return this.probeMoveProbe.getHeightOverGround();
    }
    
    @Override
    public boolean isHorizontalIdle(final double speed) {
        return false;
    }
    
    @Override
    public boolean estimateVelocity(final Steering steering, @Nonnull final Vector3d velocityOut) {
        velocityOut.assign(Vector3d.ZERO);
        return false;
    }
    
    @Override
    public void clearOverrides() {
        this.desiredAltitudeOverride = null;
    }
    
    public void setDesiredAltitudeOverride(final double[] desiredAltitudeOverride) {
        this.desiredAltitudeOverride = desiredAltitudeOverride;
    }
    
    public void takeOff(@Nonnull final Ref<EntityStore> ref, final double speed, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        PhysicsMath.vectorFromAngles(transformComponent.getRotation().getYaw(), 0.7853982f, this.lastVelocity);
        this.lastSpeed = speed;
    }
    
    public double getMinSpeedAfterForceSquared() {
        return this.minSpeedAfterForceSquared;
    }
    
    public double getDampingDeceleration() {
        return this.forceVelocityDamping * 20.0;
    }
    
    protected double computeMaxSpeedFromPitch(final double pitch) {
        final double sinePitch = TrigMathUtil.sin(pitch);
        final double cosinePitch = Math.sqrt(1.0 - sinePitch * sinePitch);
        final double c = cosinePitch * this.maxHorizontalSpeed;
        final double s = sinePitch * ((sinePitch > 0.0) ? this.maxClimbSpeed : this.maxSinkSpeed);
        return Math.sqrt(c * c + s * s);
    }
}
