Minestom/src/main/java/net/minestom/server/entity/Entity.java

1419 lines
46 KiB
Java
Raw Normal View History

2020-04-24 03:25:58 +02:00
package net.minestom.server.entity;
2019-08-10 08:44:35 +02:00
2020-04-24 03:25:58 +02:00
import net.minestom.server.MinecraftServer;
import net.minestom.server.Viewable;
2020-06-22 23:25:00 +02:00
import net.minestom.server.chat.ColoredText;
2020-04-24 03:25:58 +02:00
import net.minestom.server.collision.BoundingBox;
import net.minestom.server.collision.CollisionUtils;
import net.minestom.server.data.Data;
import net.minestom.server.data.DataContainer;
import net.minestom.server.event.Event;
2020-04-28 01:20:11 +02:00
import net.minestom.server.event.EventCallback;
2020-05-15 18:03:28 +02:00
import net.minestom.server.event.entity.EntityDeathEvent;
2020-05-08 06:54:33 +02:00
import net.minestom.server.event.entity.EntitySpawnEvent;
import net.minestom.server.event.entity.EntityTickEvent;
import net.minestom.server.event.entity.EntityVelocityEvent;
2020-05-08 06:54:33 +02:00
import net.minestom.server.event.handler.EventHandler;
2020-08-15 13:32:36 +02:00
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.instance.WorldBorder;
import net.minestom.server.instance.block.CustomBlock;
2020-04-24 03:25:58 +02:00
import net.minestom.server.network.packet.server.play.*;
import net.minestom.server.thread.ThreadProvider;
2020-07-11 14:16:36 +02:00
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Position;
2020-04-28 01:20:11 +02:00
import net.minestom.server.utils.Vector;
import net.minestom.server.utils.binary.BinaryWriter;
2020-10-22 22:55:40 +02:00
import net.minestom.server.utils.callback.OptionalCallback;
import net.minestom.server.utils.chunk.ChunkUtils;
2020-07-11 14:16:36 +02:00
import net.minestom.server.utils.entity.EntityUtils;
2020-05-25 02:37:57 +02:00
import net.minestom.server.utils.player.PlayerUtils;
2020-05-15 18:03:28 +02:00
import net.minestom.server.utils.time.TimeUnit;
2020-05-23 04:20:01 +02:00
import net.minestom.server.utils.validate.Check;
2020-10-24 10:46:23 +02:00
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
2019-08-10 08:44:35 +02:00
2020-04-28 01:20:11 +02:00
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;
2019-08-12 08:30:59 +02:00
import java.util.concurrent.atomic.AtomicInteger;
2019-09-02 06:02:12 +02:00
import java.util.function.Consumer;
2019-08-10 08:44:35 +02:00
2020-05-08 06:54:33 +02:00
public abstract class Entity implements Viewable, EventHandler, DataContainer {
2019-08-10 08:44:35 +02:00
private static final Map<Integer, Entity> entityById = new ConcurrentHashMap<>();
private static final AtomicInteger lastEntityId = new AtomicInteger();
2019-08-12 08:30:59 +02:00
2019-08-19 17:04:19 +02:00
// Metadata
protected static final byte METADATA_BYTE = 0;
protected static final byte METADATA_VARINT = 1;
protected static final byte METADATA_FLOAT = 2;
protected static final byte METADATA_STRING = 3;
protected static final byte METADATA_CHAT = 4;
protected static final byte METADATA_OPTCHAT = 5;
protected static final byte METADATA_SLOT = 6;
protected static final byte METADATA_BOOLEAN = 7;
2020-05-26 00:07:35 +02:00
protected static final byte METADATA_ROTATION = 8;
protected static final byte METADATA_POSITION = 9;
2020-05-29 18:56:42 +02:00
protected static final byte METADATA_PARTICLE = 15;
2019-09-10 06:59:15 +02:00
protected static final byte METADATA_POSE = 18;
2019-08-19 17:04:19 +02:00
2019-08-11 07:42:56 +02:00
protected Instance instance;
2019-08-21 16:50:52 +02:00
protected Position position;
2019-08-25 20:03:43 +02:00
protected float lastX, lastY, lastZ;
protected float cacheX, cacheY, cacheZ; // Used to synchronize with #getPosition
2019-08-20 22:40:57 +02:00
protected float lastYaw, lastPitch;
protected float cacheYaw, cachePitch;
protected boolean onGround;
2019-08-30 01:17:46 +02:00
private BoundingBox boundingBox;
protected Entity vehicle;
2019-08-25 20:03:43 +02:00
// Velocity
protected Vector velocity = new Vector(); // Movement in block per second
protected long lastVelocityUpdateTime; // Reset velocity to 0 after countdown
private long velocityUpdatePeriod;
2019-08-27 20:49:11 +02:00
protected float gravityDragPerTick;
2020-05-21 00:33:56 +02:00
protected float eyeHeight;
2019-08-27 20:49:11 +02:00
private boolean autoViewable;
private final int id;
private Data data;
private final Set<Player> viewers = new CopyOnWriteArraySet<>();
protected UUID uuid;
2019-08-10 08:44:35 +02:00
private boolean isActive; // False if entity has only been instanced without being added somewhere
private boolean removed;
2019-08-10 08:44:35 +02:00
private boolean shouldRemove;
2019-08-21 16:50:52 +02:00
private long scheduledRemoveTime;
private final Set<Entity> passengers = new CopyOnWriteArraySet<>();
2019-08-21 16:50:52 +02:00
private long lastUpdate;
private final EntityType entityType;
// Network synchronization
private static final long SYNCHRONIZATION_DELAY = 1500; // In ms
2019-08-24 20:34:01 +02:00
private long lastSynchronizationTime;
// Events
2020-10-06 04:06:59 +02:00
private final Map<Class<? extends Event>, Collection<EventCallback>> eventCallbacks = new ConcurrentHashMap<>();
2019-08-21 16:50:52 +02:00
// Metadata
protected boolean onFire;
protected boolean crouched;
2019-08-19 17:04:19 +02:00
protected boolean UNUSED_METADATA;
protected boolean sprinting;
protected boolean swimming;
protected boolean invisible;
protected boolean glowing;
protected boolean usingElytra;
protected int air = 300;
2020-06-22 23:25:00 +02:00
protected ColoredText customName;
2019-08-19 17:04:19 +02:00
protected boolean customNameVisible;
protected boolean silent;
protected boolean noGravity;
protected Pose pose = Pose.STANDING;
// list of scheduled tasks to be executed during the next entity tick
protected final ConcurrentLinkedQueue<Consumer<Entity>> nextTick = new ConcurrentLinkedQueue<>();
// Tick related
private long ticks;
private final EntityTickEvent tickEvent = new EntityTickEvent(this);
2020-10-24 10:46:23 +02:00
public Entity(@NotNull EntityType entityType, @NotNull Position spawnPosition) {
2019-08-10 08:44:35 +02:00
this.id = generateId();
2019-08-19 17:04:19 +02:00
this.entityType = entityType;
2019-08-10 08:44:35 +02:00
this.uuid = UUID.randomUUID();
2020-04-09 14:25:42 +02:00
this.position = spawnPosition.clone();
2019-08-21 16:50:52 +02:00
2019-08-30 01:17:46 +02:00
setBoundingBox(0, 0, 0);
setAutoViewable(true);
2019-09-03 07:36:04 +02:00
entityById.put(id, this);
setVelocityUpdatePeriod(5);
2019-08-10 08:44:35 +02:00
}
/**
2020-10-15 21:16:31 +02:00
* Schedules a task to be run during the next entity tick.
* It ensures that the task will be executed in the same thread as the entity (depending of the {@link ThreadProvider}).
*
* @param callback the task to execute during the next entity tick
*/
2020-10-24 10:46:23 +02:00
public void scheduleNextTick(@NotNull Consumer<Entity> callback) {
this.nextTick.add(callback);
}
2020-10-24 10:46:23 +02:00
public Entity(@NotNull EntityType entityType) {
2020-03-29 20:58:30 +02:00
this(entityType, new Position());
}
2020-05-15 18:03:28 +02:00
/**
* @param id the entity unique id ({@link #getEntityId()})
* @return the entity having the specified id, null if not found
*/
2020-10-24 10:46:23 +02:00
@Nullable
2019-08-21 16:50:52 +02:00
public static Entity getEntity(int id) {
2020-05-15 18:03:28 +02:00
return entityById.getOrDefault(id, null);
2019-08-21 16:50:52 +02:00
}
2019-08-19 17:04:19 +02:00
2019-08-10 08:44:35 +02:00
private static int generateId() {
2019-08-12 08:30:59 +02:00
return lastEntityId.incrementAndGet();
2019-08-10 08:44:35 +02:00
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Called each tick.
*
* @param time the time of update in milliseconds
2020-05-15 18:03:28 +02:00
*/
public abstract void update(long time);
2019-08-19 17:04:19 +02:00
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Called when a new instance is set.
2020-05-15 18:03:28 +02:00
*/
2019-08-24 20:34:01 +02:00
public abstract void spawn();
2020-03-29 20:58:30 +02:00
public boolean isOnGround() {
return onGround || EntityUtils.isOnGround(this) /* backup for levitating entities */;
}
/**
2020-10-15 21:16:31 +02:00
* Checks if now is a good time to send a velocity update packet.
*
2020-08-06 07:42:00 +02:00
* @param time the current time in milliseconds
* @return true if the velocity update packet should be send
*/
protected boolean shouldSendVelocityUpdate(long time) {
return (time - lastVelocityUpdateTime) >= velocityUpdatePeriod;
}
/**
2020-10-15 21:16:31 +02:00
* Gets the period, in ms, between two velocity update packets.
*
* @return period, in ms, between two velocity update packets
*/
public long getVelocityUpdatePeriod() {
return velocityUpdatePeriod;
}
/**
2020-10-15 21:16:31 +02:00
* 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;
2020-03-29 20:58:30 +02:00
}
2019-08-27 20:49:11 +02:00
2020-08-06 07:42:00 +02:00
/**
2020-10-15 21:16:31 +02:00
* Teleports the entity only if the chunk at {@code position} is loaded or if
* {@link Instance#hasEnabledAutoChunkLoad()} returns true.
2020-08-06 07:42:00 +02:00
*
* @param position the teleport position
2020-10-24 10:46:23 +02:00
* @param callback the optional callback executed, even if auto chunk is not enabled
2020-08-06 07:42:00 +02:00
*/
2020-10-24 10:46:23 +02:00
public void teleport(@NotNull Position position, @Nullable Runnable callback) {
Check.notNull(position, "Teleport position cannot be null");
2020-05-23 04:20:01 +02:00
Check.stateCondition(instance == null, "You need to use Entity#setInstance before teleporting an entity!");
2019-08-21 16:50:52 +02:00
2020-08-09 10:59:12 +02:00
final Runnable runnable = () -> {
if (!this.position.isSimilar(position)) {
refreshPosition(position.getX(), position.getY(), position.getZ());
}
if (!this.position.hasSimilarView(position)) {
refreshView(position.getYaw(), position.getPitch());
}
sendSynchronization();
2020-10-22 22:55:40 +02:00
OptionalCallback.execute(callback);
};
2019-08-25 20:03:43 +02:00
if (instance.hasEnabledAutoChunkLoad()) {
instance.loadChunk(position, chunk -> runnable.run());
2019-08-25 20:03:43 +02:00
} else {
runnable.run();
2019-08-25 20:03:43 +02:00
}
}
2020-10-24 10:46:23 +02:00
public void teleport(@NotNull Position position) {
2019-08-25 20:03:43 +02:00
teleport(position, null);
2019-08-21 16:50:52 +02:00
}
/**
2020-10-15 21:16:31 +02:00
* Changes the view of the entity.
*
* @param yaw the new yaw
* @param pitch the new pitch
*/
public void setView(float yaw, float pitch) {
refreshView(yaw, pitch);
EntityRotationPacket entityRotationPacket = new EntityRotationPacket();
entityRotationPacket.entityId = getEntityId();
entityRotationPacket.yaw = yaw;
entityRotationPacket.pitch = pitch;
entityRotationPacket.onGround = onGround;
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
entityHeadLookPacket.entityId = getEntityId();
entityHeadLookPacket.yaw = yaw;
sendPacketToViewersAndSelf(entityHeadLookPacket);
sendPacketToViewersAndSelf(entityRotationPacket);
}
/**
2020-10-15 21:16:31 +02:00
* Changes the view of the entity.
* Only the yaw and pitch are used.
*
* @param position the new view
*/
2020-10-24 10:46:23 +02:00
public void setView(@NotNull Position position) {
setView(position.getYaw(), position.getPitch());
}
/**
* When set to true, the entity will automatically get new viewers when they come too close
* This can be use to complete control over which player can see it, without having to deal with
2020-10-15 21:16:31 +02:00
* raw packets.
* <p>
2020-10-15 21:16:31 +02:00
* True by default for all entities.
* When set to false, it is important to mention that the players will not be removed automatically from its viewers
2020-10-15 21:16:31 +02:00
* list, you would have to do that manually when being too far.
*
* @return true if the entity is automatically viewable for close players, false otherwise
*/
public boolean isAutoViewable() {
return autoViewable;
}
/**
2020-10-24 10:46:23 +02:00
* Makes the entity auto viewable or only manually.
*
* @param autoViewable should the entity be automatically viewable for close players
2020-10-24 10:46:23 +02:00
* @see #isAutoViewable()
*/
public void setAutoViewable(boolean autoViewable) {
this.autoViewable = autoViewable;
}
@Override
2020-10-24 10:46:23 +02:00
public boolean addViewer(@NotNull Player player) {
Check.notNull(player, "Viewer cannot be null");
boolean result = this.viewers.add(player);
2020-05-29 02:11:41 +02:00
if (!result)
return false;
2019-09-01 06:18:41 +02:00
player.viewableEntities.add(this);
return true;
}
@Override
2020-10-24 10:46:23 +02:00
public boolean removeViewer(@NotNull Player player) {
Check.notNull(player, "Viewer cannot be null");
if (!viewers.remove(player))
return false;
DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket();
destroyEntitiesPacket.entityIds = new int[]{getEntityId()};
player.getPlayerConnection().sendPacket(destroyEntitiesPacket);
2019-09-01 06:18:41 +02:00
player.viewableEntities.remove(this);
return true;
}
2020-10-24 10:46:23 +02:00
@NotNull
@Override
public Set<Player> getViewers() {
return Collections.unmodifiableSet(viewers);
}
@Override
public Data getData() {
return data;
}
@Override
2020-10-24 10:46:23 +02:00
public void setData(@Nullable Data data) {
this.data = data;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Updates the entity, called every tick.
2020-05-29 23:17:14 +02:00
*
2020-08-06 07:42:00 +02:00
* @param time the update time in milliseconds
2020-05-29 23:17:14 +02:00
*/
2020-02-09 15:34:09 +01:00
public void tick(long time) {
if (instance == null)
return;
2019-08-21 16:50:52 +02:00
if (scheduledRemoveTime != 0) { // Any entity with scheduled remove does not update
2020-07-24 16:11:48 +02:00
final boolean finished = time >= scheduledRemoveTime;
2019-08-21 16:50:52 +02:00
if (finished) {
remove();
}
return;
}
if (shouldRemove()) {
remove();
return;
}
2020-08-16 00:01:10 +02:00
BlockPosition blockPosition = position.toBlockPosition();
if (!ChunkUtils.isLoaded(instance, position.getX(), position.getZ()) || !ChunkUtils.isLoaded(instance, blockPosition.getX(), blockPosition.getZ())) {
// No update for entities in unloaded chunk
return;
}
// scheduled tasks
if (!nextTick.isEmpty()) {
Consumer<Entity> callback;
while ((callback = nextTick.poll()) != null) {
callback.accept(this);
}
}
// Synchronization with updated fields in #getPosition()
{
// X/Y/Z axis
if (cacheX != position.getX() ||
cacheY != position.getY() ||
cacheZ != position.getZ()) {
teleport(position);
}
// Yaw/Pitch
if (cacheYaw != position.getYaw() ||
cachePitch != position.getPitch()) {
setView(position);
}
}
if (shouldUpdate(time)) {
2019-08-29 02:15:52 +02:00
this.lastUpdate = time;
2019-08-24 20:34:01 +02:00
// Velocity
final boolean applyVelocity = !PlayerUtils.isNettyClient(this) ||
(PlayerUtils.isNettyClient(this) && hasVelocity());
2020-05-25 11:01:38 +02:00
if (applyVelocity) {
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);
2020-05-25 11:01:38 +02:00
if (!noGravity) {
velocity.setY(velocity.getY() - gravityDragPerTick * tps);
2019-09-14 18:00:18 +02:00
}
Vector newVelocityOut = new Vector();
2020-07-24 16:11:48 +02:00
final Vector deltaPos = new Vector(
getVelocity().getX() / tps,
getVelocity().getY() / tps,
getVelocity().getZ() / tps
);
onGround = CollisionUtils.handlePhysics(this, deltaPos, newPosition, newVelocityOut);
2020-08-13 20:43:45 +02:00
// Check chunk
if (!ChunkUtils.isLoaded(instance, newPosition.getX(), newPosition.getZ())) {
return;
}
// World border collision
{
2020-07-24 16:11:48 +02:00
final WorldBorder worldBorder = instance.getWorldBorder();
final WorldBorder.CollisionAxis collisionAxis = worldBorder.getCollisionAxis(newPosition);
switch (collisionAxis) {
case NONE:
// Apply velocity + gravity
refreshPosition(newPosition);
break;
case BOTH:
// Apply Y velocity/gravity
refreshPosition(position.getX(), newPosition.getY(), position.getZ());
break;
case X:
// Apply Y/Z velocity/gravity
refreshPosition(position.getX(), newPosition.getY(), newPosition.getZ());
break;
case Z:
// Apply X/Y velocity/gravity
refreshPosition(newPosition.getX(), newPosition.getY(), position.getZ());
break;
}
}
velocity.copy(newVelocityOut);
velocity.multiply(tps);
float drag;
if (onGround) {
2020-08-06 04:54:02 +02:00
final CustomBlock customBlock =
instance.getCustomBlock(blockPosition);
if (customBlock != null) {
// Custom drag
drag = customBlock.getDrag(instance, blockPosition);
} else {
// Default ground drag
drag = 0.5f;
}
2020-05-25 11:01:38 +02:00
// Stop player velocity
if (PlayerUtils.isNettyClient(this)) {
velocity.zero();
}
} else {
drag = 0.98f; // air drag
2019-08-24 20:34:01 +02:00
}
velocity.setX(velocity.getX() * drag);
velocity.setZ(velocity.getZ() * drag);
2020-05-24 22:24:59 +02:00
sendSynchronization();
if (shouldSendVelocityUpdate(time)) {
sendVelocityPacket();
lastVelocityUpdateTime = time;
2019-08-27 20:49:11 +02:00
}
}
// handle block contacts
2020-07-24 16:11:48 +02:00
final int minX = (int) Math.floor(boundingBox.getMinX());
final int maxX = (int) Math.ceil(boundingBox.getMaxX());
final int minY = (int) Math.floor(boundingBox.getMinY());
final int maxY = (int) Math.ceil(boundingBox.getMaxY());
final int minZ = (int) Math.floor(boundingBox.getMinZ());
final int maxZ = (int) Math.ceil(boundingBox.getMaxZ());
final BlockPosition tmpPosition = new BlockPosition(0, 0, 0); // allow reuse
for (int y = minY; y <= maxY; y++) {
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
2020-08-13 20:43:45 +02:00
final Chunk chunk = instance.getChunkAt(x, z);
if (!ChunkUtils.isLoaded(chunk))
continue;
2020-08-13 20:43:45 +02:00
final CustomBlock customBlock = chunk.getCustomBlock(x, y, z);
if (customBlock != null) {
tmpPosition.setX(x);
tmpPosition.setY(y);
tmpPosition.setZ(z);
// checks that we are actually in the block, and not just here because of a rounding error
if (boundingBox.intersect(tmpPosition)) {
// TODO: replace with check with custom block bounding box
customBlock.handleContact(instance, tmpPosition, this);
}
}
}
}
}
handleVoid();
2020-03-29 20:58:30 +02:00
// Call the abstract update method
update(time);
2019-08-24 20:34:01 +02:00
ticks++;
callEvent(EntityTickEvent.class, tickEvent); // reuse tickEvent to avoid recreating it each tick
}
// Scheduled synchronization
if (time - lastSynchronizationTime >= SYNCHRONIZATION_DELAY) {
lastSynchronizationTime = time;
sendSynchronization();
2019-08-19 17:04:19 +02:00
}
if (shouldRemove()) {
remove();
}
2019-08-19 17:04:19 +02:00
}
/**
2020-10-15 21:16:31 +02:00
* Equivalent to <code>sendPacketsToViewers(getVelocityPacket());</code>.
*/
public void sendVelocityPacket() {
sendPacketsToViewers(getVelocityPacket());
}
/**
2020-10-15 21:16:31 +02:00
* Gets the number of ticks this entity has been active for.
2020-05-08 06:54:33 +02:00
*
2020-05-29 23:17:14 +02:00
* @return the number of ticks this entity has been active for
*/
public long getAliveTicks() {
return ticks;
}
/**
* How does this entity handle being in the void?
*/
protected void handleVoid() {
// Kill if in void
2020-04-28 01:20:11 +02:00
if (getInstance().isInVoid(this.position)) {
remove();
}
}
2020-10-24 11:19:54 +02:00
@NotNull
2020-05-08 06:54:33 +02:00
@Override
2020-10-06 04:06:59 +02:00
public Map<Class<? extends Event>, Collection<EventCallback>> getEventCallbacksMap() {
return eventCallbacks;
2020-09-20 20:11:46 +02:00
}
@Override
2020-10-24 11:19:54 +02:00
public <E extends Event> void callEvent(@NotNull Class<E> eventClass, @NotNull E event) {
EventHandler.super.callEvent(eventClass, event);
// Call the same event for the current entity instance
if (instance != null) {
instance.callEvent(eventClass, event);
}
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Each entity has an unique id which will change after a restart.
2020-10-22 12:21:50 +02:00
* <p>
2020-10-15 21:16:31 +02:00
* All entities can be retrieved by calling {@link Entity#getEntity(int)}.
2020-05-15 18:03:28 +02:00
*
* @return the unique entity id
*/
2019-08-10 08:44:35 +02:00
public int getEntityId() {
return id;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-22 12:21:50 +02:00
* Returns the entity type.
2020-05-29 23:17:14 +02:00
*
2020-10-22 12:21:50 +02:00
* @return the entity type
2020-05-29 23:17:14 +02:00
*/
public EntityType getEntityType() {
2019-08-19 17:04:19 +02:00
return entityType;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-22 12:21:50 +02:00
* Gets the entity {@link UUID}.
2020-05-29 23:17:14 +02:00
*
2020-10-22 12:21:50 +02:00
* @return the entity unique id
2020-05-29 23:17:14 +02:00
*/
2020-10-24 10:46:23 +02:00
@NotNull
2019-08-10 08:44:35 +02:00
public UUID getUuid() {
return uuid;
}
2020-06-21 22:11:56 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the internal entity UUID, mostly unsafe.
2020-06-21 22:11:56 +02:00
*
* @param uuid the new entity uuid
*/
2020-10-24 10:46:23 +02:00
protected void setUuid(@NotNull UUID uuid) {
2020-06-21 22:11:56 +02:00
this.uuid = uuid;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Returns false just after instantiation, set to true after calling {@link #setInstance(Instance)}.
2020-05-29 23:17:14 +02:00
*
* @return true if the entity has been linked to an instance, false otherwise
*/
2019-08-10 08:44:35 +02:00
public boolean isActive() {
return isActive;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Is used to check collision with coordinates or other blocks/entities.
2020-05-29 23:17:14 +02:00
*
* @return the entity bounding box
*/
2020-10-24 10:46:23 +02:00
@NotNull
2019-08-30 01:17:46 +02:00
public BoundingBox getBoundingBox() {
return boundingBox;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the internal entity bounding box.
2020-05-29 23:17:14 +02:00
* <p>
2020-10-15 21:16:31 +02:00
* WARNING: this does not change the entity hit-box which is client-side.
2020-05-29 23:17:14 +02:00
*
* @param x the bounding box X size
* @param y the bounding box Y size
* @param z the bounding box Z size
*/
2019-08-30 01:17:46 +02:00
public void setBoundingBox(float x, float y, float z) {
this.boundingBox = new BoundingBox(this, x, y, z);
}
2020-09-03 00:43:42 +02:00
/**
2020-10-22 12:21:50 +02:00
* Convenient method to get the entity current chunk.
2020-09-03 00:43:42 +02:00
*
2020-10-29 22:52:07 +01:00
* @return the entity chunk, can be null even if unlikely
2020-09-03 00:43:42 +02:00
*/
2020-10-29 22:52:07 +01:00
@Nullable
2020-09-03 00:43:42 +02:00
public Chunk getChunk() {
return instance.getChunkAt(lastX, lastZ);
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the entity current instance.
2020-05-29 23:17:14 +02:00
*
* @return the entity instance, can be null if the entity doesn't have an instance yet
2020-05-29 23:17:14 +02:00
*/
@Nullable
2019-08-11 07:42:56 +02:00
public Instance getInstance() {
return instance;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the entity instance.
2020-05-29 23:17:14 +02:00
*
* @param instance the new instance of the entity
* @throws NullPointerException if {@code instance} is null
2020-08-15 13:32:36 +02:00
* @throws IllegalStateException if {@code instance} has not been registered in {@link InstanceManager}
2020-05-29 23:17:14 +02:00
*/
2020-10-24 10:46:23 +02:00
public void setInstance(@NotNull Instance instance) {
2020-05-23 04:20:01 +02:00
Check.notNull(instance, "instance cannot be null!");
2020-08-15 13:32:36 +02:00
Check.stateCondition(!instance.isRegistered(),
2020-09-28 18:27:28 +02:00
"Instances need to be registered, please use InstanceManager#registerInstance or InstanceManager#registerSharedInstance");
2019-08-11 07:42:56 +02:00
if (this.instance != null) {
this.instance.UNSAFE_removeEntity(this);
2019-08-10 08:44:35 +02:00
}
2019-08-11 07:42:56 +02:00
this.isActive = true;
this.instance = instance;
instance.UNSAFE_addEntity(this);
2019-08-24 20:34:01 +02:00
spawn();
2020-09-26 21:24:10 +02:00
EntitySpawnEvent entitySpawnEvent = new EntitySpawnEvent(this, instance);
callEvent(EntitySpawnEvent.class, entitySpawnEvent);
2019-08-24 20:34:01 +02:00
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the entity current velocity.
2020-05-29 23:17:14 +02:00
*
* @return the entity current velocity
*/
2020-10-24 10:46:23 +02:00
@NotNull
2019-08-25 20:03:43 +02:00
public Vector getVelocity() {
return velocity;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the entity velocity and calls {@link EntityVelocityEvent}.
2020-05-29 23:17:14 +02:00
* <p>
2020-10-15 21:16:31 +02:00
* The final velocity can be cancelled or modified by the event.
2020-05-29 23:17:14 +02:00
*
* @param velocity the new entity velocity
*/
2020-10-24 10:46:23 +02:00
public void setVelocity(@NotNull Vector velocity) {
EntityVelocityEvent entityVelocityEvent = new EntityVelocityEvent(this, velocity);
callCancellableEvent(EntityVelocityEvent.class, entityVelocityEvent, () -> {
this.velocity.copy(entityVelocityEvent.getVelocity());
sendPacketToViewersAndSelf(getVelocityPacket());
});
2019-08-10 08:44:35 +02:00
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the entity currently has a velocity applied.
2020-08-06 07:42:00 +02:00
*
2020-05-29 23:17:14 +02:00
* @return true if velocity is not set to 0
*/
public boolean hasVelocity() {
return velocity.getX() != 0 ||
velocity.getY() != 0 ||
velocity.getZ() != 0;
}
/**
* Changes the gravity of the entity.
2020-05-29 23:17:14 +02:00
*
2020-08-06 07:42:00 +02:00
* @param gravityDragPerTick the gravity drag per tick
2020-05-29 23:17:14 +02:00
*/
2019-08-27 20:49:11 +02:00
public void setGravity(float gravityDragPerTick) {
this.gravityDragPerTick = gravityDragPerTick;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the distance between two entities.
2020-05-29 23:17:14 +02:00
*
* @param entity the entity to get the distance from
* @return the distance between this and {@code entity}
*/
2020-10-24 10:46:23 +02:00
public float getDistance(@NotNull Entity entity) {
Check.notNull(entity, "Entity cannot be null");
2019-08-21 16:50:52 +02:00
return getPosition().getDistance(entity.getPosition());
2019-08-19 17:04:19 +02:00
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the entity vehicle or null.
2020-05-29 23:17:14 +02:00
*
* @return the entity vehicle, or null if there is not any
*/
2020-10-24 10:46:23 +02:00
@Nullable
2019-08-27 05:23:25 +02:00
public Entity getVehicle() {
return vehicle;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Adds a new passenger to this entity.
2020-05-29 23:17:14 +02:00
*
* @param entity the new passenger
* @throws NullPointerException if {@code entity} is null
* @throws IllegalStateException if {@link #getInstance()} returns null
*/
2020-10-24 10:46:23 +02:00
public void addPassenger(@NotNull Entity entity) {
2020-05-24 19:59:50 +02:00
Check.notNull(entity, "Passenger cannot be null");
2020-05-23 04:20:01 +02:00
Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance");
2019-08-27 05:23:25 +02:00
if (entity.getVehicle() != null) {
entity.getVehicle().removePassenger(entity);
}
this.passengers.add(entity);
entity.vehicle = this;
2019-08-30 01:17:46 +02:00
sendPacketToViewersAndSelf(getPassengersPacket());
2019-08-27 05:23:25 +02:00
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Removes a passenger to this entity.
2020-05-29 23:17:14 +02:00
*
* @param entity the passenger to remove
* @throws NullPointerException if {@code entity} is null
* @throws IllegalStateException if {@link #getInstance()} returns null
*/
2020-10-24 10:46:23 +02:00
public void removePassenger(@NotNull Entity entity) {
2020-05-24 19:59:50 +02:00
Check.notNull(entity, "Passenger cannot be null");
2020-05-23 04:20:01 +02:00
Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance");
2020-05-24 19:59:50 +02:00
if (!passengers.remove(entity))
2019-08-27 05:23:25 +02:00
return;
entity.vehicle = null;
2019-08-30 01:17:46 +02:00
sendPacketToViewersAndSelf(getPassengersPacket());
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the entity has any passenger.
2020-05-29 23:17:14 +02:00
*
* @return true if the entity has any passenger, false otherwise
*/
public boolean hasPassenger() {
return !passengers.isEmpty();
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the entity passengers.
2020-05-29 23:17:14 +02:00
*
2020-05-24 19:59:50 +02:00
* @return an unmodifiable list containing all the entity passengers
*/
2020-10-24 10:46:23 +02:00
@NotNull
public Set<Entity> getPassengers() {
return Collections.unmodifiableSet(passengers);
}
2020-10-24 10:46:23 +02:00
@NotNull
2019-08-30 01:17:46 +02:00
protected SetPassengersPacket getPassengersPacket() {
2019-08-27 05:23:25 +02:00
SetPassengersPacket passengersPacket = new SetPassengersPacket();
passengersPacket.vehicleEntityId = getEntityId();
int[] passengers = new int[this.passengers.size()];
int counter = 0;
for (Entity passenger : this.passengers) {
passengers[counter++] = passenger.getEntityId();
}
passengersPacket.passengersId = passengers;
2019-08-30 01:17:46 +02:00
return passengersPacket;
2019-08-27 05:23:25 +02:00
}
2020-05-24 19:59:50 +02:00
/**
2020-10-22 12:21:50 +02:00
* Entity statuses can be found <a href="https://wiki.vg/Entity_statuses">here</a>.
2020-05-24 19:59:50 +02:00
*
* @param status the status to trigger
*/
2019-08-21 16:50:52 +02:00
public void triggerStatus(byte status) {
EntityStatusPacket statusPacket = new EntityStatusPacket();
statusPacket.entityId = getEntityId();
statusPacket.status = status;
2020-04-20 23:43:09 +02:00
sendPacketToViewersAndSelf(statusPacket);
2019-08-21 16:50:52 +02:00
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the entity is on fire.
2020-05-29 23:17:14 +02:00
*
2020-05-24 19:59:50 +02:00
* @return true if the entity is in fire, false otherwise
*/
public boolean isOnFire() {
return onFire;
}
/**
2020-10-15 21:16:31 +02:00
* Sets the entity in fire visually.
2020-05-24 19:59:50 +02:00
* <p>
* WARNING: if you want to apply damage or specify a duration,
2020-10-15 21:16:31 +02:00
* see {@link LivingEntity#setFireForDuration(int, TimeUnit)}.
2020-05-24 19:59:50 +02:00
*
* @param fire should the entity be set in fire
*/
2019-08-20 22:40:57 +02:00
public void setOnFire(boolean fire) {
this.onFire = fire;
2019-08-23 15:37:38 +02:00
sendMetadataIndex(0);
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the entity is invisible or not.
2020-05-29 23:17:14 +02:00
*
* @return true if the entity is invisible, false otherwise
*/
2020-05-26 00:07:35 +02:00
public boolean isInvisible() {
return invisible;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the internal invisible value and send a {@link EntityMetaDataPacket}
* to make visible or invisible the entity to its viewers.
2020-05-29 23:17:14 +02:00
*
* @param invisible true to set the entity invisible, false otherwise
*/
2020-05-26 00:07:35 +02:00
public void setInvisible(boolean invisible) {
this.invisible = invisible;
sendMetadataIndex(0);
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the entity is glowing or not.
2020-05-29 23:17:14 +02:00
*
2020-05-24 19:59:50 +02:00
* @return true if the entity is glowing, false otherwise
*/
2019-08-27 20:49:11 +02:00
public boolean isGlowing() {
return glowing;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Sets or remove the entity glowing effect.
2020-05-29 23:17:14 +02:00
*
* @param glowing true to make the entity glows, false otherwise
*/
public void setGlowing(boolean glowing) {
this.glowing = glowing;
sendMetadataIndex(0);
}
/**
2020-10-15 21:16:31 +02:00
* Gets the entity custom name.
2020-05-29 23:17:14 +02:00
*
* @return the custom name of the entity, null if there is not
*/
2020-06-22 23:25:00 +02:00
public ColoredText getCustomName() {
2020-05-26 00:07:35 +02:00
return customName;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the entity custom name.
2020-05-29 23:17:14 +02:00
*
* @param customName the custom name of the entity, null to remove it
*/
2020-06-22 23:25:00 +02:00
public void setCustomName(ColoredText customName) {
2020-05-26 00:07:35 +02:00
this.customName = customName;
sendMetadataIndex(2);
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the custom name visible metadata field.
2020-05-29 23:17:14 +02:00
*
* @return true if the custom name is visible, false otherwise
*/
2020-05-26 00:07:35 +02:00
public boolean isCustomNameVisible() {
return customNameVisible;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the internal custom name visible field and send a {@link EntityMetaDataPacket}
* to update the entity state to its viewers.
2020-05-29 23:17:14 +02:00
*
* @param customNameVisible true to make the custom name visible, false otherwise
*/
2020-05-26 00:07:35 +02:00
public void setCustomNameVisible(boolean customNameVisible) {
this.customNameVisible = customNameVisible;
sendMetadataIndex(3);
}
public boolean isSilent() {
return silent;
}
public void setSilent(boolean silent) {
this.silent = silent;
sendMetadataIndex(4);
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the noGravity metadata field and change the gravity behaviour accordingly.
2020-05-29 23:17:14 +02:00
*
2020-05-24 19:59:50 +02:00
* @param noGravity should the entity ignore gravity
*/
2019-08-23 15:37:38 +02:00
public void setNoGravity(boolean noGravity) {
this.noGravity = noGravity;
sendMetadataIndex(5);
2019-08-20 22:40:57 +02:00
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the noGravity metadata field.
2020-05-29 23:17:14 +02:00
*
2020-05-24 19:59:50 +02:00
* @return true if the entity ignore gravity, false otherwise
*/
2019-08-27 20:49:11 +02:00
public boolean hasNoGravity() {
return noGravity;
2019-08-21 16:50:52 +02:00
}
2020-05-24 19:59:50 +02:00
/**
* Used to refresh the entity and its passengers position
* - put the entity in the right instance chunk
* - update the viewable chunks (load and unload)
* - add/remove players from the viewers list if {@link #isAutoViewable()} is enabled
* <p>
2020-10-12 06:41:47 +02:00
* WARNING: unsafe, should only be used internally in Minestom. Use {@link #teleport(Position)} instead.
2020-05-24 19:59:50 +02:00
*
* @param x new position X
* @param y new position Y
* @param z new position Z
*/
2019-08-21 16:50:52 +02:00
public void refreshPosition(float x, float y, float z) {
this.lastX = position.getX();
this.lastY = position.getY();
this.lastZ = position.getZ();
position.setX(x);
position.setY(y);
position.setZ(z);
this.cacheX = x;
this.cacheY = y;
this.cacheZ = z;
2019-08-11 07:42:56 +02:00
2020-05-23 14:04:53 +02:00
if (hasPassenger()) {
for (Entity passenger : getPassengers()) {
passenger.refreshPosition(x, y, z);
}
2019-08-30 01:17:46 +02:00
}
2020-07-22 17:39:48 +02:00
final Instance instance = getInstance();
2019-08-11 07:42:56 +02:00
if (instance != null) {
2020-07-22 17:39:48 +02:00
final Chunk lastChunk = instance.getChunkAt(lastX, lastZ);
final Chunk newChunk = instance.getChunkAt(x, z);
2019-08-11 08:57:23 +02:00
if (lastChunk != null && newChunk != null && lastChunk != newChunk) {
2019-08-24 21:41:43 +02:00
synchronized (instance) {
instance.removeEntityFromChunk(this, lastChunk);
instance.addEntityToChunk(this, newChunk);
2019-08-30 01:17:46 +02:00
}
updateView(lastChunk, newChunk);
2019-08-11 07:42:56 +02:00
}
}
2019-08-10 08:44:35 +02:00
}
2020-05-29 23:17:14 +02:00
/**
* @param position the new position
* @see #refreshPosition(float, float, float)
*/
2020-10-24 10:46:23 +02:00
public void refreshPosition(@NotNull Position position) {
2019-08-27 20:49:11 +02:00
refreshPosition(position.getX(), position.getY(), position.getZ());
}
2020-10-12 06:41:47 +02:00
/**
2020-10-15 21:16:31 +02:00
* Manages viewable entities automatically if {@link #isAutoViewable()} is enabled.
2020-10-12 06:41:47 +02:00
* <p>
* Called by {@link #refreshPosition(float, float, float)} when the new position is in a different {@link Chunk}.
*
* @param lastChunk the previous {@link Chunk} of this entity
* @param newChunk the new {@link Chunk} of this entity
*/
2020-10-24 10:46:23 +02:00
private void updateView(@NotNull Chunk lastChunk, @NotNull Chunk newChunk) {
2020-07-22 17:39:48 +02:00
final boolean isPlayer = this instanceof Player;
if (isPlayer)
((Player) this).onChunkChange(newChunk); // Refresh loaded chunk
2019-08-30 01:17:46 +02:00
// Refresh entity viewable list
final int entityViewDistance = MinecraftServer.getEntityViewDistance();
final long[] lastVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), entityViewDistance);
final long[] updatedVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), entityViewDistance);
2019-08-30 01:17:46 +02:00
2020-10-12 06:41:47 +02:00
// Remove from previous chunks
2020-07-22 17:39:48 +02:00
final int[] oldChunksEntity = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunksEntity, updatedVisibleChunksEntity);
2019-08-30 01:17:46 +02:00
for (int index : oldChunksEntity) {
final long chunkIndex = lastVisibleChunksEntity[index];
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
2019-08-30 01:17:46 +02:00
if (chunk == null)
continue;
instance.getChunkEntities(chunk).forEach(ent -> {
if (ent instanceof Player) {
2020-07-22 17:39:48 +02:00
final Player player = (Player) ent;
if (isAutoViewable())
removeViewer(player);
2019-08-30 01:17:46 +02:00
if (isPlayer) {
player.removeViewer((Player) this);
2019-08-30 01:17:46 +02:00
}
} else if (isPlayer) {
ent.removeViewer((Player) this);
2019-08-30 01:17:46 +02:00
}
});
}
2020-10-12 06:41:47 +02:00
// Add to new chunks
2020-07-22 17:39:48 +02:00
final int[] newChunksEntity = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunksEntity, lastVisibleChunksEntity);
2019-08-30 01:17:46 +02:00
for (int index : newChunksEntity) {
final long chunkIndex = updatedVisibleChunksEntity[index];
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
2019-08-30 01:17:46 +02:00
if (chunk == null)
continue;
instance.getChunkEntities(chunk).forEach(ent -> {
if (ent instanceof Player) {
Player player = (Player) ent;
if (isAutoViewable())
addViewer(player);
if (this instanceof Player) {
player.addViewer((Player) this);
2019-08-30 01:17:46 +02:00
}
} else if (isPlayer) {
ent.addViewer((Player) this);
2019-08-30 01:17:46 +02:00
}
});
}
}
2020-05-27 20:55:33 +02:00
/**
2020-10-15 21:16:31 +02:00
* Updates the entity view internally.
2020-05-27 20:55:33 +02:00
* <p>
2020-10-15 21:16:31 +02:00
* Warning: you probably want to use {@link #setView(float, float)}.
2020-05-27 20:55:33 +02:00
*
* @param yaw the yaw
* @param pitch the pitch
*/
2019-08-20 22:40:57 +02:00
public void refreshView(float yaw, float pitch) {
2019-08-21 16:50:52 +02:00
this.lastYaw = position.getYaw();
this.lastPitch = position.getPitch();
position.setYaw(yaw);
position.setPitch(pitch);
this.cacheYaw = yaw;
this.cachePitch = pitch;
2019-08-20 22:40:57 +02:00
}
2020-10-12 06:41:47 +02:00
/**
2020-10-15 21:16:31 +02:00
* Makes the entity sneak.
2020-10-12 06:41:47 +02:00
* <p>
* WARNING: this will not work for the client itself.
*
* @param sneaking true to make the entity sneak
*/
public void setSneaking(boolean sneaking) {
2019-08-20 22:40:57 +02:00
this.crouched = sneaking;
2019-09-10 06:59:15 +02:00
this.pose = sneaking ? Pose.SNEAKING : Pose.STANDING;
2019-08-23 15:37:38 +02:00
sendMetadataIndex(0);
2019-09-10 06:59:15 +02:00
sendMetadataIndex(6);
2019-08-20 22:40:57 +02:00
}
2020-10-12 06:41:47 +02:00
/**
2020-10-15 21:16:31 +02:00
* Makes the entity sprint.
2020-10-12 06:41:47 +02:00
* <p>
* WARNING: this will not work on the client itself.
*
* @param sprinting true to make the entity sprint
*/
public void setSprinting(boolean sprinting) {
2019-08-20 22:40:57 +02:00
this.sprinting = sprinting;
2019-08-23 15:37:38 +02:00
sendMetadataIndex(0);
2019-08-20 22:40:57 +02:00
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the entity position.
2020-05-29 23:17:14 +02:00
*
2020-05-15 18:03:28 +02:00
* @return the current position of the entity
*/
2019-08-21 16:50:52 +02:00
public Position getPosition() {
return position;
2019-08-19 17:04:19 +02:00
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the entity eye height.
2020-05-29 23:17:14 +02:00
*
* @return the entity eye height
*/
2020-05-21 00:33:56 +02:00
public float getEyeHeight() {
return eyeHeight;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the entity eye height.
2020-05-29 23:17:14 +02:00
*
2020-08-08 10:20:36 +02:00
* @param eyeHeight the entity eye height
2020-05-29 23:17:14 +02:00
*/
2020-05-21 00:33:56 +02:00
public void setEyeHeight(float eyeHeight) {
this.eyeHeight = eyeHeight;
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if this entity is in the same chunk as the specified position.
2020-05-29 23:17:14 +02:00
*
2020-05-15 18:03:28 +02:00
* @param position the checked position chunk
* @return true if the entity is in the same chunk as {@code position}
*/
2020-10-24 10:46:23 +02:00
public boolean sameChunk(@NotNull Position position) {
Check.notNull(position, "Position cannot be null");
2020-07-22 17:39:48 +02:00
final Position pos = getPosition();
final int chunkX1 = ChunkUtils.getChunkCoordinate((int) Math.floor(pos.getX()));
final int chunkZ1 = ChunkUtils.getChunkCoordinate((int) Math.floor(pos.getZ()));
2020-02-13 15:14:41 +01:00
2020-07-22 17:39:48 +02:00
final int chunkX2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getX()));
final int chunkZ2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getZ()));
2020-02-13 15:14:41 +01:00
return chunkX1 == chunkX2 && chunkZ1 == chunkZ2;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the entity is in the same chunk as another.
2020-05-29 23:17:14 +02:00
*
* @param entity the entity to check
* @return true if both entities are in the same chunk, false otherwise
*/
2020-10-24 10:46:23 +02:00
public boolean sameChunk(@NotNull Entity entity) {
2020-02-13 15:14:41 +01:00
return sameChunk(entity.getPosition());
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Removes the entity from the server immediately.
2020-10-12 06:41:47 +02:00
* <p>
* WARNING: this do not trigger the {@link EntityDeathEvent} event.
2020-05-15 18:03:28 +02:00
*/
2019-08-10 08:44:35 +02:00
public void remove() {
this.removed = true;
2019-08-10 08:44:35 +02:00
this.shouldRemove = true;
2019-09-03 07:36:04 +02:00
entityById.remove(id);
2019-08-24 01:05:52 +02:00
if (instance != null)
instance.UNSAFE_removeEntity(this);
2019-08-10 08:44:35 +02:00
}
/**
2020-10-15 21:16:31 +02:00
* Gets if this entity has been removed.
*
* @return true if this entity is removed
*/
public boolean isRemoved() {
return removed;
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Triggers {@link #remove()} after the specified time.
2020-05-15 18:03:28 +02:00
*
2020-08-07 09:14:50 +02:00
* @param delay the time before removing the entity
* @param timeUnit the unit of the delay
2020-05-15 18:03:28 +02:00
*/
2020-10-24 10:46:23 +02:00
public void scheduleRemove(long delay, @NotNull TimeUnit timeUnit) {
2020-05-15 18:03:28 +02:00
delay = timeUnit.toMilliseconds(delay);
2019-08-21 16:50:52 +02:00
if (delay == 0) { // Cancel the scheduled remove
this.scheduledRemoveTime = 0;
return;
}
this.scheduledRemoveTime = System.currentTimeMillis() + delay;
2019-08-20 22:40:57 +02:00
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the entity removal has been scheduled with {@link #scheduleRemove(long, TimeUnit)}.
2020-05-29 23:17:14 +02:00
*
2020-10-12 06:41:47 +02:00
* @return true if the entity removal has been scheduled
2020-05-15 18:03:28 +02:00
*/
2019-08-30 01:17:46 +02:00
public boolean isRemoveScheduled() {
return scheduledRemoveTime != 0;
}
2020-10-24 10:46:23 +02:00
@NotNull
2019-08-24 20:34:01 +02:00
protected EntityVelocityPacket getVelocityPacket() {
2020-02-17 17:33:53 +01:00
final float strength = 8000f / MinecraftServer.TICK_PER_SECOND;
2019-08-24 20:34:01 +02:00
EntityVelocityPacket velocityPacket = new EntityVelocityPacket();
velocityPacket.entityId = getEntityId();
2019-08-25 20:03:43 +02:00
velocityPacket.velocityX = (short) (velocity.getX() * strength);
velocityPacket.velocityY = (short) (velocity.getY() * strength);
velocityPacket.velocityZ = (short) (velocity.getZ() * strength);
2019-08-24 20:34:01 +02:00
return velocityPacket;
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets an {@link EntityMetaDataPacket} sent when adding viewers. Used for synchronization.
2020-05-15 18:03:28 +02:00
*
* @return The {@link EntityMetaDataPacket} related to this entity
*/
2020-10-24 10:46:23 +02:00
@NotNull
2019-08-22 14:52:32 +02:00
public EntityMetaDataPacket getMetadataPacket() {
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
metaDataPacket.entityId = getEntityId();
2019-09-02 06:02:12 +02:00
metaDataPacket.consumer = getMetadataConsumer();
2019-08-22 14:52:32 +02:00
return metaDataPacket;
}
2020-05-15 18:03:28 +02:00
/**
* Should be override when wanting to add a new metadata index
*
* @return The consumer used to write {@link EntityMetaDataPacket} in {@link #getMetadataPacket()}
*/
2020-10-24 10:46:23 +02:00
@NotNull
public Consumer<BinaryWriter> getMetadataConsumer() {
2019-09-02 06:02:12 +02:00
return packet -> {
fillMetadataIndex(packet, 0);
fillMetadataIndex(packet, 1);
2020-05-26 00:07:35 +02:00
fillMetadataIndex(packet, 2);
fillMetadataIndex(packet, 3);
fillMetadataIndex(packet, 4);
2019-09-02 06:02:12 +02:00
fillMetadataIndex(packet, 5);
2019-09-10 06:59:15 +02:00
fillMetadataIndex(packet, 6);
2019-09-02 06:02:12 +02:00
};
2019-08-19 17:04:19 +02:00
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Sends a {@link EntityMetaDataPacket} containing only the specified index
* The index is wrote using {@link #fillMetadataIndex(BinaryWriter, int)}.
2020-05-15 18:03:28 +02:00
*
2020-08-06 07:42:00 +02:00
* @param index the metadata index
2020-05-15 18:03:28 +02:00
*/
2019-08-23 15:37:38 +02:00
protected void sendMetadataIndex(int index) {
2019-08-20 22:40:57 +02:00
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
metaDataPacket.entityId = getEntityId();
metaDataPacket.consumer = packet -> fillMetadataIndex(packet, index);
2019-08-27 05:23:25 +02:00
sendPacketToViewersAndSelf(metaDataPacket);
2019-08-20 22:40:57 +02:00
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Used to fill/write a specific metadata index.
* The proper use to add a new metadata index is to override this and add your case.
* Then you can also override {@link #getMetadataConsumer()} and fill your newly added index.
2020-05-15 18:03:28 +02:00
*
* @param packet the packet writer
* @param index the index to fill/write
*/
2020-10-24 10:46:23 +02:00
protected void fillMetadataIndex(@NotNull BinaryWriter packet, int index) {
2019-08-19 17:04:19 +02:00
switch (index) {
case 0:
2019-09-02 06:02:12 +02:00
fillStateMetadata(packet);
2019-08-19 17:04:19 +02:00
break;
case 1:
2019-09-02 06:02:12 +02:00
fillAirTickMetaData(packet);
2019-08-19 17:04:19 +02:00
break;
case 2:
2019-09-02 06:02:12 +02:00
fillCustomNameMetaData(packet);
2019-08-19 17:04:19 +02:00
break;
2020-05-26 00:07:35 +02:00
case 3:
fillCustomNameVisibleMetaData(packet);
break;
case 4:
fillSilentMetaData(packet);
break;
2019-08-23 15:37:38 +02:00
case 5:
2019-09-02 06:02:12 +02:00
fillNoGravityMetaData(packet);
2019-08-23 15:37:38 +02:00
break;
2019-09-10 06:59:15 +02:00
case 6:
fillPoseMetaData(packet);
break;
2019-08-19 17:04:19 +02:00
}
}
2020-10-24 10:46:23 +02:00
private void fillStateMetadata(@NotNull BinaryWriter packet) {
2020-04-16 16:40:29 +02:00
packet.writeByte((byte) 0);
packet.writeByte(METADATA_BYTE);
2019-08-19 17:04:19 +02:00
byte index0 = 0;
if (onFire)
index0 += 1;
if (crouched)
index0 += 2;
if (UNUSED_METADATA)
index0 += 4;
if (sprinting)
index0 += 8;
if (swimming)
index0 += 16;
if (invisible)
index0 += 32;
if (glowing)
index0 += 64;
if (usingElytra)
index0 += 128;
2020-04-16 16:40:29 +02:00
packet.writeByte(index0);
2019-09-02 06:02:12 +02:00
}
2020-10-24 10:46:23 +02:00
private void fillAirTickMetaData(@NotNull BinaryWriter packet) {
2020-04-16 16:40:29 +02:00
packet.writeByte((byte) 1);
packet.writeByte(METADATA_VARINT);
packet.writeVarInt(air);
2019-09-02 06:02:12 +02:00
}
2020-10-24 10:46:23 +02:00
private void fillCustomNameMetaData(@NotNull BinaryWriter packet) {
2020-06-22 23:25:00 +02:00
boolean hasCustomName = customName != null;
2020-05-26 00:07:35 +02:00
2020-04-16 16:40:29 +02:00
packet.writeByte((byte) 2);
2020-05-26 00:07:35 +02:00
packet.writeByte(METADATA_OPTCHAT);
packet.writeBoolean(hasCustomName);
if (hasCustomName) {
2020-06-22 23:25:00 +02:00
packet.writeSizedString(customName.toString());
2020-05-26 00:07:35 +02:00
}
}
2020-10-24 10:46:23 +02:00
private void fillCustomNameVisibleMetaData(@NotNull BinaryWriter packet) {
2020-05-26 00:07:35 +02:00
packet.writeByte((byte) 3);
packet.writeByte(METADATA_BOOLEAN);
packet.writeBoolean(customNameVisible);
}
2020-10-24 10:46:23 +02:00
private void fillSilentMetaData(@NotNull BinaryWriter packet) {
2020-05-26 00:07:35 +02:00
packet.writeByte((byte) 4);
packet.writeByte(METADATA_BOOLEAN);
packet.writeBoolean(silent);
2019-09-02 06:02:12 +02:00
}
2020-10-24 10:46:23 +02:00
private void fillNoGravityMetaData(@NotNull BinaryWriter packet) {
2020-04-16 16:40:29 +02:00
packet.writeByte((byte) 5);
packet.writeByte(METADATA_BOOLEAN);
packet.writeBoolean(noGravity);
2019-08-19 17:04:19 +02:00
}
2020-10-24 10:46:23 +02:00
private void fillPoseMetaData(@NotNull BinaryWriter packet) {
2020-04-16 16:40:29 +02:00
packet.writeByte((byte) 6);
packet.writeByte(METADATA_POSE);
packet.writeVarInt(pose.ordinal());
2019-09-10 06:59:15 +02:00
}
2019-08-30 01:17:46 +02:00
protected void sendSynchronization() {
2019-08-24 20:34:01 +02:00
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
entityTeleportPacket.entityId = getEntityId();
entityTeleportPacket.position = getPosition();
2019-08-27 20:49:11 +02:00
entityTeleportPacket.onGround = isOnGround();
2019-08-24 20:34:01 +02:00
sendPacketToViewers(entityTeleportPacket);
}
/**
2020-10-15 21:16:31 +02:00
* Asks for a synchronization (position) to happen during next entity tick.
*/
public void askSynchronization() {
this.lastSynchronizationTime = 0;
2019-08-24 20:34:01 +02:00
}
2020-02-09 15:34:09 +01:00
private boolean shouldUpdate(long time) {
2020-02-17 17:33:53 +01:00
return (float) (time - lastUpdate) >= MinecraftServer.TICK_MS * 0.9f; // Margin of error
2019-08-19 17:04:19 +02:00
}
2020-05-29 23:17:14 +02:00
private enum Pose {
2019-08-19 17:04:19 +02:00
STANDING,
FALL_FLYING,
SLEEPING,
SWIMMING,
SPIN_ATTACK,
SNEAKING,
2020-04-09 14:25:42 +02:00
DYING
2019-08-19 17:04:19 +02:00
}
2019-08-10 08:44:35 +02:00
protected boolean shouldRemove() {
return shouldRemove;
}
}