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

package com.hypixel.hytale.server.core.modules.entity.player;

import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.protocol.MovementSettings;
import it.unimi.dsi.fastutil.objects.ObjectList;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.protocol.ChangeVelocityType;
import com.hypixel.hytale.protocol.Packet;
import com.hypixel.hytale.protocol.packets.entities.ApplyKnockback;
import com.hypixel.hytale.server.core.util.PositionUtil;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.modules.collision.CollisionResult;
import com.hypixel.hytale.server.core.modules.collision.CollisionModule;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath;
import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager;
import com.hypixel.hytale.server.core.universe.world.ParticleUtil;
import com.hypixel.hytale.protocol.Color;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent;
import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.component.dependency.SystemDependency;
import com.hypixel.hytale.component.dependency.Order;
import com.hypixel.hytale.protocol.MovementStates;
import com.hypixel.hytale.math.vector.Vector3d;
import java.util.List;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.dependency.Dependency;
import java.util.Set;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.system.RefSystem;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.Ref;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.system.RefChangeSystem;

public class KnockbackPredictionSystems
{
    public static boolean DEBUG_KNOCKBACK_POSITION;
    public static final float DEFAULT_BLOCK_DRAG = 0.82f;
    public static final float AIR_DENSITY = 0.001225f;
    public static final float COLLISION_PADDING = 1.0E-4f;
    public static final float MAX_CYCLE_MOVEMENT = 0.25f;
    public static final float TIME_STEP = 0.016666668f;
    public static final int MAX_JUMP_COMBOS = 3;
    
    static {
        KnockbackPredictionSystems.DEBUG_KNOCKBACK_POSITION = false;
    }
    
    public static class ClearOnTeleport extends RefChangeSystem<EntityStore, Teleport>
    {
        private static final ComponentType<EntityStore, Teleport> TELEPORT_COMPONENT_TYPE;
        private static final ComponentType<EntityStore, KnockbackSimulation> KNOCKBACK_SIMULATION_COMPONENT_TYPE;
        
        @Override
        public Query<EntityStore> getQuery() {
            return ClearOnTeleport.KNOCKBACK_SIMULATION_COMPONENT_TYPE;
        }
        
        @Nonnull
        @Override
        public ComponentType<EntityStore, Teleport> componentType() {
            return ClearOnTeleport.TELEPORT_COMPONENT_TYPE;
        }
        
        @Override
        public void onComponentAdded(@Nonnull final Ref<EntityStore> ref, @Nonnull final Teleport component, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            commandBuffer.removeComponent(ref, ClearOnTeleport.KNOCKBACK_SIMULATION_COMPONENT_TYPE);
        }
        
        @Override
        public void onComponentSet(@Nonnull final Ref<EntityStore> ref, final Teleport oldComponent, @Nonnull final Teleport newComponent, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        }
        
        @Override
        public void onComponentRemoved(@Nonnull final Ref<EntityStore> ref, @Nonnull final Teleport component, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        }
        
        static {
            TELEPORT_COMPONENT_TYPE = Teleport.getComponentType();
            KNOCKBACK_SIMULATION_COMPONENT_TYPE = KnockbackSimulation.getComponentType();
        }
    }
    
    public static class ClearOnRemove extends RefSystem<EntityStore>
    {
        private static final ComponentType<EntityStore, KnockbackSimulation> KNOCKBACK_SIMULATION_COMPONENT_TYPE;
        
        @Override
        public Query<EntityStore> getQuery() {
            return ClearOnRemove.KNOCKBACK_SIMULATION_COMPONENT_TYPE;
        }
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<EntityStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<EntityStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            store.removeComponent(ref, ClearOnRemove.KNOCKBACK_SIMULATION_COMPONENT_TYPE);
        }
        
        static {
            KNOCKBACK_SIMULATION_COMPONENT_TYPE = KnockbackSimulation.getComponentType();
        }
    }
    
    public static class CaptureKnockbackInput extends EntityTickingSystem<EntityStore>
    {
        private static final Query<EntityStore> QUERY;
        private static final Set<Dependency<EntityStore>> DEPENDENCIES;
        
        @Nonnull
        @Override
        public Query<EntityStore> getQuery() {
            return CaptureKnockbackInput.QUERY;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return CaptureKnockbackInput.DEPENDENCIES;
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final KnockbackSimulation knockbackSimulationComponent = archetypeChunk.getComponent(index, KnockbackSimulation.getComponentType());
            assert knockbackSimulationComponent != null;
            final PlayerInput playerInputComponent = archetypeChunk.getComponent(index, PlayerInput.getComponentType());
            assert playerInputComponent != null;
            final List<PlayerInput.InputUpdate> queue = playerInputComponent.getMovementUpdateQueue();
            final Vector3d client = knockbackSimulationComponent.getClientPosition();
            final Vector3d clientLast = knockbackSimulationComponent.getClientLastPosition();
            final Vector3d relativeMovement = knockbackSimulationComponent.getRelativeMovement();
            clientLast.assign(client);
            boolean hasWishMovement = false;
            if (queue.isEmpty()) {
                return;
            }
            for (int i = 0; i < queue.size(); ++i) {
                final PlayerInput.InputUpdate update = queue.get(i);
                if (update instanceof final PlayerInput.AbsoluteMovement abs) {
                    client.assign(abs.getX(), abs.getY(), abs.getZ());
                }
                else if (update instanceof final PlayerInput.RelativeMovement rel) {
                    client.add(rel.getX(), rel.getY(), rel.getZ());
                }
                else if (update instanceof final PlayerInput.WishMovement wish) {
                    hasWishMovement = true;
                    relativeMovement.assign(wish.getX(), wish.getY(), wish.getZ());
                }
                else {
                    if (!(update instanceof PlayerInput.SetMovementStates)) {
                        continue;
                    }
                    final MovementStates movementStates = ((PlayerInput.SetMovementStates)update).movementStates();
                    if (movementStates.jumping) {
                        knockbackSimulationComponent.setWasJumping(true);
                    }
                    knockbackSimulationComponent.setClientMovementStates(movementStates);
                }
                queue.remove(i);
                --i;
            }
            if (!hasWishMovement) {
                relativeMovement.assign(client).subtract(clientLast);
                if (knockbackSimulationComponent.hadWishMovement()) {
                    knockbackSimulationComponent.setClientFinished(true);
                }
            }
            else {
                knockbackSimulationComponent.setHadWishMovement(true);
            }
        }
        
        static {
            QUERY = Query.and(PlayerInput.getComponentType(), KnockbackSimulation.getComponentType());
            DEPENDENCIES = Set.of(new SystemDependency(Order.BEFORE, PlayerSystems.ProcessPlayerInput.class));
        }
    }
    
    public static class InitKnockback extends RefChangeSystem<EntityStore, KnockbackSimulation>
    {
        private static final Query<EntityStore> QUERY;
        
        @Nonnull
        @Override
        public Query<EntityStore> getQuery() {
            return InitKnockback.QUERY;
        }
        
        @Nonnull
        @Override
        public ComponentType<EntityStore, KnockbackSimulation> componentType() {
            return KnockbackSimulation.getComponentType();
        }
        
        @Override
        public void onComponentAdded(@Nonnull final Ref<EntityStore> ref, @Nonnull final KnockbackSimulation component, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType());
            assert transformComponent != null;
            component.getClientPosition().assign(transformComponent.getPosition());
            component.getSimPosition().assign(transformComponent.getPosition());
            final MovementStatesComponent movementStatesComponent = commandBuffer.getComponent(ref, MovementStatesComponent.getComponentType());
            assert movementStatesComponent != null;
            component.setClientMovementStates(new MovementStates(movementStatesComponent.getMovementStates()));
        }
        
        @Override
        public void onComponentSet(@Nonnull final Ref<EntityStore> ref, final KnockbackSimulation oldComponent, @Nonnull final KnockbackSimulation newComponent, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        }
        
        @Override
        public void onComponentRemoved(@Nonnull final Ref<EntityStore> ref, @Nonnull final KnockbackSimulation knockbackSimulationComponent, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final Player playerComponent = commandBuffer.getComponent(ref, Player.getComponentType());
            assert playerComponent != null;
            final Vector3d clientPosition = knockbackSimulationComponent.getClientPosition();
            playerComponent.moveTo(ref, clientPosition.x, clientPosition.y, clientPosition.z, commandBuffer);
            final MovementStatesComponent movementStatesComponent = commandBuffer.getComponent(ref, MovementStatesComponent.getComponentType());
            assert movementStatesComponent != null;
            movementStatesComponent.setMovementStates(knockbackSimulationComponent.getClientMovementStates());
        }
        
        static {
            QUERY = Query.and(Player.getComponentType(), TransformComponent.getComponentType(), KnockbackSimulation.getComponentType(), MovementStatesComponent.getComponentType());
        }
    }
    
    @Deprecated
    public static class SimulateKnockback extends EntityTickingSystem<EntityStore>
    {
        private static final ComponentType<EntityStore, BoundingBox> BOUNDING_BOX_COMPONENT_TYPE;
        private static final Query<EntityStore> QUERY;
        private static final Set<Dependency<EntityStore>> DEPENDENCIES;
        
        @Nonnull
        @Override
        public Query<EntityStore> getQuery() {
            return SimulateKnockback.QUERY;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return SimulateKnockback.DEPENDENCIES;
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final KnockbackSimulation knockbackSimulationComponent = archetypeChunk.getComponent(index, KnockbackSimulation.getComponentType());
            assert knockbackSimulationComponent != null;
            final TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType());
            assert transformComponent != null;
            final Player playerComponent = archetypeChunk.getComponent(index, Player.getComponentType());
            assert playerComponent != null;
            final World world = store.getExternalData().getWorld();
            float time = knockbackSimulationComponent.getRemainingTime();
            time -= dt;
            knockbackSimulationComponent.setRemainingTime(time);
            if (time < 0.0f || archetypeChunk.getArchetype().contains(DeathComponent.getComponentType())) {
                commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), KnockbackSimulation.getComponentType());
                return;
            }
            if (KnockbackPredictionSystems.DEBUG_KNOCKBACK_POSITION) {
                final Vector3d particlePosition = knockbackSimulationComponent.getClientPosition();
                final SpatialResource<Ref<EntityStore>, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType());
                final ObjectList<Ref<EntityStore>> results = SpatialResource.getThreadLocalReferenceList();
                playerSpatialResource.getSpatialStructure().collect(particlePosition, 75.0, results);
                final Color color = knockbackSimulationComponent.hadWishMovement() ? new Color((byte)(-1), (byte)0, (byte)0) : new Color((byte)0, (byte)0, (byte)(-1));
                ParticleUtil.spawnParticleEffect("Example_Simple", particlePosition, 0.0f, 0.0f, 0.0f, 1.0f, color, results, commandBuffer);
            }
            knockbackSimulationComponent.setTickBuffer(knockbackSimulationComponent.getTickBuffer() + dt);
            final MovementStates clientStates = knockbackSimulationComponent.getClientMovementStates();
            while (knockbackSimulationComponent.getTickBuffer() >= 0.016666668f) {
                knockbackSimulationComponent.setTickBuffer(knockbackSimulationComponent.getTickBuffer() - 0.016666668f);
                final Vector3d rel = knockbackSimulationComponent.getRelativeMovement();
                final Vector3d requestedVelocity = knockbackSimulationComponent.getRequestedVelocity();
                final Vector3d simPos = knockbackSimulationComponent.getSimPosition();
                final Vector3d velocity = knockbackSimulationComponent.getSimVelocity();
                final MovementStatesComponent movementStatesComponent = archetypeChunk.getComponent(index, MovementStatesComponent.getComponentType());
                assert movementStatesComponent != null;
                final MovementStates movementStates = movementStatesComponent.getMovementStates();
                final MovementManager movementManagerComponent = archetypeChunk.getComponent(index, MovementManager.getComponentType());
                assert movementManagerComponent != null;
                final MovementSettings movementManagerSettings = movementManagerComponent.getSettings();
                final BoundingBox boundingBoxComponent = archetypeChunk.getComponent(index, SimulateKnockback.BOUNDING_BOX_COMPONENT_TYPE);
                assert boundingBoxComponent != null;
                final Box hitBox = boundingBoxComponent.getBoundingBox();
                if (clientStates.flying) {
                    knockbackSimulationComponent.setRemainingTime(0.0f);
                    return;
                }
                if (clientStates.climbing && !clientStates.onGround) {
                    knockbackSimulationComponent.setRemainingTime(0.0f);
                    return;
                }
                if (clientStates.swimming) {
                    knockbackSimulationComponent.setRemainingTime(0.0f);
                    return;
                }
                final int invertedGravityModifier = movementManagerSettings.invertedGravity ? 1 : -1;
                final double terminalVelocity = invertedGravityModifier * PhysicsMath.getTerminalVelocity(movementManagerSettings.mass, 0.0012250000145286322, Math.abs(hitBox.width() * hitBox.depth()), movementManagerSettings.dragCoefficient);
                final double gravityStep = invertedGravityModifier * PhysicsMath.getAcceleration(velocity.y, terminalVelocity) * 0.01666666753590107;
                if (velocity.y < terminalVelocity && gravityStep > 0.0) {
                    velocity.y = Math.min(velocity.y + gravityStep, terminalVelocity);
                }
                else if (velocity.y > terminalVelocity && gravityStep < 0.0) {
                    velocity.y = Math.max(velocity.y + gravityStep, terminalVelocity);
                }
                if (movementStates.onGround) {
                    movementStates.falling = false;
                    if (knockbackSimulationComponent.wasOnGround() && knockbackSimulationComponent.consumeWasJumping()) {
                        velocity.y = movementManagerSettings.jumpForce;
                        movementStates.onGround = false;
                        knockbackSimulationComponent.setJumpCombo(Math.min(knockbackSimulationComponent.getJumpCombo() + 1, 3));
                    }
                }
                else {
                    final Vector3d clone;
                    final Vector3d checkPosition = clone = simPos.clone();
                    clone.y += 0.10000000149011612;
                    movementStates.falling = (velocity.y < 0.0 && CollisionModule.get().validatePosition(world, hitBox, checkPosition, new CollisionResult()) != 0);
                    if (movementStates.falling) {
                        movementStates.jumping = false;
                        movementStates.swimJumping = false;
                    }
                }
                if (knockbackSimulationComponent.getJumpCombo() != 0 && ((movementStates.onGround && knockbackSimulationComponent.wasOnGround()) || velocity.x == 0.0 || velocity.z == 0.0)) {
                    knockbackSimulationComponent.setJumpCombo(0);
                }
                final float friction = this.computeMoveForce(knockbackSimulationComponent, movementStates, movementManagerSettings);
                if (!movementStates.flying && knockbackSimulationComponent.getRequestedVelocityChangeType() != null) {
                    switch (knockbackSimulationComponent.getRequestedVelocityChangeType()) {
                        case Add: {
                            velocity.add(requestedVelocity.x * 0.18000000715255737 * movementManagerSettings.velocityResistance, requestedVelocity.y, requestedVelocity.z * 0.18000000715255737 * movementManagerSettings.velocityResistance);
                            break;
                        }
                        case Set: {
                            velocity.assign(requestedVelocity);
                            break;
                        }
                    }
                    final PlayerRef playerRefComponent = archetypeChunk.getComponent(index, PlayerRef.getComponentType());
                    assert playerRefComponent != null;
                    playerRefComponent.getPacketHandler().write(new ApplyKnockback(PositionUtil.toPositionPacket(transformComponent.getPosition()), (float)requestedVelocity.x, (float)requestedVelocity.y, (float)requestedVelocity.z, knockbackSimulationComponent.getRequestedVelocityChangeType()));
                }
                requestedVelocity.assign(0.0);
                knockbackSimulationComponent.setRequestedVelocityChangeType(null);
                final Vector3d movementOffset = knockbackSimulationComponent.getMovementOffset();
                movementOffset.assign(0.0);
                if (knockbackSimulationComponent.hadWishMovement()) {
                    final float converter = this.convertWishMovement(knockbackSimulationComponent, movementStates, movementManagerSettings);
                    velocity.addScaled(rel, converter);
                }
                else {
                    movementOffset.addScaled(rel, friction);
                    rel.assign(0.0);
                }
                movementOffset.addScaled(velocity, 0.01666666753590107);
                this.applyMovementOffset(world, hitBox, knockbackSimulationComponent, movementStates, movementOffset);
                final Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);
                if (time < 0.2f) {
                    final Vector3d move = Vector3d.lerp(knockbackSimulationComponent.getClientPosition(), simPos, time / 0.2f);
                    playerComponent.moveTo(ref, move.x, move.y, move.z, commandBuffer);
                }
                else {
                    playerComponent.moveTo(ref, simPos.x, simPos.y, simPos.z, commandBuffer);
                }
            }
        }
        
        private float convertWishMovement(@Nonnull final KnockbackSimulation simulation, @Nonnull final MovementStates movementStates, @Nonnull final MovementSettings movementSettings) {
            MovementStates clientStates = simulation.getClientMovementStates();
            if (clientStates == null) {
                clientStates = movementStates;
            }
            final Vector3d velocity = simulation.getSimVelocity();
            final float horizontalSpeed = (float)Math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z);
            float drag = 0.0f;
            float friction = 1.0f;
            if (!movementStates.flying && !movementStates.climbing) {
                drag = ((!movementStates.onGround && !movementStates.swimming) ? convertToNewRange(horizontalSpeed, movementSettings.airDragMinSpeed, movementSettings.airDragMaxSpeed, movementSettings.airDragMin, movementSettings.airDragMax) : 0.82f);
                friction = ((!movementStates.onGround && !movementStates.swimming) ? convertToNewRange(horizontalSpeed, movementSettings.airFrictionMinSpeed, movementSettings.airFrictionMaxSpeed, movementSettings.airFrictionMax, movementSettings.airFrictionMin) : (1.0f - drag));
            }
            float clientDrag = 0.0f;
            float clientFriction = 1.0f;
            if (!clientStates.flying && !clientStates.climbing) {
                clientDrag = ((!clientStates.onGround && !clientStates.swimming) ? convertToNewRange(horizontalSpeed, movementSettings.airDragMinSpeed, movementSettings.airDragMaxSpeed, movementSettings.airDragMin, movementSettings.airDragMax) : 0.82f);
                clientFriction = ((!clientStates.onGround && !clientStates.swimming) ? convertToNewRange(horizontalSpeed, movementSettings.airFrictionMinSpeed, movementSettings.airFrictionMaxSpeed, movementSettings.airFrictionMax, movementSettings.airFrictionMin) : (1.0f - clientDrag));
            }
            return friction / clientFriction;
        }
        
        private float computeMoveForce(@Nonnull final KnockbackSimulation simulation, @Nonnull final MovementStates movementStates, @Nonnull final MovementSettings movementSettings) {
            float drag = 0.0f;
            float friction = 1.0f;
            final Vector3d velocity = simulation.getSimVelocity();
            final float horizontalSpeed = (float)Math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z);
            if (!movementStates.flying && !movementStates.climbing) {
                drag = ((!movementStates.onGround && !movementStates.swimming) ? convertToNewRange(horizontalSpeed, movementSettings.airDragMinSpeed, movementSettings.airDragMaxSpeed, movementSettings.airDragMin, movementSettings.airDragMax) : 0.82f);
                friction = ((!movementStates.onGround && !movementStates.swimming) ? (convertToNewRange(horizontalSpeed, movementSettings.airFrictionMinSpeed, movementSettings.airFrictionMaxSpeed, movementSettings.airFrictionMax, movementSettings.airFrictionMin) / (1.0f - drag)) : 1.0f);
            }
            final Vector3d vector3d = velocity;
            vector3d.x *= drag;
            final Vector3d vector3d2 = velocity;
            vector3d2.z *= drag;
            return friction;
        }
        
        private static float convertToNewRange(final float value, final float oldMinRange, final float oldMaxRange, final float newMinRange, final float newMaxRange) {
            if (newMinRange == newMaxRange || oldMinRange == oldMaxRange) {
                return newMinRange;
            }
            final float newValue = (value - oldMinRange) * (newMaxRange - newMinRange) / (oldMaxRange - oldMinRange) + newMinRange;
            return MathUtil.clamp(newValue, Math.min(newMinRange, newMaxRange), Math.max(newMinRange, newMaxRange));
        }
        
        public void applyMovementOffset(@Nonnull final World world, @Nonnull final Box hitBox, @Nonnull final KnockbackSimulation simulation, @Nonnull final MovementStates movementStates, @Nonnull final Vector3d movementOffset) {
            final int moveCycles = (int)Math.ceil(movementOffset.length() / 0.25);
            final Vector3d cycleMovementOffset = (moveCycles == 1) ? movementOffset : movementOffset.clone().scale(1.0f / moveCycles);
            simulation.setWasOnGround(movementStates.onGround);
            for (int i = 0; i < moveCycles; ++i) {
                this.doMoveCycle(world, hitBox, simulation, movementStates, cycleMovementOffset);
            }
            if (movementStates.onGround) {
                simulation.getSimVelocity().y = 0.0;
            }
        }
        
        private void doMoveCycle(@Nonnull final World world, @Nonnull final Box hitBox, @Nonnull final KnockbackSimulation simulation, @Nonnull final MovementStates movementStates, @Nonnull final Vector3d offset) {
            final Vector3d simPos = simulation.getSimPosition();
            final Vector3d velocity = simulation.getSimVelocity();
            final Vector3d checkPosition = simulation.getCheckPosition();
            checkPosition.assign(simPos);
            final CollisionResult collisionResult = simulation.getCollisionResult();
            collisionResult.reset();
            final Vector3d vector3d = checkPosition;
            vector3d.y += offset.y;
            final boolean hasCollidedY = this.checkCollision(simulation, world, hitBox, checkPosition, offset, CollisionAxis.Y, collisionResult);
            if (movementStates.onGround && offset.y < 0.0) {
                if (!hasCollidedY) {
                    movementStates.onGround = false;
                }
                else {
                    final Vector3d vector3d2 = checkPosition;
                    vector3d2.y -= offset.y;
                }
            }
            else if (hasCollidedY) {
                if (offset.y <= 0.0) {
                    movementStates.onGround = true;
                    final Vector3d vector3d3 = checkPosition;
                    vector3d3.y -= offset.y;
                }
                else {
                    movementStates.onGround = false;
                    final Vector3d vector3d4 = checkPosition;
                    vector3d4.y -= offset.y;
                }
                velocity.y = 0.0;
            }
            else {
                movementStates.onGround = false;
            }
            if (offset.x != 0.0) {
                final Vector3d vector3d5 = checkPosition;
                vector3d5.x += offset.x;
                collisionResult.reset();
                final boolean hasCollidedX = this.checkCollision(simulation, world, hitBox, checkPosition, offset, CollisionAxis.Y, collisionResult);
                if (hasCollidedX) {
                    final Vector3d vector3d6 = checkPosition;
                    vector3d6.x -= offset.x;
                }
            }
            if (offset.z != 0.0) {
                final Vector3d vector3d7 = checkPosition;
                vector3d7.z += offset.z;
                collisionResult.reset();
                final boolean hasCollidedZ = this.checkCollision(simulation, world, hitBox, checkPosition, offset, CollisionAxis.Y, collisionResult);
                if (hasCollidedZ) {
                    final Vector3d vector3d8 = checkPosition;
                    vector3d8.z -= offset.z;
                }
            }
            simPos.assign(checkPosition);
        }
        
        private boolean checkCollision(@Nonnull final KnockbackSimulation simulation, @Nonnull final World world, @Nonnull final Box hitBox, @Nonnull final Vector3d position, final Vector3d moveOffset, final CollisionAxis axis, @Nonnull final CollisionResult result) {
            final Vector3d tempPosition = simulation.getTempPosition();
            tempPosition.assign(position);
            tempPosition.add(0.0, 9.999999747378752E-5, 0.0);
            return CollisionModule.get().validatePosition(world, hitBox, tempPosition, result) != 0;
        }
        
        static {
            BOUNDING_BOX_COMPONENT_TYPE = BoundingBox.getComponentType();
            QUERY = Query.and(Player.getComponentType(), TransformComponent.getComponentType(), KnockbackSimulation.getComponentType(), SimulateKnockback.BOUNDING_BOX_COMPONENT_TYPE, MovementStatesComponent.getComponentType(), MovementManager.getComponentType(), PlayerRef.getComponentType());
            DEPENDENCIES = Set.of(new SystemDependency(Order.AFTER, PlayerSystems.ProcessPlayerInput.class));
        }
    }
    
    private enum CollisionAxis
    {
        X, 
        Y, 
        Z;
    }
}
