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

1577 lines
53 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
2021-06-11 16:51:45 +02:00
import net.kyori.adventure.sound.Sound;
2021-03-03 20:27:33 +01:00
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;
2020-04-24 03:25:58 +02:00
import net.minestom.server.MinecraftServer;
2021-03-11 20:54:30 +01:00
import net.minestom.server.Tickable;
2020-04-24 03:25:58 +02:00
import net.minestom.server.Viewable;
import net.minestom.server.acquirable.Acquirable;
2020-04-24 03:25:58 +02:00
import net.minestom.server.collision.BoundingBox;
import net.minestom.server.collision.CollisionUtils;
2021-07-08 18:26:26 +02:00
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
2021-02-23 16:37:00 +01:00
import net.minestom.server.entity.metadata.EntityMeta;
2021-06-04 03:48:51 +02:00
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.GlobalHandles;
2021-01-03 00:23:41 +01:00
import net.minestom.server.event.entity.*;
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;
2021-05-29 00:55:24 +02:00
import net.minestom.server.instance.block.Block;
2021-07-11 20:44:37 +02:00
import net.minestom.server.instance.block.BlockGetter;
2021-05-29 00:55:24 +02:00
import net.minestom.server.instance.block.BlockHandler;
2020-04-24 03:25:58 +02:00
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;
2020-12-31 01:29:07 +01:00
import net.minestom.server.potion.Potion;
import net.minestom.server.potion.PotionEffect;
import net.minestom.server.potion.TimedPotion;
import net.minestom.server.tag.Tag;
import net.minestom.server.tag.TagHandler;
import net.minestom.server.thread.ThreadProvider;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.block.BlockIterator;
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;
2021-03-31 19:17:37 +02:00
import net.minestom.server.utils.time.Cooldown;
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;
import org.jetbrains.annotations.ApiStatus;
2020-10-24 10:46:23 +02:00
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
2019-08-10 08:44:35 +02:00
2021-06-30 00:59:26 +02:00
import java.time.Duration;
import java.time.temporal.TemporalUnit;
2020-04-28 01:20:11 +02:00
import java.util.*;
2021-07-11 02:54:02 +02:00
import java.util.concurrent.*;
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;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
2019-08-10 08:44:35 +02:00
2020-11-01 22:53:36 +01:00
/**
* Could be a player, a monster, or an object.
* <p>
2021-03-26 11:25:03 +01:00
* To create your own entity you probably want to extends {@link LivingEntity} or {@link EntityCreature} instead.
2020-11-01 22:53:36 +01:00
*/
2021-07-15 05:23:33 +02:00
public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler, HoverEventSource<ShowEntity>, Sound.Emitter {
2019-08-10 08:44:35 +02:00
2021-03-26 21:26:35 +01:00
private static final Map<Integer, Entity> ENTITY_BY_ID = new ConcurrentHashMap<>();
private static final Map<UUID, Entity> ENTITY_BY_UUID = new ConcurrentHashMap<>();
private static final AtomicInteger LAST_ENTITY_ID = new AtomicInteger();
2019-08-12 08:30:59 +02:00
2019-08-11 07:42:56 +02:00
protected Instance instance;
2021-04-10 21:42:45 +02:00
protected Chunk currentChunk;
2021-07-06 20:44:24 +02:00
protected Pos position;
protected Pos previousPosition;
2021-07-06 20:44:24 +02:00
protected Pos lastSyncedPosition;
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
2021-07-06 20:44:24 +02:00
protected Vec velocity = Vec.ZERO; // Movement in block per second
2021-02-22 12:42:46 +01:00
protected boolean hasPhysics = true;
/**
* The amount of drag applied on the Y axle.
* <p>
* Unit: 1/tick
*/
2021-01-25 19:33:53 +01:00
protected double gravityDragPerTick;
/**
* Acceleration on the Y axle due to gravity
* <p>
* Unit: blocks/tick
*/
2021-01-25 19:33:53 +01:00
protected double gravityAcceleration;
protected int gravityTickCount; // Number of tick where gravity tick was applied
2019-08-27 20:49:11 +02:00
private boolean autoViewable;
private final int id;
protected final Set<Player> viewers = ConcurrentHashMap.newKeySet();
private final Set<Player> unmodifiableViewers = Collections.unmodifiableSet(viewers);
private final NBTCompound nbtCompound = new NBTCompound();
2020-12-10 02:56:56 +01:00
private final Set<Permission> permissions = 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<>();
2020-12-05 01:36:06 +01:00
protected EntityType entityType; // UNSAFE to change, modify at your own risk
// Network synchronization, send the absolute position of the entity each X milliseconds
private static final Duration SYNCHRONIZATION_COOLDOWN = Duration.of(1, TimeUnit.MINUTE);
2021-06-30 00:59:26 +02:00
private Duration customSynchronizationCooldown;
private long lastAbsoluteSynchronizationTime;
2019-08-24 20:34:01 +02:00
2021-01-30 04:44:44 +01:00
protected Metadata metadata = new Metadata(this);
2021-02-23 16:37:00 +01:00
protected EntityMeta entityMeta;
2020-12-31 12:05:36 +01:00
private final List<TimedPotion> effects = new CopyOnWriteArrayList<>();
2020-12-31 01:29:07 +01:00
// list of scheduled tasks to be executed during the next entity tick
protected final Queue<Consumer<Entity>> nextTick = new ConcurrentLinkedQueue<>();
// Tick related
private long ticks;
2021-04-26 12:52:02 +02:00
private final Acquirable<Entity> acquirable = Acquirable.of(this);
2021-04-15 01:44:08 +02:00
/**
* Lock used to support #switchEntityType
*/
private final Object entityTypeLock = new Object();
public Entity(@NotNull EntityType entityType, @NotNull UUID uuid) {
this.id = generateId();
this.entityType = entityType;
this.uuid = uuid;
2021-07-06 20:44:24 +02:00
this.position = Pos.ZERO;
this.previousPosition = Pos.ZERO;
2021-07-06 20:44:24 +02:00
this.lastSyncedPosition = Pos.ZERO;
2021-07-27 11:56:20 +02:00
setBoundingBox(entityType.width(), entityType.height(), entityType.width());
this.entityMeta = EntityTypeImpl.createMeta(entityType, this, this.metadata);
setAutoViewable(true);
2021-03-26 21:26:35 +01:00
Entity.ENTITY_BY_ID.put(id, this);
Entity.ENTITY_BY_UUID.put(uuid, this);
2021-06-04 04:05:57 +02:00
this.gravityAcceleration = EntityTypeImpl.getAcceleration(entityType.name());
this.gravityDragPerTick = EntityTypeImpl.getDrag(entityType.name());
}
public Entity(@NotNull EntityType entityType) {
this(entityType, UUID.randomUUID());
}
/**
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-05-15 18:03:28 +02:00
/**
* Gets an entity based on its id (from {@link #getEntityId()}).
* <p>
* Entity id are unique server-wide.
*
* @param id the entity unique id
2020-05-15 18:03:28 +02:00
* @return the entity having the specified id, null if not found
*/
2021-07-27 11:56:20 +02:00
public static @Nullable Entity getEntity(int id) {
2021-03-26 21:26:35 +01:00
return Entity.ENTITY_BY_ID.getOrDefault(id, null);
2019-08-21 16:50:52 +02:00
}
2019-08-19 17:04:19 +02:00
2020-12-29 19:58:40 +01:00
/**
* 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
*/
2021-07-27 11:56:20 +02:00
public static @Nullable Entity getEntity(@NotNull UUID uuid) {
2021-03-26 21:26:35 +01:00
return Entity.ENTITY_BY_UUID.getOrDefault(uuid, null);
2020-12-29 19:58:40 +01:00
}
/**
* Generate and return a new unique entity id.
* <p>
* 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() {
2021-03-26 21:26:35 +01:00
return LAST_ENTITY_ID.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.
*
2020-11-15 05:12:28 +01:00
* @param time time of the update in milliseconds
2020-05-15 18:03:28 +02:00
*/
2021-02-25 11:00:02 +01:00
public 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
*/
2021-02-25 11:00:02 +01:00
public void spawn() {
}
2019-08-24 20:34:01 +02:00
2020-03-29 20:58:30 +02:00
public boolean isOnGround() {
return onGround || EntityUtils.isOnGround(this) /* backup for levitating entities */;
}
2021-02-23 16:37:00 +01:00
/**
* Gets metadata of this entity.
* You may want to cast it to specific implementation.
*
* @return metadata of this entity.
*/
2021-07-27 11:56:20 +02:00
public @NotNull EntityMeta getEntityMeta() {
2021-02-23 16:37:00 +01:00
return this.entityMeta;
}
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
* @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}
* @throws IllegalStateException if you try to teleport an entity before settings its instance
2020-08-06 07:42:00 +02:00
*/
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position, long @Nullable [] chunks) {
2020-05-23 04:20:01 +02:00
Check.stateCondition(instance == null, "You need to use Entity#setInstance before teleporting an entity!");
2021-07-11 02:54:02 +02:00
final Runnable endCallback = () -> {
2021-07-06 20:44:24 +02:00
refreshPosition(position);
previousPosition = position;
synchronizePosition(true);
};
if (chunks != null && chunks.length > 0) {
// Chunks need to be loaded before the teleportation can happen
return ChunkUtils.optionalLoadAll(instance, chunks, null).thenRun(endCallback);
}
final Pos currentPosition = this.position;
if (!currentPosition.sameChunk(position)) {
// Ensure that the chunk is loaded
return instance.loadOptionalChunk(position).thenRun(endCallback);
2019-08-25 20:03:43 +02:00
} else {
// Position is in the same chunk, keep it sync
endCallback.run();
return AsyncUtils.empty();
2019-08-25 20:03:43 +02:00
}
}
2021-07-11 02:54:02 +02:00
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position) {
return 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) {
2021-07-06 20:44:24 +02:00
this.position = position.withView(yaw, pitch);
2021-07-22 12:50:38 +02:00
sendPacketToViewersAndSelf(new EntityHeadLookPacket(getEntityId(), yaw));
sendPacketToViewersAndSelf(new EntityRotationPacket(getEntityId(), yaw, pitch, onGround));
}
/**
* 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
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
* 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;
}
/**
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
public final boolean addViewer(@NotNull Player player) {
synchronized (this.entityTypeLock) {
return addViewer0(player);
}
}
protected boolean addViewer0(@NotNull Player player) {
if (player == this || !this.viewers.add(player)) {
2020-05-29 02:11:41 +02:00
return false;
}
2019-09-01 06:18:41 +02:00
player.viewableEntities.add(this);
PlayerConnection playerConnection = player.getPlayerConnection();
2021-07-27 11:56:20 +02:00
playerConnection.sendPacket(getEntityType().registry().spawnType().getSpawnPacket(this));
if (hasVelocity()) {
playerConnection.sendPacket(getVelocityPacket());
}
playerConnection.sendPacket(getMetadataPacket());
// Passenger
if (hasPassenger()) {
playerConnection.sendPacket(getPassengersPacket());
}
// Head position
2021-07-22 12:50:38 +02:00
playerConnection.sendPacket(new EntityHeadLookPacket(getEntityId(), position.yaw()));
return true;
}
@Override
public final boolean removeViewer(@NotNull Player player) {
synchronized (this.entityTypeLock) {
return removeViewer0(player);
}
}
protected boolean removeViewer0(@NotNull Player player) {
if (player == this || !viewers.remove(player)) {
return false;
}
2021-07-14 16:26:32 +02:00
player.getPlayerConnection().sendPacket(new DestroyEntitiesPacket(getEntityId()));
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 unmodifiableViewers;
}
/**
* Changes the entity type of this entity.
* <p>
* Works by changing the internal entity type field and by calling {@link #removeViewer(Player)}
* followed by {@link #addViewer(Player)} to all current viewers.
* <p>
* 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
*/
2021-03-04 23:06:29 +01:00
public void switchEntityType(@NotNull EntityType entityType) {
synchronized (entityTypeLock) {
this.entityType = entityType;
this.metadata = new Metadata(this);
this.entityMeta = EntityTypeImpl.createMeta(entityType, this, this.metadata);
Set<Player> viewers = new HashSet<>(getViewers());
getViewers().forEach(this::removeViewer0);
viewers.forEach(this::addViewer0);
}
}
@NotNull
@Override
2020-12-10 02:56:56 +01:00
public Set<Permission> getAllPermissions() {
return permissions;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Updates the entity, called every tick.
* <p>
* Ignored if {@link #getInstance()} returns null.
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
*/
2021-03-11 20:54:30 +01:00
@Override
2020-02-09 15:34:09 +01:00
public void tick(long time) {
if (instance == null)
return;
// Scheduled remove
if (scheduledRemoveTime != 0) {
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;
2019-08-21 16:50:52 +02:00
}
}
// Instant remove
if (shouldRemove()) {
remove();
return;
}
2021-04-12 04:50:45 +02:00
// Fix current chunk being null if the entity has been spawned before
if (currentChunk == null) {
refreshCurrentChunk(instance.getChunkAt(position));
2021-04-12 04:50:45 +02:00
}
// 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<Entity> callback;
while ((callback = nextTick.poll()) != null) {
callback.accept(this);
}
}
// Entity tick
{
// Cache the number of "gravity tick"
2021-07-30 18:10:35 +02:00
this.gravityTickCount = onGround ? 0 : gravityTickCount + 1;
velocityTick();
2019-08-27 20:49:11 +02:00
// handle block contacts
touchTick();
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++;
EventDispatcher.call(new EntityTickEvent(this), GlobalHandles.ENTITY_TICK);
2021-01-03 00:23:41 +01:00
// remove expired effects
effectTick(time);
}
// Scheduled synchronization
2021-03-31 19:17:37 +02:00
if (!Cooldown.hasCooldown(time, lastAbsoluteSynchronizationTime, getSynchronizationCooldown())) {
synchronizePosition(false);
2019-08-19 17:04:19 +02:00
}
if (shouldRemove() && !MinecraftServer.isStopping()) {
remove();
}
2019-08-19 17:04:19 +02:00
}
2021-07-30 18:10:35 +02:00
private void velocityTick() {
final boolean isSocketClient = PlayerUtils.isSocketClient(this);
if (isSocketClient) {
2021-08-22 08:13:28 +02:00
if (position.samePoint(previousPosition))
return; // Didn't move since last tick
// Calculate velocity from client
velocity = position.sub(previousPosition).asVec().mul(MinecraftServer.TICK_PER_SECOND);
previousPosition = position;
return;
}
2021-07-30 18:10:35 +02:00
final boolean noGravity = hasNoGravity();
final boolean hasVelocity = hasVelocity();
if (!hasVelocity && noGravity) {
2021-07-30 18:10:35 +02:00
return;
}
final float tps = MinecraftServer.TICK_PER_SECOND;
final Vec currentVelocity = getVelocity();
final Vec deltaPos = new Vec(
currentVelocity.x() / tps,
currentVelocity.y() / tps - (noGravity ? 0 : gravityAcceleration),
currentVelocity.z() / tps
);
final Pos newPosition;
final Vec newVelocity;
if (this.hasPhysics) {
final var physicsResult = CollisionUtils.handlePhysics(this, deltaPos);
this.onGround = physicsResult.isOnGround();
newPosition = physicsResult.newPosition();
newVelocity = physicsResult.newVelocity();
} else {
newVelocity = deltaPos;
newPosition = position.add(currentVelocity.div(20));
}
// World border collision
final var finalVelocityPosition = CollisionUtils.applyWorldBorder(instance, position, newPosition);
if (finalVelocityPosition.samePoint(position)) {
this.velocity = Vec.ZERO;
if (hasVelocity) {
sendPacketToViewers(getVelocityPacket());
2021-07-30 18:10:35 +02:00
}
return;
}
final Chunk finalChunk = ChunkUtils.retrieve(instance, currentChunk, finalVelocityPosition);
if (!ChunkUtils.isLoaded(finalChunk)) {
// Entity shouldn't be updated when moving in an unloaded chunk
return;
}
2021-08-13 08:01:48 +02:00
if (this instanceof ItemEntity) {
// TODO find other exceptions
this.position = finalVelocityPosition;
refreshCoordinate(finalVelocityPosition);
} else {
refreshPosition(finalVelocityPosition, true);
}
2021-07-30 18:10:35 +02:00
// Update velocity
if (hasVelocity || !newVelocity.isZero()) {
final double airDrag = this instanceof LivingEntity ? 0.91 : 0.98;
final double drag = this.onGround ?
finalChunk.getBlock(position).registry().friction() : airDrag;
this.velocity = newVelocity
// Convert from block/tick to block/sec
.mul(tps)
// Apply drag
.apply((x, y, z) -> new Vec(
x * drag,
!noGravity ? y * (1 - gravityDragPerTick) : y,
z * drag
))
// Prevent infinitely decreasing velocity
.apply(Vec.Operator.EPSILON);
2021-07-30 18:10:35 +02:00
}
// Verify if velocity packet has to be sent
if (hasVelocity || gravityTickCount > 0) {
sendPacketToViewers(getVelocityPacket());
2021-07-30 18:10:35 +02:00
}
}
private void touchTick() {
// TODO do not call every tick (it is pretty expensive)
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());
for (int y = minY; y <= maxY; y++) {
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
final Chunk chunk = ChunkUtils.retrieve(instance, currentChunk, x, z);
if (!ChunkUtils.isLoaded(chunk))
continue;
final Block block = chunk.getBlock(x, y, z, BlockGetter.Condition.CACHED);
if (block == null)
continue;
final BlockHandler handler = block.handler();
if (handler != null) {
// checks that we are actually in the block, and not just here because of a rounding error
if (boundingBox.intersectWithBlock(x, y, z)) {
// TODO: replace with check with custom block bounding box
handler.onTouch(new BlockHandler.Touch(block, instance, new Vec(x, y, z), this));
}
}
}
}
}
}
private void effectTick(long time) {
final List<TimedPotion> effects = this.effects;
if (effects.isEmpty()) return;
effects.removeIf(timedPotion -> {
final long potionTime = (long) timedPotion.getPotion().getDuration() * MinecraftServer.TICK_MS;
// Remove if the potion should be expired
if (time >= timedPotion.getStartingTime() + potionTime) {
// Send the packet that the potion should no longer be applied
timedPotion.getPotion().sendRemovePacket(this);
EventDispatcher.call(new EntityPotionRemoveEvent(this, timedPotion.getPotion()));
return true;
}
return false;
});
}
/**
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-05-15 18:03:28 +02:00
/**
* Each entity has an unique id (server-wide) which will change after a restart.
2020-05-15 18:03:28 +02:00
*
* @return the unique entity id
* @see Entity#getEntity(int) to retrive an entity based on its id
2020-05-15 18:03:28 +02:00
*/
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
*/
2021-02-25 08:38:53 +01:00
@NotNull
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
*/
public void setUuid(@NotNull UUID uuid) {
2021-01-03 00:23:41 +01:00
// Refresh internal map
2021-03-26 21:26:35 +01:00
Entity.ENTITY_BY_UUID.remove(this.uuid);
Entity.ENTITY_BY_UUID.put(uuid, this);
2021-01-03 00:23:41 +01:00
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.
2021-01-25 19:33:53 +01:00
*
* @param x the bounding box X size
2020-05-29 23:17:14 +02:00
* @param y the bounding box Y size
* @param z the bounding box Z size
*/
public void setBoundingBox(double x, double y, double z) {
2019-08-30 01:17:46 +02:00
this.boundingBox = new BoundingBox(this, x, y, z);
}
/**
* Changes the internal entity bounding box.
* <p>
* 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;
}
2019-08-30 01:17:46 +02:00
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
*/
2021-04-10 21:42:45 +02:00
public @Nullable Chunk getChunk() {
return currentChunk;
2020-09-03 00:43:42 +02:00
}
@ApiStatus.Internal
protected void refreshCurrentChunk(Chunk currentChunk) {
this.currentChunk = currentChunk;
MinecraftServer.getUpdateManager().getThreadProvider().updateEntity(this);
}
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
*/
2021-04-10 21:42:45 +02:00
public @Nullable Instance getInstance() {
2019-08-11 07:42:56 +02:00
return instance;
}
2020-05-29 23:17:14 +02:00
/**
* Changes the entity instance, i.e. spawns it.
2020-05-29 23:17:14 +02:00
*
2021-02-25 15:48:48 +01:00
* @param instance the new instance of the entity
* @param spawnPosition the spawn position for the entity.
2021-07-11 13:45:28 +02:00
* @return a {@link CompletableFuture} called once the entity's instance has been set,
* this is due to chunks needing to load
2021-07-11 03:35:17 +02:00
* @throws IllegalStateException if {@code instance} has not been registered in {@link InstanceManager}
2020-05-29 23:17:14 +02:00
*/
2021-07-11 02:59:24 +02:00
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
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");
final Instance previousInstance = this.instance;
if (previousInstance != null) {
previousInstance.UNSAFE_removeEntity(this);
2019-08-10 08:44:35 +02:00
}
2021-07-06 20:44:24 +02:00
this.position = spawnPosition;
this.previousPosition = spawnPosition;
2019-08-11 07:42:56 +02:00
this.isActive = true;
this.instance = instance;
return instance.loadOptionalChunk(position).thenAccept(chunk -> {
Check.notNull(chunk, "Entity has been placed in an unloaded chunk!");
refreshCurrentChunk(chunk);
instance.UNSAFE_addEntity(this);
spawn();
EventDispatcher.call(new EntitySpawnEvent(this, instance));
});
2019-08-24 20:34:01 +02:00
}
2021-07-11 02:59:24 +02:00
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Point spawnPosition) {
return setInstance(instance, Pos.fromPoint(spawnPosition));
2021-07-08 18:26:26 +02:00
}
2020-05-29 23:17:14 +02:00
/**
* Changes the entity instance.
*
* @param instance the new instance of the entity
2021-07-11 13:45:28 +02:00
* @return a {@link CompletableFuture} called once the entity's instance has been set,
* this is due to chunks needing to load
* @throws NullPointerException if {@code instance} is null
* @throws IllegalStateException if {@code instance} has not been registered in {@link InstanceManager}
*/
2021-07-11 13:45:28 +02:00
public CompletableFuture<Void> setInstance(@NotNull Instance instance) {
return setInstance(instance, this.position);
}
/**
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
*/
2021-07-06 20:44:24 +02:00
public @NotNull Vec getVelocity() {
2019-08-25 20:03:43 +02:00
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
*/
2021-07-06 20:44:24 +02:00
public void setVelocity(@NotNull Vec velocity) {
EntityVelocityEvent entityVelocityEvent = new EntityVelocityEvent(this, velocity);
2021-06-04 03:48:51 +02:00
EventDispatcher.callCancellable(entityVelocityEvent, () -> {
2021-07-06 20:44:24 +02:00
this.velocity = 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() {
2021-07-09 12:42:36 +02:00
return !velocity.isZero();
2020-05-29 23:17:14 +02:00
}
/**
* Gets the gravity drag per tick.
*
* @return the gravity drag per tick in block
*/
2021-01-25 19:33:53 +01:00
public double getGravityDragPerTick() {
return gravityDragPerTick;
}
/**
* Gets the gravity acceleration.
*
* @return the gravity acceleration in block
*/
2021-01-25 19:33:53 +01:00
public double getGravityAcceleration() {
return gravityAcceleration;
}
/**
* 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;
}
2020-05-29 23:17:14 +02:00
/**
* Changes the gravity of the entity.
2020-05-29 23:17:14 +02:00
*
* @param gravityDragPerTick the gravity drag per tick in block
* @param gravityAcceleration the gravity acceleration in block
* @see <a href="https://minecraft.gamepedia.com/Entity#Motion_of_entities">Entities motion</a>
2020-05-29 23:17:14 +02:00
*/
2021-06-28 19:24:53 +02:00
public void setGravity(double gravityDragPerTick, double gravityAcceleration) {
2019-08-27 20:49:11 +02:00
this.gravityDragPerTick = gravityDragPerTick;
this.gravityAcceleration = gravityAcceleration;
2019-08-27 20:49:11 +02:00
}
2021-07-24 04:31:35 +02:00
public double getDistance(@NotNull Point point) {
return getPosition().distance(point);
}
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}
*/
public double getDistance(@NotNull Entity entity) {
2021-07-24 04:31:35 +02:00
return getDistance(entity.getPosition());
2019-08-19 17:04:19 +02:00
}
/**
* 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) {
2021-07-06 20:44:24 +02:00
return getPosition().distanceSquared(entity.getPosition());
}
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-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-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) {
2021-07-22 12:50:38 +02:00
sendPacketToViewersAndSelf(new EntityStatusPacket(getEntityId(), status));
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() {
2021-02-23 17:18:53 +01:00
return this.entityMeta.isOnFire();
2020-05-24 19:59:50 +02:00
}
/**
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,
2021-06-30 00:59:26 +02:00
* see {@link LivingEntity#setFireForDuration(int, TemporalUnit)}.
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) {
2021-02-23 17:18:53 +01:00
this.entityMeta.setOnFire(fire);
2021-01-30 04:44:44 +01:00
}
/**
* Gets if the entity is sneaking.
* <p>
* 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() {
2021-02-23 17:18:53 +01:00
return this.entityMeta.isSneaking();
2021-01-30 04:44:44 +01:00
}
/**
* Makes the entity sneak.
* <p>
* 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);
2021-02-23 17:18:53 +01:00
this.entityMeta.setSneaking(sneaking);
2021-01-30 04:44:44 +01:00
}
/**
* Gets if the player is sprinting.
* <p>
* 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() {
2021-02-23 17:18:53 +01:00
return this.entityMeta.isSprinting();
2021-01-30 04:44:44 +01:00
}
/**
* Makes the entity sprint.
* <p>
* WARNING: this will not work on the client itself.
*
* @param sprinting true to make the entity sprint
*/
public void setSprinting(boolean sprinting) {
2021-02-23 17:18:53 +01:00
this.entityMeta.setSprinting(sprinting);
2019-08-23 15:37:38 +02:00
}
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() {
2021-02-23 17:18:53 +01:00
return this.entityMeta.isInvisible();
2020-05-26 00:07:35 +02:00
}
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) {
2021-02-23 17:18:53 +01:00
this.entityMeta.setInvisible(invisible);
2020-05-26 00:07:35 +02:00
}
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() {
2021-02-23 17:18:53 +01:00
return this.entityMeta.isHasGlowingEffect();
2019-08-27 20:49:11 +02:00
}
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) {
2021-02-23 17:18:53 +01:00
this.entityMeta.setHasGlowingEffect(glowing);
2021-01-30 04:44:44 +01:00
}
/**
* Gets the current entity pose.
*
* @return the entity pose
*/
@NotNull
public Pose getPose() {
2021-02-23 17:18:53 +01:00
return this.entityMeta.getPose();
2021-01-30 04:44:44 +01:00
}
/**
* Changes the entity pose.
* <p>
* The internal {@code crouched} and {@code swimming} field will be
* updated accordingly.
*
* @param pose the new entity pose
*/
public void setPose(@NotNull Pose pose) {
2021-02-23 17:18:53 +01:00
this.entityMeta.setPose(pose);
2020-05-29 23:17:14 +02:00
}
/**
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
*/
2021-07-27 06:55:08 +02:00
public @Nullable Component getCustomName() {
2021-02-23 17:18:53 +01:00
return this.entityMeta.getCustomName();
2020-05-26 00:07:35 +02:00
}
2021-03-03 20:27:33 +01:00
/**
* 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);
}
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() {
2021-02-23 17:18:53 +01:00
return this.entityMeta.isCustomNameVisible();
2020-05-26 00:07:35 +02:00
}
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) {
2021-02-23 17:18:53 +01:00
this.entityMeta.setCustomNameVisible(customNameVisible);
2020-05-26 00:07:35 +02:00
}
public boolean isSilent() {
2021-02-23 17:18:53 +01:00
return this.entityMeta.isSilent();
2020-05-26 00:07:35 +02:00
}
public void setSilent(boolean silent) {
2021-02-23 17:18:53 +01:00
this.entityMeta.setSilent(silent);
2020-05-26 00:07:35 +02:00
}
2020-05-24 19:59:50 +02:00
/**
2021-01-30 04:44:44 +01:00
* Gets the noGravity metadata field.
2020-05-29 23:17:14 +02:00
*
2021-01-30 04:44:44 +01:00
* @return true if the entity ignore gravity, false otherwise
2020-05-24 19:59:50 +02:00
*/
2021-01-30 04:44:44 +01:00
public boolean hasNoGravity() {
2021-02-23 17:18:53 +01:00
return this.entityMeta.isHasNoGravity();
2019-08-20 22:40:57 +02:00
}
2020-05-24 19:59:50 +02:00
/**
2021-01-30 04:44:44 +01:00
* Changes the noGravity metadata field and change the gravity behaviour accordingly.
2020-05-29 23:17:14 +02:00
*
2021-01-30 04:44:44 +01:00
* @param noGravity should the entity ignore gravity
2020-05-24 19:59:50 +02:00
*/
2021-01-30 04:44:44 +01:00
public void setNoGravity(boolean noGravity) {
2021-02-23 17:18:53 +01:00
this.entityMeta.setHasNoGravity(noGravity);
2019-08-21 16:50:52 +02:00
}
2021-07-06 20:44:24 +02:00
/**
2021-07-07 19:20:58 +02:00
* Updates internal fields and sends updates.
2021-07-06 20:44:24 +02:00
*
* @param newPosition the new position
2021-07-06 20:44:24 +02:00
*/
@ApiStatus.Internal
public void refreshPosition(@NotNull final Pos newPosition, boolean ignoreView) {
2021-07-13 18:21:32 +02:00
final var previousPosition = this.position;
final Pos position = ignoreView ? previousPosition.withCoord(newPosition) : newPosition;
if (position.equals(lastSyncedPosition)) return;
this.position = position;
2021-07-13 18:21:32 +02:00
if (!position.samePoint(previousPosition)) {
2021-07-07 19:20:58 +02:00
refreshCoordinate(position);
}
2021-07-20 03:06:27 +02:00
final boolean viewChange = !position.sameView(lastSyncedPosition);
final double distanceX = Math.abs(position.x() - lastSyncedPosition.x());
final double distanceY = Math.abs(position.y() - lastSyncedPosition.y());
final double distanceZ = Math.abs(position.z() - lastSyncedPosition.z());
final boolean positionChange = (distanceX + distanceY + distanceZ) > 0;
if (distanceX > 8 || distanceY > 8 || distanceZ > 8) {
sendPacketToViewers(new EntityTeleportPacket(getEntityId(), position, isOnGround()));
2021-07-20 03:06:27 +02:00
} else if (positionChange && viewChange) {
sendPacketToViewers(EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
lastSyncedPosition, isOnGround()));
// Fix head rotation
sendPacketToViewers(new EntityHeadLookPacket(getEntityId(), position.yaw()));
} else if (positionChange) {
sendPacketToViewers(EntityPositionPacket.getPacket(getEntityId(), position, lastSyncedPosition, onGround));
} else if (viewChange) {
sendPacketToViewers(new EntityHeadLookPacket(getEntityId(), position.yaw()));
sendPacketToViewers(new EntityRotationPacket(getEntityId(), position.yaw(), position.pitch(), onGround));
}
this.lastAbsoluteSynchronizationTime = System.currentTimeMillis();
2021-07-20 03:06:27 +02:00
this.lastSyncedPosition = position;
2021-07-06 20:44:24 +02:00
}
@ApiStatus.Internal
public void refreshPosition(@NotNull final Pos newPosition) {
refreshPosition(newPosition, false);
2021-07-06 20:44:24 +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>
2021-07-06 20:44:24 +02:00
* WARNING: unsafe, should only be used internally in Minestom. Use {@link #teleport(Pos)} instead.
2020-05-24 19:59:50 +02:00
*
2021-07-06 20:44:24 +02:00
* @param newPosition the new position
2020-05-24 19:59:50 +02:00
*/
2021-07-06 20:44:24 +02:00
private void refreshCoordinate(Point newPosition) {
2020-05-23 14:04:53 +02:00
if (hasPassenger()) {
for (Entity passenger : getPassengers()) {
2021-07-13 18:21:32 +02:00
passenger.position = passenger.position.withCoord(newPosition);
passenger.previousPosition = passenger.position;
2021-07-06 20:44:24 +02:00
passenger.refreshCoordinate(newPosition);
2020-05-23 14:04:53 +02:00
}
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) {
2021-04-10 21:42:45 +02:00
final int lastChunkX = currentChunk.getChunkX();
final int lastChunkZ = currentChunk.getChunkZ();
2021-07-06 20:44:24 +02:00
final int newChunkX = ChunkUtils.getChunkCoordinate(newPosition.x());
final int newChunkZ = ChunkUtils.getChunkCoordinate(newPosition.z());
if (lastChunkX != newChunkX || lastChunkZ != newChunkZ) {
// Entity moved in a new chunk
final Chunk newChunk = instance.getChunk(newChunkX, newChunkZ);
2021-07-06 20:44:24 +02:00
Check.notNull(newChunk, "The entity {0} tried to move in an unloaded chunk at {1}", getEntityId(), newPosition);
2021-04-10 21:57:33 +02:00
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);
2019-08-11 07:42:56 +02:00
}
}
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
*/
2021-07-06 20:44:24 +02:00
public @NotNull Pos getPosition() {
2019-08-21 16:50:52 +02:00
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.
* <p>
* Default to {@link BoundingBox#getHeight()}x0.85
2020-05-29 23:17:14 +02:00
*
* @return the entity eye height
*/
public double getEyeHeight() {
return boundingBox.getHeight() * 0.85;
2020-05-21 00:33:56 +02:00
}
2021-01-03 00:23:41 +01:00
/**
* Gets all the potion effect of this entity.
*
* @return an unmodifiable list of all this entity effects
*/
@NotNull
public List<TimedPotion> getActiveEffects() {
return Collections.unmodifiableList(effects);
}
2021-02-22 06:00:49 +01:00
/**
* Adds an effect to an entity.
*
* @param potion The potion to add
*/
public void addEffect(@NotNull Potion potion) {
removeEffect(potion.getEffect());
this.effects.add(new TimedPotion(potion, System.currentTimeMillis()));
potion.sendAddPacket(this);
2021-06-04 03:48:51 +02:00
EventDispatcher.call(new EntityPotionAddEvent(this, potion));
2021-02-22 06:00:49 +01:00
}
2021-01-03 00:23:41 +01:00
/**
* Removes effect from entity, if it has it.
*
* @param effect The effect to remove
*/
public void removeEffect(@NotNull PotionEffect effect) {
this.effects.removeIf(timedPotion -> {
if (timedPotion.getPotion().getEffect() == effect) {
timedPotion.getPotion().sendRemovePacket(this);
2021-06-04 03:48:51 +02:00
EventDispatcher.call(new EntityPotionRemoveEvent(this, timedPotion.getPotion()));
2021-01-03 00:23:41 +01:00
return true;
}
return false;
});
}
/**
2021-02-22 06:00:49 +01:00
* Removes all the effects currently applied to the entity.
2021-01-03 00:23:41 +01:00
*/
2021-02-22 06:00:49 +01:00
public void clearEffects() {
for (TimedPotion timedPotion : effects) {
timedPotion.getPotion().sendRemovePacket(this);
2021-06-04 03:48:51 +02:00
EventDispatcher.call(new EntityPotionRemoveEvent(this, timedPotion.getPotion()));
2021-02-22 06:00:49 +01:00
}
this.effects.clear();
2021-01-03 00:23:41 +01:00
}
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>
2020-12-30 20:29:46 +01:00
* WARNING: this does not trigger {@link EntityDeathEvent}.
2020-05-15 18:03:28 +02:00
*/
2019-08-10 08:44:35 +02:00
public void remove() {
2021-08-05 15:10:15 +02:00
if (isRemoved()) return;
2021-07-30 12:38:15 +02:00
// Remove passengers if any (also done with LivingEntity#kill)
if (hasPassenger()) {
getPassengers().forEach(this::removePassenger);
}
2021-07-30 14:01:54 +02:00
var vehicle = this.vehicle;
if (vehicle != null) {
vehicle.removePassenger(this);
}
2021-04-15 01:44:08 +02:00
MinecraftServer.getUpdateManager().getThreadProvider().removeEntity(this);
this.removed = true;
2019-08-10 08:44:35 +02:00
this.shouldRemove = true;
2021-03-26 21:26:35 +01:00
Entity.ENTITY_BY_ID.remove(id);
Entity.ENTITY_BY_UUID.remove(uuid);
2021-08-05 15:10:15 +02:00
if (instance != null) {
instance.UNSAFE_removeEntity(this);
2021-08-05 15:10:15 +02:00
}
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
*
* @param delay the time before removing the entity,
* 0 to cancel the removing
2021-06-30 00:59:26 +02:00
* @param temporalUnit the unit of the delay
2020-05-15 18:03:28 +02:00
*/
2021-06-30 00:59:26 +02:00
public void scheduleRemove(long delay, @NotNull TemporalUnit temporalUnit) {
scheduleRemove(Duration.of(delay, temporalUnit));
}
/**
* Triggers {@link #remove()} after the specified time.
*
* @param delay the time before removing the entity,
* 0 to cancel the removing
*/
public void scheduleRemove(Duration delay) {
if (delay.isZero()) { // Cancel the scheduled remove
2019-08-21 16:50:52 +02:00
this.scheduledRemoveTime = 0;
return;
}
this.scheduledRemoveTime = System.currentTimeMillis() + delay.toMillis();
2019-08-20 22:40:57 +02:00
}
2020-05-15 18:03:28 +02:00
/**
* Gets if the entity removal has been scheduled with {@link #scheduleRemove(Duration)}.
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;
}
2021-07-06 20:44:24 +02:00
protected @NotNull Vec getVelocityForPacket() {
return this.velocity.mul(8000f / MinecraftServer.TICK_PER_SECOND);
2021-02-25 06:59:55 +01:00
}
2021-07-06 20:44:24 +02:00
protected @NotNull EntityVelocityPacket getVelocityPacket() {
2021-07-22 12:50:38 +02:00
return new EntityVelocityPacket(getEntityId(), getVelocityForPacket());
2019-08-24 20:34:01 +02:00
}
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
*/
2021-07-22 13:01:00 +02:00
public @NotNull EntityMetaDataPacket getMetadataPacket() {
return new EntityMetaDataPacket(getEntityId(), metadata.getEntries());
2019-08-22 14:52:32 +02:00
}
/**
2021-05-01 00:51:10 +02:00
* 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) {
2021-07-20 03:06:27 +02:00
sendPacketToViewers(new EntityTeleportPacket(getEntityId(), position, isOnGround()));
this.lastAbsoluteSynchronizationTime = System.currentTimeMillis();
2021-07-06 20:44:24 +02:00
this.lastSyncedPosition = position;
}
/**
2020-10-15 21:16:31 +02:00
* Asks for a synchronization (position) to happen during next entity tick.
*/
public void askSynchronization() {
this.lastAbsoluteSynchronizationTime = 0;
2019-08-24 20:34:01 +02:00
}
/**
* Set custom cooldown for position synchronization.
*
* @param cooldown custom cooldown for position synchronization.
*/
2021-06-30 00:59:26 +02:00
public void setCustomSynchronizationCooldown(@Nullable Duration cooldown) {
this.customSynchronizationCooldown = cooldown;
}
@Override
public @NotNull HoverEvent<ShowEntity> asHoverEvent(@NotNull UnaryOperator<ShowEntity> op) {
return HoverEvent.showEntity(ShowEntity.of(this.entityType, this.uuid));
}
2021-06-30 00:59:26 +02:00
private Duration getSynchronizationCooldown() {
2021-04-07 18:35:19 +02:00
return Objects.requireNonNullElse(this.customSynchronizationCooldown, SYNCHRONIZATION_COOLDOWN);
}
@ApiStatus.Experimental
2021-04-26 12:52:02 +02:00
public <T extends Entity> @NotNull Acquirable<T> getAcquirable() {
return (Acquirable<T>) acquirable;
}
@ApiStatus.Experimental
2021-04-26 12:52:02 +02:00
public <T extends Entity> @NotNull Acquirable<T> getAcquirable(@NotNull Class<T> clazz) {
return (Acquirable<T>) acquirable;
2021-04-15 01:44:08 +02:00
}
@Override
public <T> @Nullable T getTag(@NotNull Tag<T> tag) {
return tag.read(nbtCompound);
}
@Override
public <T> void setTag(@NotNull Tag<T> tag, @Nullable T value) {
tag.write(nbtCompound, value);
}
2021-06-27 23:05:54 +02:00
/**
* Applies knockback to the entity
*
* @param strength the strength of the knockback, 0.4 is the vanilla value for a bare hand hit
* @param x knockback on x axle, for default knockback use the following formula <pre>sin(attacker.yaw * (pi/180))</pre>
* @param z knockback on z axle, for default knockback use the following formula <pre>-cos(attacker.yaw * (pi/180))</pre>
2021-06-27 23:05:54 +02:00
*/
public void takeKnockback(final float strength, final double x, final double z) {
if (strength > 0) {
2021-06-28 19:23:36 +02:00
//TODO check possible side effects of unnatural TPS (other than 20TPS)
2021-07-06 20:44:24 +02:00
final Vec velocityModifier = new Vec(x, z)
.normalize()
.mul(strength * MinecraftServer.TICK_PER_SECOND / 2);
setVelocity(new Vec(velocity.x() / 2d - velocityModifier.x(),
onGround ? Math.min(.4d, velocity.y() / 2d + strength) * MinecraftServer.TICK_PER_SECOND : velocity.y(),
velocity.z() / 2d - velocityModifier.z()
));
2021-06-27 23:05:54 +02:00
}
}
2021-08-22 16:47:52 +02:00
/**
* Gets the line of sight of the entity.
*
* @param maxDistance The max distance to scan
* @return A list of {@link Point poiints} in this entities line of sight
*/
public List<Point> getLineOfSight(int maxDistance) {
2021-08-22 16:47:52 +02:00
Instance instance = getInstance();
if (instance == null) {
return Collections.emptyList();
}
List<Point> blocks = new ArrayList<>();
2021-08-22 16:47:52 +02:00
var it = new BlockIterator(this, maxDistance);
while (it.hasNext()) {
final Point position = it.next();
2021-08-22 16:47:52 +02:00
if (!instance.getBlock(position).isAir()) blocks.add(position);
}
return blocks;
}
/**
* Checks whether the current entity has line of sight to the given one.
* If so, it doesn't mean that the given entity is IN line of sight of the current,
* but the current one can rotate so that it will be true.
*
* @param entity the entity to be checked.
* @return if the current entity has line of sight to the given one.
*/
public boolean hasLineOfSight(Entity entity) {
2021-08-22 16:47:52 +02:00
Instance instance = getInstance();
if (instance == null) {
return false;
}
final Vec start = getPosition().asVec().add(0D, getEyeHeight(), 0D);
final Vec end = entity.getPosition().asVec().add(0D, getEyeHeight(), 0D);
final Vec direction = end.sub(start);
final int maxDistance = (int) Math.ceil(direction.length());
2021-08-22 16:47:52 +02:00
var it = new BlockIterator(start, direction.normalize(), 0D, maxDistance);
while (it.hasNext()) {
2021-08-22 16:47:52 +02:00
Block block = instance.getBlock(it.next());
if (!block.isAir() && !block.isLiquid()) {
return false;
}
}
return true;
}
/**
* Gets first entity on the line of sight of the current one that matches the given predicate.
*
* @param range max length of the line of sight of the current entity to be checked.
2021-08-22 16:47:52 +02:00
* @param predicate optional predicate
* @return resulting entity whether there're any, null otherwise.
*/
2021-08-22 16:47:52 +02:00
public @Nullable Entity getLineOfSightEntity(double range, Predicate<Entity> predicate) {
Instance instance = getInstance();
if (instance == null) {
return null;
}
2021-08-22 16:47:52 +02:00
Vec start = new Vec(position.x(), position.y() + getEyeHeight(), position.z());
Vec end = start.add(position.direction().mul(range));
2021-08-22 16:47:52 +02:00
List<Entity> nearby = instance.getNearbyEntities(position, range).stream()
.filter(e -> e != this && e.boundingBox.intersect(start, end) && predicate.test(e))
.collect(Collectors.toList());
if (nearby.isEmpty()) {
return null;
}
2021-08-22 16:47:52 +02:00
Vec direction = end.sub(start);
int maxDistance = (int) Math.ceil(direction.length());
double maxVisibleDistanceSquared = direction.lengthSquared();
2021-08-22 16:47:52 +02:00
var iterator = new BlockIterator(start, direction.normalize(), 0D, maxDistance);
while (iterator.hasNext()) {
2021-08-22 16:47:52 +02:00
Point blockPos = iterator.next();
Block block = instance.getBlock(blockPos);
if (!block.isAir() && !block.isLiquid()) {
maxVisibleDistanceSquared = blockPos.distanceSquared(position);
break;
}
}
Entity result = null;
double minDistanceSquared = 0D;
2021-08-22 16:47:52 +02:00
for (Entity entity : nearby) {
double distanceSquared = entity.getDistanceSquared(this);
if (result == null || minDistanceSquared > distanceSquared) {
result = entity;
minDistanceSquared = distanceSquared;
}
}
if (minDistanceSquared < maxVisibleDistanceSquared) {
return result;
} else {
return null;
}
}
2020-12-29 00:43:04 +01:00
public 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;
}
}