package net.minestom.server.entity; import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEvent.ShowEntity; import net.kyori.adventure.text.event.HoverEventSource; import net.minestom.server.MinecraftServer; import net.minestom.server.Tickable; import net.minestom.server.Viewable; import net.minestom.server.acquirable.Acquirable; import net.minestom.server.chat.JsonMessage; 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.entity.metadata.EntityMeta; import net.minestom.server.event.EventCallback; import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.EventFilter; import net.minestom.server.event.EventNode; import net.minestom.server.event.entity.*; import net.minestom.server.event.handler.EventHandler; import net.minestom.server.event.trait.EntityEvent; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceManager; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.permission.Permission; import net.minestom.server.permission.PermissionHandler; import net.minestom.server.potion.Potion; import net.minestom.server.potion.PotionEffect; import net.minestom.server.potion.TimedPotion; import net.minestom.server.thread.ThreadProvider; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.Position; import net.minestom.server.utils.Vector; import net.minestom.server.utils.callback.OptionalCallback; import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.entity.EntityUtils; import net.minestom.server.utils.player.PlayerUtils; import net.minestom.server.utils.time.Cooldown; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.time.UpdateOption; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.UnaryOperator; /** * Could be a player, a monster, or an object. *
* To create your own entity you probably want to extends {@link LivingEntity} or {@link EntityCreature} instead.
*/
public class Entity implements Viewable, Tickable, EventHandler
* Entity id are unique server-wide.
*
* @param id the entity unique id
* @return the entity having the specified id, null if not found
*/
@Nullable
public static Entity getEntity(int id) {
return Entity.ENTITY_BY_ID.getOrDefault(id, null);
}
/**
* Gets an entity based on its UUID (from {@link #getUuid()}).
*
* @param uuid the entity UUID
* @return the entity having the specified uuid, null if not found
*/
@Nullable
public static Entity getEntity(@NotNull UUID uuid) {
return Entity.ENTITY_BY_UUID.getOrDefault(uuid, null);
}
/**
* Generate and return a new unique entity id.
*
* Useful if you want to spawn entities using packet but don't risk to have duplicated id.
*
* @return a newly generated entity id
*/
public static int generateId() {
return LAST_ENTITY_ID.incrementAndGet();
}
/**
* Called each tick.
*
* @param time time of the update in milliseconds
*/
public void update(long time) {
}
/**
* Called when a new instance is set.
*/
public void spawn() {
}
public boolean isOnGround() {
return onGround || EntityUtils.isOnGround(this) /* backup for levitating entities */;
}
/**
* Gets metadata of this entity.
* You may want to cast it to specific implementation.
*
* @return metadata of this entity.
*/
@NotNull
public EntityMeta getEntityMeta() {
return this.entityMeta;
}
/**
* Teleports the entity only if the chunk at {@code position} is loaded or if
* {@link Instance#hasEnabledAutoChunkLoad()} returns true.
*
* @param position the teleport position
* @param chunks the chunk indexes to load before teleporting the entity,
* indexes are from {@link ChunkUtils#getChunkIndex(int, int)},
* can be null or empty to only load the chunk at {@code position}
* @param callback the optional callback executed, even if auto chunk is not enabled
* @throws IllegalStateException if you try to teleport an entity before settings its instance
*/
public void teleport(@NotNull Position position, @Nullable long[] chunks, @Nullable Runnable callback) {
Check.stateCondition(instance == null, "You need to use Entity#setInstance before teleporting an entity!");
final Position teleportPosition = position.clone(); // Prevent synchronization issue
final ChunkCallback endCallback = (chunk) -> {
refreshPosition(teleportPosition);
synchronizePosition(true);
OptionalCallback.execute(callback);
};
if (chunks == null || chunks.length == 0) {
instance.loadOptionalChunk(teleportPosition, endCallback);
} else {
ChunkUtils.optionalLoadAll(instance, chunks, null, endCallback);
}
}
public void teleport(@NotNull Position position, @Nullable Runnable callback) {
teleport(position, null, callback);
}
public void teleport(@NotNull Position position) {
teleport(position, null);
}
/**
* 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);
}
/**
* Changes the view of the entity.
* Only the yaw and pitch are used.
*
* @param position the new view
*/
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 have complete control over which player can see it, without having to deal with
* raw packets.
*
* 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
* list, you would have to do that manually using {@link #addViewer(Player)} and {@link #removeViewer(Player)}..
*
* @return true if the entity is automatically viewable for close players, false otherwise
*/
public boolean isAutoViewable() {
return autoViewable;
}
/**
* Makes the entity auto viewable or only manually.
*
* @param autoViewable should the entity be automatically viewable for close players
* @see #isAutoViewable()
*/
public void setAutoViewable(boolean autoViewable) {
this.autoViewable = autoViewable;
}
@Override
public final boolean addViewer(@NotNull Player player) {
synchronized (this.entityTypeLock) {
return addViewer0(player);
}
}
protected boolean addViewer0(@NotNull Player player) {
if (!this.viewers.add(player)) {
return false;
}
player.viewableEntities.add(this);
PlayerConnection playerConnection = player.getPlayerConnection();
playerConnection.sendPacket(getEntityType().getSpawnType().getSpawnPacket(this));
if (hasVelocity()) {
playerConnection.sendPacket(getVelocityPacket());
}
playerConnection.sendPacket(getMetadataPacket());
// Passenger
if (hasPassenger()) {
playerConnection.sendPacket(getPassengersPacket());
}
// Head position
{
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
entityHeadLookPacket.entityId = getEntityId();
entityHeadLookPacket.yaw = position.getYaw();
playerConnection.sendPacket(entityHeadLookPacket);
}
return true;
}
@Override
public final boolean removeViewer(@NotNull Player player) {
synchronized (this.entityTypeLock) {
return removeViewer0(player);
}
}
protected boolean removeViewer0(@NotNull Player player) {
if (!viewers.remove(player)) {
return false;
}
player.getPlayerConnection().sendPacket(DestroyEntityPacket.of(getEntityId()));
player.viewableEntities.remove(this);
return true;
}
@NotNull
@Override
public Set
* Works by changing the internal entity type field and by calling {@link #removeViewer(Player)}
* followed by {@link #addViewer(Player)} to all current viewers.
*
* Be aware that this only change the visual of the entity, the {@link net.minestom.server.collision.BoundingBox}
* will not be modified.
*
* @param entityType the new entity type
*/
public void switchEntityType(@NotNull EntityType entityType) {
synchronized (entityTypeLock) {
this.entityType = entityType;
this.metadata = new Metadata(this);
this.entityMeta = entityType.getMetaConstructor().apply(this, this.metadata);
Set
* Ignored if {@link #getInstance()} returns null.
*
* @param time the update time in milliseconds
*/
@Override
public void tick(long time) {
if (instance == null)
return;
// Scheduled remove
if (scheduledRemoveTime != 0) {
final boolean finished = time >= scheduledRemoveTime;
if (finished) {
remove();
return;
}
}
// Instant remove
if (shouldRemove()) {
remove();
return;
}
// Fix current chunk being null if the entity has been spawned before
if (currentChunk == null) {
refreshCurrentChunk(instance.getChunkAt(position));
}
// Check if the entity chunk is loaded
if (!ChunkUtils.isLoaded(currentChunk)) {
// No update for entities in unloaded chunk
return;
}
// scheduled tasks
if (!nextTick.isEmpty()) {
Consumer
* The following packets are sent to viewers (check are performed in this order):
*
* WARNING: this does not change the entity hit-box which is client-side.
*
* @param x the bounding box X size
* @param y the bounding box Y size
* @param z the bounding box Z size
*/
public void setBoundingBox(double x, double y, double z) {
this.boundingBox = new BoundingBox(this, x, y, z);
}
/**
* Changes the internal entity bounding box.
*
* WARNING: this does not change the entity hit-box which is client-side.
*
* @param boundingBox the new bounding box
*/
public void setBoundingBox(BoundingBox boundingBox) {
this.boundingBox = boundingBox;
}
/**
* Convenient method to get the entity current chunk.
*
* @return the entity chunk, can be null even if unlikely
*/
public @Nullable Chunk getChunk() {
return currentChunk;
}
@ApiStatus.Internal
protected void refreshCurrentChunk(Chunk currentChunk) {
this.currentChunk = currentChunk;
MinecraftServer.getUpdateManager().getThreadProvider().updateEntity(this);
}
/**
* Gets the entity current instance.
*
* @return the entity instance, can be null if the entity doesn't have an instance yet
*/
public @Nullable Instance getInstance() {
return instance;
}
/**
* Changes the entity instance, i.e. spawns it.
*
* @param instance the new instance of the entity
* @param spawnPosition the spawn position for the entity.
* @throws IllegalStateException if {@code instance} has not been registered in {@link InstanceManager}
*/
public void setInstance(@NotNull Instance instance, @NotNull Position spawnPosition) {
Check.stateCondition(!instance.isRegistered(),
"Instances need to be registered, please use InstanceManager#registerInstance or InstanceManager#registerSharedInstance");
if (this.instance != null) {
this.instance.UNSAFE_removeEntity(this);
}
this.position.set(spawnPosition);
this.lastPosition.set(position);
this.isActive = true;
this.instance = instance;
refreshCurrentChunk(instance.getChunkAt(position.getX(), position.getZ()));
instance.UNSAFE_addEntity(this);
spawn();
EventDispatcher.call(new EntitySpawnEvent(this, instance));
}
/**
* Changes the entity instance.
*
* @param instance the new instance of the entity
* @throws NullPointerException if {@code instance} is null
* @throws IllegalStateException if {@code instance} has not been registered in {@link InstanceManager}
*/
public void setInstance(@NotNull Instance instance) {
setInstance(instance, this.position);
}
/**
* Gets the entity current velocity.
*
* @return the entity current velocity
*/
@NotNull
public Vector getVelocity() {
return velocity;
}
/**
* Changes the entity velocity and calls {@link EntityVelocityEvent}.
*
* The final velocity can be cancelled or modified by the event.
*
* @param velocity the new entity velocity
*/
public void setVelocity(@NotNull Vector velocity) {
EntityVelocityEvent entityVelocityEvent = new EntityVelocityEvent(this, velocity);
EventDispatcher.callCancellable(entityVelocityEvent, () -> {
this.velocity.copy(entityVelocityEvent.getVelocity());
sendPacketToViewersAndSelf(getVelocityPacket());
});
}
/**
* Gets if the entity currently has a velocity applied.
*
* @return true if velocity is not set to 0
*/
public boolean hasVelocity() {
return !velocity.isZero();
}
/**
* Gets the gravity drag per tick.
*
* @return the gravity drag per tick in block
*/
public double getGravityDragPerTick() {
return gravityDragPerTick;
}
/**
* Gets the gravity acceleration.
*
* @return the gravity acceleration in block
*/
public double getGravityAcceleration() {
return gravityAcceleration;
}
/**
* Gets the maximum gravity velocity.
*
* @return the maximum gravity velocity in block
*/
public double getGravityTerminalVelocity() {
return gravityTerminalVelocity;
}
/**
* Gets the number of tick this entity has been applied gravity.
*
* @return the number of tick of which gravity has been consequently applied
*/
public int getGravityTickCount() {
return gravityTickCount;
}
/**
* Changes the gravity of the entity.
*
* @param gravityDragPerTick the gravity drag per tick in block
* @param gravityAcceleration the gravity acceleration in block
* @param gravityTerminalVelocity the gravity terminal velocity (maximum) in block
* @see Entities motion
*/
public void setGravity(double gravityDragPerTick, double gravityAcceleration, double gravityTerminalVelocity) {
this.gravityDragPerTick = gravityDragPerTick;
this.gravityAcceleration = gravityAcceleration;
this.gravityTerminalVelocity = gravityTerminalVelocity;
}
/**
* Gets the distance between two entities.
*
* @param entity the entity to get the distance from
* @return the distance between this and {@code entity}
*/
public double getDistance(@NotNull Entity entity) {
return getPosition().getDistance(entity.getPosition());
}
/**
* Gets the distance squared between two entities.
*
* @param entity the entity to get the distance from
* @return the distance squared between this and {@code entity}
*/
public double getDistanceSquared(@NotNull Entity entity) {
return getPosition().getDistanceSquared(entity.getPosition());
}
/**
* Gets the entity vehicle or null.
*
* @return the entity vehicle, or null if there is not any
*/
@Nullable
public Entity getVehicle() {
return vehicle;
}
/**
* Adds a new passenger to this entity.
*
* @param entity the new passenger
* @throws NullPointerException if {@code entity} is null
* @throws IllegalStateException if {@link #getInstance()} returns null
*/
public void addPassenger(@NotNull Entity entity) {
Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance");
if (entity.getVehicle() != null) {
entity.getVehicle().removePassenger(entity);
}
this.passengers.add(entity);
entity.vehicle = this;
sendPacketToViewersAndSelf(getPassengersPacket());
}
/**
* Removes a passenger to this entity.
*
* @param entity the passenger to remove
* @throws NullPointerException if {@code entity} is null
* @throws IllegalStateException if {@link #getInstance()} returns null
*/
public void removePassenger(@NotNull Entity entity) {
Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance");
if (!passengers.remove(entity))
return;
entity.vehicle = null;
sendPacketToViewersAndSelf(getPassengersPacket());
}
/**
* Gets if the entity has any passenger.
*
* @return true if the entity has any passenger, false otherwise
*/
public boolean hasPassenger() {
return !passengers.isEmpty();
}
/**
* Gets the entity passengers.
*
* @return an unmodifiable list containing all the entity passengers
*/
@NotNull
public Set
* WARNING: if you want to apply damage or specify a duration,
* see {@link LivingEntity#setFireForDuration(int, TimeUnit)}.
*
* @param fire should the entity be set in fire
*/
public void setOnFire(boolean fire) {
this.entityMeta.setOnFire(fire);
}
/**
* Gets if the entity is sneaking.
*
* WARNING: this can be bypassed by hacked client, this is only what the client told the server.
*
* @return true if the player is sneaking
*/
public boolean isSneaking() {
return this.entityMeta.isSneaking();
}
/**
* Makes the entity sneak.
*
* WARNING: this will not work for the client itself.
*
* @param sneaking true to make the entity sneak
*/
public void setSneaking(boolean sneaking) {
setPose(sneaking ? Pose.SNEAKING : Pose.STANDING);
this.entityMeta.setSneaking(sneaking);
}
/**
* Gets if the player is sprinting.
*
* WARNING: this can be bypassed by hacked client, this is only what the client told the server.
*
* @return true if the player is sprinting
*/
public boolean isSprinting() {
return this.entityMeta.isSprinting();
}
/**
* Makes the entity sprint.
*
* WARNING: this will not work on the client itself.
*
* @param sprinting true to make the entity sprint
*/
public void setSprinting(boolean sprinting) {
this.entityMeta.setSprinting(sprinting);
}
/**
* Gets if the entity is invisible or not.
*
* @return true if the entity is invisible, false otherwise
*/
public boolean isInvisible() {
return this.entityMeta.isInvisible();
}
/**
* Changes the internal invisible value and send a {@link EntityMetaDataPacket}
* to make visible or invisible the entity to its viewers.
*
* @param invisible true to set the entity invisible, false otherwise
*/
public void setInvisible(boolean invisible) {
this.entityMeta.setInvisible(invisible);
}
/**
* Gets if the entity is glowing or not.
*
* @return true if the entity is glowing, false otherwise
*/
public boolean isGlowing() {
return this.entityMeta.isHasGlowingEffect();
}
/**
* Sets or remove the entity glowing effect.
*
* @param glowing true to make the entity glows, false otherwise
*/
public void setGlowing(boolean glowing) {
this.entityMeta.setHasGlowingEffect(glowing);
}
/**
* Gets the current entity pose.
*
* @return the entity pose
*/
@NotNull
public Pose getPose() {
return this.entityMeta.getPose();
}
/**
* Changes the entity pose.
*
* The internal {@code crouched} and {@code swimming} field will be
* updated accordingly.
*
* @param pose the new entity pose
*/
@NotNull
public void setPose(@NotNull Pose pose) {
this.entityMeta.setPose(pose);
}
/**
* Gets the entity custom name.
*
* @return the custom name of the entity, null if there is not
* @deprecated Use {@link #getCustomName()}
*/
@Deprecated
@Nullable
public JsonMessage getCustomNameJson() {
return this.entityMeta.getCustomNameJson();
}
/**
* Gets the entity custom name.
*
* @return the custom name of the entity, null if there is not
*/
@Nullable
public Component getCustomName() {
return this.entityMeta.getCustomName();
}
/**
* Changes the entity custom name.
*
* @param customName the custom name of the entity, null to remove it
* @deprecated Use {@link #setCustomName(Component)}
*/
@Deprecated
public void setCustomName(@Nullable JsonMessage customName) {
this.entityMeta.setCustomName(customName);
}
/**
* Changes the entity custom name.
*
* @param customName the custom name of the entity, null to remove it
*/
public void setCustomName(@Nullable Component customName) {
this.entityMeta.setCustomName(customName);
}
/**
* Gets the custom name visible metadata field.
*
* @return true if the custom name is visible, false otherwise
*/
public boolean isCustomNameVisible() {
return this.entityMeta.isCustomNameVisible();
}
/**
* Changes the internal custom name visible field and send a {@link EntityMetaDataPacket}
* to update the entity state to its viewers.
*
* @param customNameVisible true to make the custom name visible, false otherwise
*/
public void setCustomNameVisible(boolean customNameVisible) {
this.entityMeta.setCustomNameVisible(customNameVisible);
}
public boolean isSilent() {
return this.entityMeta.isSilent();
}
public void setSilent(boolean silent) {
this.entityMeta.setSilent(silent);
}
/**
* Gets the noGravity metadata field.
*
* @return true if the entity ignore gravity, false otherwise
*/
public boolean hasNoGravity() {
return this.entityMeta.isHasNoGravity();
}
/**
* Changes the noGravity metadata field and change the gravity behaviour accordingly.
*
* @param noGravity should the entity ignore gravity
*/
public void setNoGravity(boolean noGravity) {
this.entityMeta.setHasNoGravity(noGravity);
}
/**
* 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
*
* WARNING: unsafe, should only be used internally in Minestom. Use {@link #teleport(Position)} instead.
*
* @param x new position X
* @param y new position Y
* @param z new position Z
*/
private void refreshPosition(double x, double y, double z) {
position.setX(x);
position.setY(y);
position.setZ(z);
if (hasPassenger()) {
for (Entity passenger : getPassengers()) {
passenger.refreshPosition(x, y, z);
}
}
final Instance instance = getInstance();
if (instance != null) {
final int lastChunkX = currentChunk.getChunkX();
final int lastChunkZ = currentChunk.getChunkZ();
final int newChunkX = ChunkUtils.getChunkCoordinate(x);
final int newChunkZ = ChunkUtils.getChunkCoordinate(z);
if (lastChunkX != newChunkX || lastChunkZ != newChunkZ) {
// Entity moved in a new chunk
final Chunk newChunk = instance.getChunk(newChunkX, newChunkZ);
Check.notNull(newChunk, "The entity {0} tried to move in an unloaded chunk at {1};{2}", getEntityId(), x, z);
instance.UNSAFE_switchEntityChunk(this, currentChunk, newChunk);
if (this instanceof Player) {
// Refresh player view
final Player player = (Player) this;
player.refreshVisibleChunks(newChunk);
player.refreshVisibleEntities(newChunk);
}
refreshCurrentChunk(newChunk);
}
}
this.lastPosition.setX(position.getX());
this.lastPosition.setY(position.getY());
this.lastPosition.setZ(position.getZ());
}
/**
* Updates internal fields and sends updates
*
* @param position the new position
* @see #refreshPosition(double, double, double)
* @see #refreshView(float, float)
* @see #sendPositionUpdate(boolean)
*/
@ApiStatus.Internal
public void refreshPosition(@NotNull final Position position) {
if (!position.isSimilar(this.position))
refreshPosition(position.getX(), position.getY(), position.getZ());
refreshView(position.getYaw(), position.getPitch());
sendPositionUpdate(true);
}
/**
* Updates the entity view internally.
*
* Warning: you probably want to use {@link #setView(float, float)}.
*
* @param yaw the yaw
* @param pitch the pitch
*/
private void refreshView(final float yaw, final float pitch) {
lastPosition.setYaw(position.getYaw());
lastPosition.setPitch(position.getPitch());
position.setYaw(yaw);
position.setPitch(pitch);
}
/**
* Gets the entity position.
*
* @return the current position of the entity
*/
@NotNull
public Position getPosition() {
return position;
}
/**
* Gets the entity eye height.
*
* Default to {@link BoundingBox#getHeight()}x0.85
*
* @return the entity eye height
*/
public double getEyeHeight() {
return boundingBox.getHeight() * 0.85;
}
/**
* Gets all the potion effect of this entity.
*
* @return an unmodifiable list of all this entity effects
*/
@NotNull
public List
* WARNING: this does not trigger {@link EntityDeathEvent}.
*/
public void remove() {
if (isRemoved())
return;
MinecraftServer.getUpdateManager().getThreadProvider().removeEntity(this);
this.removed = true;
this.shouldRemove = true;
Entity.ENTITY_BY_ID.remove(id);
Entity.ENTITY_BY_UUID.remove(uuid);
if (instance != null)
instance.UNSAFE_removeEntity(this);
}
/**
* Gets if this entity has been removed.
*
* @return true if this entity is removed
*/
public boolean isRemoved() {
return removed;
}
/**
* Triggers {@link #remove()} after the specified time.
*
* @param delay the time before removing the entity,
* 0 to cancel the removing
* @param timeUnit the unit of the delay
*/
public void scheduleRemove(long delay, @NotNull TimeUnit timeUnit) {
if (delay == 0) { // Cancel the scheduled remove
this.scheduledRemoveTime = 0;
return;
}
this.scheduledRemoveTime = System.currentTimeMillis() + timeUnit.toMilliseconds(delay);
}
/**
* Gets if the entity removal has been scheduled with {@link #scheduleRemove(long, TimeUnit)}.
*
* @return true if the entity removal has been scheduled
*/
public boolean isRemoveScheduled() {
return scheduledRemoveTime != 0;
}
@NotNull
protected Vector getVelocityForPacket() {
return this.velocity.clone().multiply(8000f / MinecraftServer.TICK_PER_SECOND);
}
@NotNull
protected EntityVelocityPacket getVelocityPacket() {
EntityVelocityPacket velocityPacket = new EntityVelocityPacket();
velocityPacket.entityId = getEntityId();
Vector velocity = getVelocityForPacket();
velocityPacket.velocityX = (short) velocity.getX();
velocityPacket.velocityY = (short) velocity.getY();
velocityPacket.velocityZ = (short) velocity.getZ();
return velocityPacket;
}
/**
* Gets an {@link EntityMetaDataPacket} sent when adding viewers. Used for synchronization.
*
* @return The {@link EntityMetaDataPacket} related to this entity
*/
@NotNull
public EntityMetaDataPacket getMetadataPacket() {
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
metaDataPacket.entityId = getEntityId();
metaDataPacket.entries = metadata.getEntries();
return metaDataPacket;
}
/**
* Used to synchronize entity position with viewers by sending an
* {@link EntityTeleportPacket} to viewers, in case of a player this is
* overridden in order to send an additional {@link PlayerPositionAndLookPacket}
* to itself.
*
* @param includeSelf if {@code true} and this is a {@link Player} an additional {@link PlayerPositionAndLookPacket}
* will be sent to the player itself
*/
@ApiStatus.Internal
protected void synchronizePosition(boolean includeSelf) {
final Position pos = position.clone();
final EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
entityTeleportPacket.entityId = getEntityId();
entityTeleportPacket.position = pos;
entityTeleportPacket.onGround = isOnGround();
sendPacketToViewers(entityTeleportPacket);
this.lastAbsoluteSynchronizationTime = System.currentTimeMillis();
this.lastSyncedPosition.set(pos);
}
/**
* Asks for a synchronization (position) to happen during next entity tick.
*/
public void askSynchronization() {
this.lastAbsoluteSynchronizationTime = 0;
}
/**
* Set custom cooldown for position synchronization.
*
* @param cooldown custom cooldown for position synchronization.
*/
public void setCustomSynchronizationCooldown(@Nullable UpdateOption cooldown) {
this.customSynchronizationCooldown = cooldown;
}
@Override
public @NotNull HoverEvent
*
* In case of a player's position and/or view change an additional {@link PlayerPositionAndLookPacket}
* is sent to self.
*
* @param clientSide {@code true} if the client triggered this action
*/
protected void sendPositionUpdate(final boolean clientSide) {
final boolean viewChange = !position.hasSimilarView(lastSyncedPosition);
final double distanceX = Math.abs(position.getX() - lastSyncedPosition.getX());
final double distanceY = Math.abs(position.getY() - lastSyncedPosition.getY());
final double distanceZ = Math.abs(position.getZ() - lastSyncedPosition.getZ());
final boolean positionChange = (distanceX + distanceY + distanceZ) > 0;
if (distanceX > 8 || distanceY > 8 || distanceZ > 8) {
synchronizePosition(true);
// #synchronizePosition sets sync fields, it's safe to return
return;
} else if (positionChange && viewChange) {
EntityPositionAndRotationPacket positionAndRotationPacket = EntityPositionAndRotationPacket
.getPacket(getEntityId(), position, lastSyncedPosition, isOnGround());
sendPacketToViewers(positionAndRotationPacket);
// Fix head rotation
final EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
entityHeadLookPacket.entityId = getEntityId();
entityHeadLookPacket.yaw = position.getYaw();
sendPacketToViewersAndSelf(entityHeadLookPacket);
} else if (positionChange) {
final EntityPositionPacket entityPositionPacket = EntityPositionPacket
.getPacket(getEntityId(), position, lastSyncedPosition, onGround);
sendPacketToViewers(entityPositionPacket);
} else if (viewChange) {
final EntityRotationPacket entityRotationPacket = new EntityRotationPacket();
entityRotationPacket.entityId = getEntityId();
entityRotationPacket.yaw = position.getYaw();
entityRotationPacket.pitch = position.getPitch();
entityRotationPacket.onGround = onGround;
final EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
entityHeadLookPacket.entityId = getEntityId();
entityHeadLookPacket.yaw = position.getYaw();
if (clientSide) {
sendPacketToViewers(entityHeadLookPacket);
sendPacketToViewers(entityRotationPacket);
} else {
sendPacketToViewersAndSelf(entityHeadLookPacket);
sendPacketToViewersAndSelf(entityRotationPacket);
}
} else {
// Nothing changed, return
return;
}
if (PlayerUtils.isNettyClient(this) && !clientSide) {
final PlayerPositionAndLookPacket playerPositionAndLookPacket = new PlayerPositionAndLookPacket();
playerPositionAndLookPacket.flags = 0b111;
playerPositionAndLookPacket.position = position.clone().subtract(lastSyncedPosition.getX(), lastSyncedPosition.getY(), lastSyncedPosition.getZ());
playerPositionAndLookPacket.teleportId = ((Player) this).getNextTeleportId();
((Player) this).getPlayerConnection().sendPacket(playerPositionAndLookPacket);
}
lastSyncedPosition.set(position);
}
/**
* Gets the number of ticks this entity has been active for.
*
* @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
if (getInstance().isInVoid(this.position)) {
remove();
}
}
@Override
public @NotNull EventNode