diff --git a/src/main/java/fr/themode/demo/PlayerInit.java b/src/main/java/fr/themode/demo/PlayerInit.java index b4fb96f54..1f2038528 100644 --- a/src/main/java/fr/themode/demo/PlayerInit.java +++ b/src/main/java/fr/themode/demo/PlayerInit.java @@ -92,13 +92,13 @@ public class PlayerInit { creature.damage(DamageType.fromPlayer(player), -1); Vector velocity = player.getPosition().clone().getDirection().multiply(6); velocity.setY(4f); - entity.setVelocity(velocity, 150); + entity.setVelocity(velocity); player.sendMessage("You attacked an entity!"); } else if (entity instanceof Player) { Player target = (Player) entity; Vector velocity = player.getPosition().clone().getDirection().multiply(4); velocity.setY(3.5f); - target.setVelocity(velocity, 150); + target.setVelocity(velocity); target.damage(DamageType.fromPlayer(player), 5); player.sendMessage("ATTACK"); } @@ -143,7 +143,7 @@ public class PlayerInit { itemEntity.refreshPosition(player.getPosition().clone().add(0, 1.5f, 0)); itemEntity.setInstance(player.getInstance()); Vector velocity = player.getPosition().clone().getDirection().multiply(6); - itemEntity.setVelocity(velocity, 500); + itemEntity.setVelocity(velocity); }); player.addEventCallback(PlayerDisconnectEvent.class, event -> { diff --git a/src/main/java/fr/themode/demo/entity/ChickenCreature.java b/src/main/java/fr/themode/demo/entity/ChickenCreature.java index 2d6bb34ed..3c35b9f60 100644 --- a/src/main/java/fr/themode/demo/entity/ChickenCreature.java +++ b/src/main/java/fr/themode/demo/entity/ChickenCreature.java @@ -41,7 +41,7 @@ public class ChickenCreature extends EntityCreature { boolean unmount = vehicleInformation.shouldUnmount(); if (jump && isOnGround()) { - setVelocity(new Vector(0, 6, 0), 500); + setVelocity(new Vector(0, 6, 0)); } boolean updateView = forward > 0; diff --git a/src/main/java/net/minestom/server/collision/BoundingBox.java b/src/main/java/net/minestom/server/collision/BoundingBox.java index 7e2b26bbe..9fc95f400 100644 --- a/src/main/java/net/minestom/server/collision/BoundingBox.java +++ b/src/main/java/net/minestom/server/collision/BoundingBox.java @@ -8,6 +8,7 @@ import net.minestom.server.utils.Position; * See https://wiki.vg/Entity_metadata#Mobs_2 */ public class BoundingBox { +// TODO: private Entity entity; private float x, y, z; @@ -68,15 +69,15 @@ public class BoundingBox { return new BoundingBox(entity, this.x - x, this.y - y, this.z - z); } - public float getX() { + public float getWidth() { return x; } - public float getY() { + public float getHeight() { return y; } - public float getZ() { + public float getDepth() { return z; } diff --git a/src/main/java/net/minestom/server/collision/CollisionUtils.java b/src/main/java/net/minestom/server/collision/CollisionUtils.java index 8d1b70f7a..ee241b8f6 100644 --- a/src/main/java/net/minestom/server/collision/CollisionUtils.java +++ b/src/main/java/net/minestom/server/collision/CollisionUtils.java @@ -1,40 +1,98 @@ package net.minestom.server.collision; +import net.minestom.server.entity.Entity; import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.Block; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.Position; +import net.minestom.server.utils.Vector; public class CollisionUtils { - public static Position entity(Instance instance, BoundingBox boundingBox, Position currentPosition, Position targetPosition) { + /** + * Moves an entity with physics applied (ie checking against blocks) + * @param entity the entity to move + * @param positionOut the Position object in which the new position will be saved + * @param velocityOut the Vector object in which the new velocity will be saved + * @return whether this entity is on the ground + */ + public static boolean handlePhysics(Entity entity, Vector deltaPosition, Position positionOut, Vector velocityOut) { + Instance instance = entity.getInstance(); + Position currentPosition = entity.getPosition(); + BoundingBox boundingBox = entity.getBoundingBox(); float currentX = currentPosition.getX(); float currentY = currentPosition.getY(); float currentZ = currentPosition.getZ(); - float targetX = targetPosition.getX(); - float targetY = targetPosition.getY(); - float targetZ = targetPosition.getZ(); + // target_WithBB is the target_ with the length in the _ direction of the bounding box added. Used to determinate block intersections + // step Y + float targetY = entity.getPosition().getY() + deltaPosition.getY(); + float targetYWithBB = targetY; + if(deltaPosition.getY() > 0) { + targetYWithBB += boundingBox.getHeight(); + } + BlockPosition yBlock = new BlockPosition(currentX, (int) targetYWithBB, currentZ); + boolean yAir = !Block.fromId(instance.getBlockId(yBlock)).isSolid(); + boolean yIntersect = boundingBox.intersect(yBlock); - BlockPosition xBlock = new BlockPosition(targetX, (int) currentY, currentZ); - BlockPosition yBlock = new BlockPosition(currentX, (int) targetY, currentZ); - BlockPosition zBlock = new BlockPosition(currentX, (int) currentY, targetZ); + boolean yCollision = true; + if(yAir || !yIntersect) + yCollision = false; + float newY = yCollision ? currentY : targetY; + + if(yCollision) { + if(deltaPosition.getY() < 0) { + newY = (float) (Math.ceil(newY)+0.01f); // TODO: custom block bounding boxes + } else if(deltaPosition.getY() > 0) { + newY = (float) (Math.floor(newY)-0.01f); // TODO: custom block bounding boxes + } + } + + // step X/Z + float targetX = entity.getPosition().getX() + deltaPosition.getX(); + float targetXWithBB = targetX+Math.signum(deltaPosition.getX()) * boundingBox.getWidth()/2f; + + float targetZ = entity.getPosition().getZ() + deltaPosition.getZ(); + float targetZWithBB = targetZ+Math.signum(deltaPosition.getZ()) * boundingBox.getDepth()/2f; + + BlockPosition xBlock = new BlockPosition(targetXWithBB, (int) newY, currentZ); + BlockPosition zBlock = new BlockPosition(currentX, (int) newY, targetZWithBB); boolean xAir = !Block.fromId(instance.getBlockId(xBlock)).isSolid(); - boolean yAir = !Block.fromId(instance.getBlockId(yBlock)).isSolid(); boolean zAir = !Block.fromId(instance.getBlockId(zBlock)).isSolid(); boolean xIntersect = boundingBox.intersect(xBlock); - boolean yIntersect = boundingBox.intersect(yBlock); boolean zIntersect = boundingBox.intersect(zBlock); - float newX = xAir ? targetX : xIntersect ? currentX : targetX; - float newY = yAir ? targetY : yIntersect ? currentY : targetY; - float newZ = zAir ? targetZ : zIntersect ? currentZ : targetZ; + boolean xCollision = true; + if(xAir || !xIntersect) + xCollision = false; + boolean zCollision = true; + if(zAir || !zIntersect) + zCollision = false; + float newX = xCollision ? currentX : targetX; + float newZ = zCollision ? currentZ : targetZ; - return new Position(newX, newY, newZ); + velocityOut.copy(entity.getVelocity()); + + if(xCollision) { + velocityOut.setX(0f); + } + if(yCollision) { + velocityOut.setY(0f); + } + if(zCollision) { + velocityOut.setZ(0f); + } + + positionOut.setX(newX); + positionOut.setY(newY); + positionOut.setZ(newZ); + + + return yCollision && deltaPosition.getY() < 0; } } diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index b15f935e5..a406b02e0 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -65,7 +65,7 @@ public abstract class Entity implements Viewable, DataContainer { private int entityType; private long lastUpdate; private Map, List> eventCallbacks = new ConcurrentHashMap<>(); - protected long velocityTime; // Reset velocity to 0 after countdown + protected long lastVelocityUpdateTime; // Reset velocity to 0 after countdown // Synchronization private long synchronizationDelay = 1500; // In ms @@ -86,6 +86,8 @@ public abstract class Entity implements Viewable, DataContainer { protected boolean silent; protected boolean noGravity; protected Pose pose = Pose.STANDING; + private long velocityUpdatePeriod; + protected boolean onGround; public Entity(int entityType, Position spawnPosition) { this.id = generateId(); @@ -96,6 +98,7 @@ public abstract class Entity implements Viewable, DataContainer { setBoundingBox(0, 0, 0); entityById.put(id, this); + setVelocityUpdatePeriod(5); } public Entity(int entityType) { @@ -117,7 +120,32 @@ public abstract class Entity implements Viewable, DataContainer { public abstract void spawn(); public boolean isOnGround() { - return EntityUtils.isOnGround(this); + return onGround || EntityUtils.isOnGround(this) /* backup for levitating entities */; + } + + /** + * Checks if now is a good time to send a velocity update packet + * @return + * @param time + */ + protected boolean shouldSendVelocityUpdate(long time) { + return (time-lastVelocityUpdateTime) >= velocityUpdatePeriod; + } + + /** + * Gets the period, in ms, between two velocity update packets + * @return period, in ms, between two velocity update packets + */ + public long getVelocityUpdatePeriod() { + return velocityUpdatePeriod; + } + + /** + * Sets the period, in ms, between two velocity update packets + * @param velocityUpdatePeriod period, in ms, between two velocity update packets + */ + public void setVelocityUpdatePeriod(long velocityUpdatePeriod) { + this.velocityUpdatePeriod = velocityUpdatePeriod; } public void teleport(Position position, Runnable callback) { @@ -209,74 +237,43 @@ public abstract class Entity implements Viewable, DataContainer { this.lastUpdate = time; // Velocity - if (velocityTime != 0) { - if (this instanceof Player) { - sendPacketToViewersAndSelf(getVelocityPacket()); + if (!(this instanceof Player)) { + final float tps = MinecraftServer.TICK_PER_SECOND; + float newX = position.getX() + velocity.getX() / tps; + float newY = position.getY() + velocity.getY() / tps; + float newZ = position.getZ() + velocity.getZ() / tps; + + Position newPosition = new Position(newX, newY, newZ); + + if (!(this instanceof Player) && !noGravity) { // players handle gravity by themselves + velocity.setY(velocity.getY() - gravityDragPerTick*tps); + } + + Position newPositionOut = new Position(); + Vector newVelocityOut = new Vector(); + Vector deltaPos = new Vector( + getVelocity().getX() / tps, + getVelocity().getY() / tps, + getVelocity().getZ() / tps + ); + onGround = CollisionUtils.handlePhysics(this, deltaPos, newPosition, newVelocityOut); + + refreshPosition(newPosition); + velocity.copy(newVelocityOut); + + float drag; + if(onGround) { + drag = 0.5f; // ground drag } else { - final float tps = MinecraftServer.TICK_PER_SECOND; - float newX = position.getX() + velocity.getX() / tps; - float newY = position.getY() + velocity.getY() / tps; - float newZ = position.getZ() + velocity.getZ() / tps; - - Position newPosition = new Position(newX, newY, newZ); - - newPosition = CollisionUtils.entity(getInstance(), getBoundingBox(), getPosition(), newPosition); - - refreshPosition(newPosition); - if (this instanceof ObjectEntity) { - // FIXME velocity/gravity - //sendPacketToViewers(getVelocityPacket()); - teleport(getPosition()); - } else if (this instanceof EntityCreature) { - teleport(getPosition()); - } + drag = 0.98f; // air drag } - if (time >= velocityTime) { - sendSynchronization(); // Send synchronization after velocity ended - resetVelocity(); - } - } + velocity.multiply(drag); - // Gravity - if (!(this instanceof Player) && !noGravity && gravityDragPerTick != 0) { // Players do manage gravity client-side - Position position = getPosition(); - //System.out.println("on ground: "+isOnGround()); - if (!isOnGround()) { - float strength = gravityDragPerTick * gravityTickCounter; - boolean foundBlock = false; - int firstBlock = 0; - for (int y = (int) position.getY(); y > 0; y--) { - BlockPosition blockPosition = new BlockPosition(position.getX(), y, position.getZ()); - short blockId = instance.getBlockId(blockPosition); - //if (y == 70) - // System.out.println("id: " + blockId); - if (blockId != 0) { - firstBlock = y; - foundBlock = true; - //System.out.println("first block: " + y); - break; - } - } - - float newY = position.getY() - strength; - //System.out.println("REFRESH Y " + newY); - // allow entities to go below the world - if (foundBlock) { - newY = Math.max(newY, firstBlock); - } - refreshPosition(position.getX(), newY, position.getZ()); - gravityTickCounter++; - if (isOnGround()) { // Round Y axis when gravity movement is done - //System.out.println("ROUND " + position.getY()); - refreshPosition(position.getX(), Math.round(position.getY()), position.getZ()); - gravityTickCounter = 0; - // System.out.println("recheck: " + isOnGround() + " : " + getPosition()); - } - - if (this instanceof EntityCreature) { - teleport(getPosition()); - } + sendSynchronization(); + if (shouldSendVelocityUpdate(time)) { + sendPacketToViewers(getVelocityPacket()); + lastVelocityUpdateTime = time; } } @@ -377,14 +374,8 @@ public abstract class Entity implements Viewable, DataContainer { return velocity; } - public void setVelocity(Vector velocity, long velocityTime) { - this.velocity = velocity; - this.velocityTime = System.currentTimeMillis() + velocityTime; - } - - public void resetVelocity() { - this.velocity = new Vector(); - this.velocityTime = 0; + public void setVelocity(Vector velocity) { + this.velocity.copy(velocity); } public void setGravity(float gravityDragPerTick) { diff --git a/src/main/java/net/minestom/server/entity/EntityCreature.java b/src/main/java/net/minestom/server/entity/EntityCreature.java index 83145e005..7185b8ecd 100644 --- a/src/main/java/net/minestom/server/entity/EntityCreature.java +++ b/src/main/java/net/minestom/server/entity/EntityCreature.java @@ -49,9 +49,9 @@ public abstract class EntityCreature extends LivingEntity { float newX = position.getX() + x; float newY = position.getY() + y; float newZ = position.getZ() + z; - Position newPosition = new Position(newX, newY, newZ); + Position newPosition = new Position(); // Calculate collisions boxes - newPosition = CollisionUtils.entity(getInstance(), getBoundingBox(), position, newPosition); + onGround = CollisionUtils.handlePhysics(this, new Vector(x, y, z), newPosition, new Vector()); // Refresh target position newX = newPosition.getX(); newY = newPosition.getY(); @@ -133,7 +133,7 @@ public abstract class EntityCreature extends LivingEntity { public void jump(float height) { // FIXME magic value Vector velocity = new Vector(0, height * 10, 0); - setVelocity(velocity, 200); + setVelocity(velocity); } public void setPathTo(Position position, int maxCheck, Consumer resultConsumer) { diff --git a/src/main/java/net/minestom/server/utils/EntityUtils.java b/src/main/java/net/minestom/server/utils/EntityUtils.java index 8f9fdc140..15670db68 100644 --- a/src/main/java/net/minestom/server/utils/EntityUtils.java +++ b/src/main/java/net/minestom/server/utils/EntityUtils.java @@ -35,6 +35,7 @@ public class EntityUtils { Position entityPosition = entity.getPosition(); + // TODO: check entire bounding box BlockPosition blockPosition = entityPosition.toBlockPosition(); blockPosition = blockPosition.subtract(0, 1, 0); try {