Entity pose fixes (#487)

This commit is contained in:
TogAr2 2022-05-03 22:29:38 +02:00 committed by GitHub
parent 0dc7f52a50
commit 23d7df7cbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 13 deletions

View File

@ -6,11 +6,15 @@ import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* See https://wiki.vg/Entity_metadata#Mobs_2
*/
public final class BoundingBox implements Shape {
private static final BoundingBox sleepingBoundingBox = new BoundingBox(0.2, 0.2, 0.2);
private static final BoundingBox sneakingBoundingBox = new BoundingBox(0.6, 1.5, 0.6);
private static final BoundingBox smallBoundingBox = new BoundingBox(0.6, 0.6, 0.6);
final static BoundingBox ZERO = new BoundingBox(0, 0, 0);
@ -153,4 +157,26 @@ public final class BoundingBox implements Shape {
public double maxZ() {
return relativeEnd().z();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BoundingBox that = (BoundingBox) o;
if (Double.compare(that.width, width) != 0) return false;
if (Double.compare(that.height, height) != 0) return false;
if (Double.compare(that.depth, depth) != 0) return false;
return offset.equals(that.offset);
}
public static @Nullable BoundingBox fromPose(@NotNull Entity.Pose pose) {
return switch (pose) {
case FALL_FLYING, SWIMMING, SPIN_ATTACK -> smallBoundingBox;
case SLEEPING, DYING -> sleepingBoundingBox;
case SNEAKING -> sneakingBoundingBox;
default -> null;
};
}
}

View File

@ -16,6 +16,7 @@ import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.metadata.EntityMeta;
import net.minestom.server.entity.metadata.LivingEntityMeta;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.EventFilter;
import net.minestom.server.event.EventHandler;
@ -182,10 +183,10 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
this.previousPosition = Pos.ZERO;
this.lastSyncedPosition = Pos.ZERO;
setBoundingBox(entityType.registry().boundingBox());
this.entityMeta = EntityTypeImpl.createMeta(entityType, this, this.metadata);
setBoundingBox(entityType.registry().boundingBox());
Entity.ENTITY_BY_ID.put(id, this);
Entity.ENTITY_BY_UUID.put(uuid, this);
@ -760,16 +761,20 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
}
/**
* Returns the current bounding box (based on pose).
* Is used to check collision with coordinates or other blocks/entities.
*
* @return the entity bounding box
*/
public @NotNull BoundingBox getBoundingBox() {
return boundingBox;
// Check if there is a specific bounding box for this pose
BoundingBox poseBoundingBox = BoundingBox.fromPose(getPose());
return poseBoundingBox == null ? boundingBox : poseBoundingBox;
}
/**
* Changes the internal entity bounding box.
* Changes the internal entity standing bounding box.
* When the pose is not standing, a different bounding box may be used for collision.
* <p>
* WARNING: this does not change the entity hit-box which is client-side.
*
@ -778,11 +783,12 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
* @param depth the bounding box Z size
*/
public void setBoundingBox(double width, double height, double depth) {
this.boundingBox = new BoundingBox(width, height, depth);
setBoundingBox(new BoundingBox(width, height, depth));
}
/**
* Changes the internal entity bounding box.
* Changes the internal entity standing bounding box.
* When the pose is not standing, a different bounding box may be used for collision.
* <p>
* WARNING: this does not change the entity hit-box which is client-side.
*
@ -1102,8 +1108,8 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
* @param sneaking true to make the entity sneak
*/
public void setSneaking(boolean sneaking) {
setPose(sneaking ? Pose.SNEAKING : Pose.STANDING);
this.entityMeta.setSneaking(sneaking);
updatePose();
}
/**
@ -1186,6 +1192,20 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
this.entityMeta.setPose(pose);
}
protected void updatePose() {
if (entityMeta.isFlyingWithElytra()) {
setPose(Pose.FALL_FLYING);
} else if (entityMeta.isSwimming()) {
setPose(Pose.SWIMMING);
} else if (this instanceof LivingEntity && ((LivingEntityMeta) entityMeta).isInRiptideSpinAttack()) {
setPose(Pose.SPIN_ATTACK);
} else if (entityMeta.isSneaking()) {
setPose(Pose.SNEAKING);
} else {
setPose(Pose.STANDING);
}
}
/**
* Gets the entity custom name.
*
@ -1379,7 +1399,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
* @return the entity eye height
*/
public double getEyeHeight() {
return boundingBox.height() * 0.85;
return getPose() == Pose.SLEEPING ? 0.2 : (boundingBox.height() * 0.85);
}
/**

View File

@ -265,6 +265,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
public void kill() {
refreshIsDead(true); // So the entity isn't killed over and over again
triggerStatus((byte) 3); // Start death animation status
setPose(Pose.DYING);
setHealth(0);
// Reset velocity
@ -521,8 +522,8 @@ public class LivingEntity extends Entity implements EquipmentHandler {
}
@Override
public void setBoundingBox(double x, double y, double z) {
super.setBoundingBox(x, y, z);
public void setBoundingBox(BoundingBox boundingBox) {
super.setBoundingBox(boundingBox);
this.expandedBoundingBox = getBoundingBox().expand(1, 0.5f, 1);
}
@ -550,6 +551,8 @@ public class LivingEntity extends Entity implements EquipmentHandler {
meta.setActiveHand(offHand ? Player.Hand.OFF : Player.Hand.MAIN);
meta.setInRiptideSpinAttack(riptideSpinAttack);
meta.setNotifyAboutChanges(true);
updatePose(); // Riptide spin attack has a pose
}
}
@ -559,6 +562,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
public void setFlyingWithElytra(boolean isFlying) {
this.entityMeta.setFlyingWithElytra(isFlying);
updatePose();
}
/**

View File

@ -20,6 +20,7 @@ import net.minestom.server.adventure.AdventurePacketConvertor;
import net.minestom.server.adventure.Localizable;
import net.minestom.server.adventure.audience.Audiences;
import net.minestom.server.attribute.Attribute;
import net.minestom.server.collision.BoundingBox;
import net.minestom.server.command.CommandManager;
import net.minestom.server.command.CommandSender;
import net.minestom.server.coordinate.Point;
@ -105,7 +106,6 @@ import java.util.function.UnaryOperator;
* You can easily create your own implementation of this and use it with {@link ConnectionManager#setPlayerProvider(PlayerProvider)}.
*/
public class Player extends LivingEntity implements CommandSender, Localizable, HoverEventSource<ShowEntity>, Identified, NamedAndIdentified {
private static final Component REMOVE_MESSAGE = Component.text("You have been removed from the server without reason.", NamedTextColor.RED);
private static final int PACKET_PER_TICK = Integer.getInteger("minestom.packet-per-tick", 20);
private static final int PACKET_QUEUE_SIZE = Integer.getInteger("minestom.packet-queue-size", 1000);
@ -202,8 +202,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
this.usernameComponent = Component.text(username);
this.playerConnection = playerConnection;
setBoundingBox(0.6f, 1.8f, 0.6f);
setRespawnPoint(Pos.ZERO);
this.settings = new PlayerSettings();
@ -435,6 +433,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
EventDispatcher.call(respawnEvent);
triggerStatus((byte) (24 + permissionLevel)); // Set permission level
refreshIsDead(false);
updatePose();
// Runnable called when teleportation is successful (after loading and sending necessary chunk)
teleport(respawnEvent.getRespawnPosition()).thenRun(this::refreshAfterTeleport);
@ -873,6 +872,16 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
this.defaultEatingTime = defaultEatingTime;
}
@Override
public double getEyeHeight() {
return switch (getPose()) {
case SLEEPING -> 0.2;
case FALL_FLYING, SWIMMING, SPIN_ATTACK -> 0.4;
case SNEAKING -> 1.27;
default -> 1.62;
};
}
/**
* Gets the player display name in the tab-list.
*

View File

@ -0,0 +1,51 @@
package net.minestom.server.entity;
import net.minestom.server.api.Env;
import net.minestom.server.api.EnvTest;
import net.minestom.server.collision.BoundingBox;
import net.minestom.server.coordinate.Pos;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnvTest
public class EntityBoundingBoxIntegrationTest {
@Test
public void pose(Env env) {
var instance = env.createFlatInstance();
var connection = env.createConnection();
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
// Bounding box should be from the registry
assertEquals(player.getEntityType().registry().boundingBox(), player.getBoundingBox());
player.setPose(Entity.Pose.STANDING);
assertEquals(player.getEntityType().registry().boundingBox(), player.getBoundingBox());
player.setPose(Entity.Pose.SLEEPING);
assertEquals(new BoundingBox(0.2, 0.2, 0.2), player.getBoundingBox());
player.setPose(Entity.Pose.SNEAKING);
assertEquals(new BoundingBox(0.6, 1.5, 0.6), player.getBoundingBox());
player.setPose(Entity.Pose.FALL_FLYING);
assertEquals(new BoundingBox(0.6, 0.6, 0.6), player.getBoundingBox());
}
@Test
public void eyeHeight(Env env) {
var instance = env.createFlatInstance();
var connection = env.createConnection();
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
assertEquals(1.62, player.getEyeHeight());
player.setPose(Entity.Pose.SLEEPING);
assertEquals(0.2, player.getEyeHeight());
player.setPose(Entity.Pose.SNEAKING);
assertEquals(1.27, player.getEyeHeight());
player.setPose(Entity.Pose.FALL_FLYING);
assertEquals(0.4, player.getEyeHeight());
}
}