mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-04 07:28:19 +01:00
Entity pose fixes (#487)
This commit is contained in:
parent
0dc7f52a50
commit
23d7df7cbb
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user