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.ServerProcess; import net.minestom.server.Tickable; import net.minestom.server.Viewable; import net.minestom.server.collision.BoundingBox; import net.minestom.server.collision.CollisionUtils; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.metadata.EntityMeta; import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.EventFilter; import net.minestom.server.event.EventHandler; import net.minestom.server.event.EventNode; import net.minestom.server.event.entity.*; import net.minestom.server.event.instance.AddEntityToInstanceEvent; import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent; import net.minestom.server.event.trait.EntityEvent; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.EntityTracker; import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceManager; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockHandler; import net.minestom.server.network.packet.server.CachedPacket; import net.minestom.server.network.packet.server.LazyPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.play.*; 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.snapshot.EntitySnapshot; import net.minestom.server.snapshot.SnapshotUpdater; import net.minestom.server.snapshot.Snapshotable; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagHandler; import net.minestom.server.tag.TagReadable; import net.minestom.server.thread.Acquirable; import net.minestom.server.timer.Schedulable; import net.minestom.server.timer.Scheduler; import net.minestom.server.timer.TaskSchedule; import net.minestom.server.utils.ArrayUtils; import net.minestom.server.utils.PacketUtils; import net.minestom.server.utils.async.AsyncUtils; import net.minestom.server.utils.block.BlockIterator; 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.position.PositionUtils; import net.minestom.server.utils.time.Cooldown; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound; import space.vectrix.flare.fastutil.Int2ObjectSyncMap; import java.time.Duration; import java.time.temporal.TemporalUnit; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; 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.Predicate; 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, Schedulable, Snapshotable, EventHandler
* Unit: 1/tick
*/
protected double gravityDragPerTick;
/**
* Acceleration on the Y axle due to gravity
*
* Unit: blocks/tick
*/
protected double gravityAcceleration;
protected int gravityTickCount; // Number of tick where gravity tick was applied
private final int id;
// Players must be aware of all surrounding entities
// General entities should only be aware of surrounding players to update their viewing list
private final EntityTracker.Target
* Entity id are unique server-wide.
*
* @param id the entity unique id
* @return the entity having the specified id, null if not found
*/
public static @Nullable Entity getEntity(int id) {
return Entity.ENTITY_BY_ID.get(id);
}
/**
* 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
*/
public static @Nullable 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.
*/
public @NotNull 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}
* @throws IllegalStateException if you try to teleport an entity before settings its instance
*/
public @NotNull CompletableFuture
* 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 synchronized void switchEntityType(@NotNull EntityType entityType) {
this.entityType = entityType;
this.metadata = new Metadata(this);
this.entityMeta = EntityTypeImpl.createMeta(entityType, 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 || isRemoved() || !ChunkUtils.isLoaded(currentChunk))
return;
// scheduled tasks
this.scheduler.processTick();
if (isRemoved()) return;
// Entity tick
{
// Cache the number of "gravity tick"
velocityTick();
// handle block contacts
touchTick();
handleVoid();
// Call the abstract update method
update(time);
ticks++;
EventDispatcher.call(new EntityTickEvent(this));
// remove expired effects
effectTick(time);
}
// Scheduled synchronization
if (!Cooldown.hasCooldown(time, lastAbsoluteSynchronizationTime, getSynchronizationCooldown())) {
synchronizePosition(false);
}
}
private void velocityTick() {
this.gravityTickCount = onGround ? 0 : gravityTickCount + 1;
if (PlayerUtils.isSocketClient(this)) return;
if (vehicle != null) return;
final boolean noGravity = hasNoGravity();
final boolean hasVelocity = hasVelocity();
if (!hasVelocity && noGravity) {
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());
}
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;
}
if (entityType == EntityTypes.ITEM || entityType == EntityType.FALLING_BLOCK) {
// TODO find other exceptions
this.previousPosition = this.position;
this.position = finalVelocityPosition;
refreshCoordinate(finalVelocityPosition);
} else {
refreshPosition(finalVelocityPosition, true);
}
// 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);
}
// Verify if velocity packet has to be sent
if (hasVelocity || gravityTickCount > 0) {
sendPacketToViewers(getVelocityPacket());
}
}
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, Block.Getter.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
* 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.process().dispatcher().updateElement(this, currentChunk);
}
/**
* 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.
* @return a {@link CompletableFuture} called once the entity's instance has been set,
* this is due to chunks needing to load
* @throws IllegalStateException if {@code instance} has not been registered in {@link InstanceManager}
*/
public CompletableFuture
* The final velocity can be cancelled or modified by the event.
*
* @param velocity the new entity velocity
*/
public void setVelocity(@NotNull Vec velocity) {
EntityVelocityEvent entityVelocityEvent = new EntityVelocityEvent(this, velocity);
EventDispatcher.callCancellable(entityVelocityEvent, () -> {
this.velocity = 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 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
* @see Entities motion
*/
public void setGravity(double gravityDragPerTick, double gravityAcceleration) {
this.gravityDragPerTick = gravityDragPerTick;
this.gravityAcceleration = gravityAcceleration;
}
public double getDistance(@NotNull Point point) {
return getPosition().distance(point);
}
/**
* 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 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().distanceSquared(entity.getPosition());
}
/**
* Gets the entity vehicle or null.
*
* @return the entity vehicle, or null if there is not any
*/
public @Nullable 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 or the passenger cannot be added
*/
public void addPassenger(@NotNull Entity entity) {
final Instance currentInstance = this.instance;
Check.stateCondition(currentInstance == null, "You need to set an instance using Entity#setInstance");
Check.stateCondition(entity == getVehicle(), "Cannot add the entity vehicle as a passenger");
final Entity vehicle = entity.getVehicle();
if (vehicle != null) vehicle.removePassenger(entity);
if (!currentInstance.equals(entity.getInstance()))
entity.setInstance(currentInstance, position).join();
this.passengers.add(entity);
entity.vehicle = this;
sendPacketToViewersAndSelf(getPassengersPacket());
// Updates the position of the new passenger, and then teleports the passenger
updatePassengerPosition(position, entity);
entity.synchronizePosition(false);
}
/**
* 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());
entity.synchronizePosition(false);
}
/**
* 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
*/
public @NotNull Set<@NotNull Entity> getPassengers() {
return Collections.unmodifiableSet(passengers);
}
protected @NotNull SetPassengersPacket getPassengersPacket() {
return new SetPassengersPacket(getEntityId(), passengers.stream().map(Entity::getEntityId).toList());
}
/**
* Entity statuses can be found here.
*
* @param status the status to trigger
*/
public void triggerStatus(byte status) {
sendPacketToViewersAndSelf(new EntityStatusPacket(getEntityId(), status));
}
/**
* Gets if the entity is on fire.
*
* @return true if the entity is in fire, false otherwise
*/
public boolean isOnFire() {
return this.entityMeta.isOnFire();
}
/**
* Sets the entity in fire visually.
*
* WARNING: if you want to apply damage or specify a duration,
* see {@link LivingEntity#setFireForDuration(int, TemporalUnit)}.
*
* @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
*/
public @NotNull 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
*/
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
*/
public @Nullable Component getCustomName() {
return this.entityMeta.getCustomName();
}
/**
* 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);
}
/**
* Updates internal fields and sends updates.
*
* @param newPosition the new position
*/
@ApiStatus.Internal
public void refreshPosition(@NotNull final Pos newPosition, boolean ignoreView) {
final var previousPosition = this.position;
final Pos position = ignoreView ? previousPosition.withCoord(newPosition) : newPosition;
if (position.equals(lastSyncedPosition)) return;
this.position = position;
this.previousPosition = previousPosition;
if (!position.samePoint(previousPosition)) {
refreshCoordinate(position);
// Update player velocity
if (PlayerUtils.isSocketClient(this)) {
// Calculate from client
this.velocity = position.sub(previousPosition).asVec().mul(MinecraftServer.TICK_PER_SECOND);
}
}
// Update viewers
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;
final Chunk chunk = getChunk();
if (distanceX > 8 || distanceY > 8 || distanceZ > 8) {
PacketUtils.prepareViewablePacket(chunk, new EntityTeleportPacket(getEntityId(), position, isOnGround()), this);
this.lastAbsoluteSynchronizationTime = System.currentTimeMillis();
} else if (positionChange && viewChange) {
PacketUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
lastSyncedPosition, isOnGround()), this);
// Fix head rotation
PacketUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), this);
} else if (positionChange) {
PacketUtils.prepareViewablePacket(chunk, EntityPositionPacket.getPacket(getEntityId(), position, lastSyncedPosition, onGround), this);
} else if (viewChange) {
PacketUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), this);
PacketUtils.prepareViewablePacket(chunk, new EntityRotationPacket(getEntityId(), position.yaw(), position.pitch(), onGround), this);
}
this.lastSyncedPosition = position;
}
@ApiStatus.Internal
public void refreshPosition(@NotNull final Pos newPosition) {
refreshPosition(newPosition, false);
}
/**
* @return The height offset for passengers of this vehicle
*/
private double getPassengerHeightOffset() {
// TODO: Move this logic elsewhere
if (entityType == EntityType.BOAT) {
return -0.1;
} else if (entityType == EntityType.MINECART) {
return 0.0;
} else {
return entityType.height() * 0.75;
}
}
/**
* Sets the X,Z coordinate of the passenger to the X,Z coordinate of this vehicle
* and sets the Y coordinate of the passenger to the Y coordinate of this vehicle + {@link #getPassengerHeightOffset()}
*
* @param newPosition The X,Y,Z position of this vehicle
* @param passenger The passenger to be moved
*/
private void updatePassengerPosition(Point newPosition, Entity passenger) {
final Pos oldPassengerPos = passenger.position;
final Pos newPassengerPos = oldPassengerPos.withCoord(newPosition.x(),
newPosition.y() + getPassengerHeightOffset(),
newPosition.z());
passenger.position = newPassengerPos;
passenger.previousPosition = oldPassengerPos;
passenger.refreshCoordinate(newPassengerPos);
}
/**
* 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(Pos)} instead.
*
* @param newPosition the new position
*/
private void refreshCoordinate(Point newPosition) {
// Passengers update
final Set
* 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
*/
public @NotNull List<@NotNull TimedPotion> getActiveEffects() {
return Collections.unmodifiableList(effects);
}
/**
* Adds an effect to an entity.
*
* @param potion The potion to add
*/
public void addEffect(@NotNull Potion potion) {
removeEffect(potion.effect());
this.effects.add(new TimedPotion(potion, System.currentTimeMillis()));
potion.sendAddPacket(this);
EventDispatcher.call(new EntityPotionAddEvent(this, potion));
}
/**
* 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().effect() == effect) {
timedPotion.getPotion().sendRemovePacket(this);
EventDispatcher.call(new EntityPotionRemoveEvent(this, timedPotion.getPotion()));
return true;
}
return false;
});
}
/**
* Removes all the effects currently applied to the entity.
*/
public void clearEffects() {
for (TimedPotion timedPotion : effects) {
timedPotion.getPotion().sendRemovePacket(this);
EventDispatcher.call(new EntityPotionRemoveEvent(this, timedPotion.getPotion()));
}
this.effects.clear();
}
/**
* Removes the entity from the server immediately.
*
* WARNING: this does not trigger {@link EntityDeathEvent}.
*/
public void remove() {
if (isRemoved()) return;
// Remove passengers if any (also done with LivingEntity#kill)
Setsin(attacker.yaw * (pi/180))
* @param z knockback on z axle, for default knockback use the following formula -cos(attacker.yaw * (pi/180))
*/
public void takeKnockback(final float strength, final double x, final double z) {
if (strength > 0) {
//TODO check possible side effects of unnatural TPS (other than 20TPS)
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()
));
}
}
/**
* 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