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;
|
2021-03-11 18:07:04 +01:00
|
|
|
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;
|
2022-03-06 07:29:51 +01:00
|
|
|
import net.minestom.server.ServerProcess;
|
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;
|
2023-06-27 23:40:57 +02:00
|
|
|
import net.minestom.server.collision.*;
|
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;
|
2022-05-03 22:29:38 +02:00
|
|
|
import net.minestom.server.entity.metadata.LivingEntityMeta;
|
2021-06-04 03:48:51 +02:00
|
|
|
import net.minestom.server.event.EventDispatcher;
|
2022-03-05 17:01:10 +01:00
|
|
|
import net.minestom.server.event.EventFilter;
|
|
|
|
import net.minestom.server.event.EventHandler;
|
|
|
|
import net.minestom.server.event.EventNode;
|
2021-01-03 00:23:41 +01:00
|
|
|
import net.minestom.server.event.entity.*;
|
2021-11-01 18:04:00 +01:00
|
|
|
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
|
|
|
|
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
|
2022-03-05 17:01:10 +01:00
|
|
|
import net.minestom.server.event.trait.EntityEvent;
|
2020-08-15 13:32:36 +02:00
|
|
|
import net.minestom.server.instance.Chunk;
|
2021-11-01 18:04:00 +01:00
|
|
|
import net.minestom.server.instance.EntityTracker;
|
2020-08-15 13:32:36 +02:00
|
|
|
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;
|
2023-06-27 23:40:57 +02:00
|
|
|
import net.minestom.server.instance.block.BlockFace;
|
2021-05-29 00:55:24 +02:00
|
|
|
import net.minestom.server.instance.block.BlockHandler;
|
2021-11-17 00:48:43 +01:00
|
|
|
import net.minestom.server.network.packet.server.CachedPacket;
|
2021-12-22 08:06:00 +01:00
|
|
|
import net.minestom.server.network.packet.server.LazyPacket;
|
2021-09-22 19:12:48 +02:00
|
|
|
import net.minestom.server.network.packet.server.ServerPacket;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.network.packet.server.play.*;
|
2020-11-14 07:09:36 +01:00
|
|
|
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;
|
2020-12-31 01:47:43 +01:00
|
|
|
import net.minestom.server.potion.TimedPotion;
|
2022-03-03 07:44:57 +01:00
|
|
|
import net.minestom.server.snapshot.EntitySnapshot;
|
2022-05-04 13:25:24 +02:00
|
|
|
import net.minestom.server.snapshot.SnapshotImpl;
|
2022-03-03 07:44:57 +01:00
|
|
|
import net.minestom.server.snapshot.SnapshotUpdater;
|
|
|
|
import net.minestom.server.snapshot.Snapshotable;
|
2021-06-22 02:56:00 +02:00
|
|
|
import net.minestom.server.tag.TagHandler;
|
2022-03-20 01:47:57 +01:00
|
|
|
import net.minestom.server.tag.Taggable;
|
2022-01-17 14:24:12 +01:00
|
|
|
import net.minestom.server.thread.Acquirable;
|
2021-12-16 00:15:55 +01:00
|
|
|
import net.minestom.server.timer.Schedulable;
|
|
|
|
import net.minestom.server.timer.Scheduler;
|
2021-12-17 00:05:39 +01:00
|
|
|
import net.minestom.server.timer.TaskSchedule;
|
2022-03-03 07:44:57 +01:00
|
|
|
import net.minestom.server.utils.ArrayUtils;
|
2021-08-25 09:01:13 +02:00
|
|
|
import net.minestom.server.utils.PacketUtils;
|
2021-08-17 21:58:15 +02:00
|
|
|
import net.minestom.server.utils.async.AsyncUtils;
|
2021-08-22 15:56:34 +02:00
|
|
|
import net.minestom.server.utils.block.BlockIterator;
|
2022-03-13 23:28:31 +01:00
|
|
|
import net.minestom.server.utils.chunk.ChunkCache;
|
2020-05-25 13:46:48 +02:00
|
|
|
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;
|
2021-04-23 10:17:42 +02:00
|
|
|
import org.jetbrains.annotations.ApiStatus;
|
2020-10-24 10:46:23 +02:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
2022-11-27 14:47:59 +01:00
|
|
|
import org.jetbrains.annotations.UnknownNullability;
|
2021-12-16 05:29:11 +01:00
|
|
|
import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
|
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-12-16 00:15:55 +01:00
|
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
|
|
import java.util.concurrent.CopyOnWriteArraySet;
|
2019-08-12 08:30:59 +02:00
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
2019-09-02 06:02:12 +02:00
|
|
|
import java.util.function.Consumer;
|
2021-08-22 15:56:34 +02:00
|
|
|
import java.util.function.Predicate;
|
2021-03-11 18:07:04 +01:00
|
|
|
import java.util.function.UnaryOperator;
|
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
|
|
|
*/
|
2022-04-17 06:19:14 +02:00
|
|
|
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler<EntityEvent>, Taggable,
|
2023-06-27 23:40:57 +02:00
|
|
|
PermissionHandler, HoverEventSource<ShowEntity>, Sound.Emitter, Shape {
|
2019-08-10 08:44:35 +02:00
|
|
|
|
2022-10-07 17:19:29 +02:00
|
|
|
private static final int VELOCITY_UPDATE_INTERVAL = 1;
|
|
|
|
|
2021-12-16 05:29:11 +01:00
|
|
|
private static final Int2ObjectSyncMap<Entity> ENTITY_BY_ID = Int2ObjectSyncMap.hashmap();
|
2021-03-26 21:26:35 +01:00
|
|
|
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
|
|
|
|
2021-11-07 17:29:14 +01:00
|
|
|
private final CachedPacket destroyPacketCache = new CachedPacket(() -> new DestroyEntitiesPacket(getEntityId()));
|
|
|
|
|
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;
|
2021-08-20 16:18:20 +02:00
|
|
|
protected Pos previousPosition;
|
2021-07-06 20:44:24 +02:00
|
|
|
protected Pos lastSyncedPosition;
|
2020-09-23 22:01:47 +02:00
|
|
|
protected boolean onGround;
|
2019-08-20 17:41:07 +02:00
|
|
|
|
2019-08-30 01:17:46 +02:00
|
|
|
private BoundingBox boundingBox;
|
2022-03-09 19:08:42 +01:00
|
|
|
private PhysicsResult lastPhysicsResult = null;
|
2019-08-30 01:17:46 +02:00
|
|
|
|
2019-08-20 17:41:07 +02:00
|
|
|
protected Entity vehicle;
|
2020-08-17 16:50:23 +02:00
|
|
|
|
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
|
2022-10-07 17:19:29 +02:00
|
|
|
protected boolean lastVelocityWasZero = true;
|
2021-02-22 12:42:46 +01:00
|
|
|
protected boolean hasPhysics = true;
|
2023-06-27 23:40:57 +02:00
|
|
|
protected boolean hasCollision = true;
|
2020-08-17 16:50:23 +02:00
|
|
|
|
2021-06-27 22:48:58 +02:00
|
|
|
/**
|
|
|
|
* The amount of drag applied on the Y axle.
|
|
|
|
* <p>
|
|
|
|
* Unit: 1/tick
|
|
|
|
*/
|
2021-01-25 19:33:53 +01:00
|
|
|
protected double gravityDragPerTick;
|
2021-06-27 22:48:58 +02:00
|
|
|
/**
|
|
|
|
* Acceleration on the Y axle due to gravity
|
|
|
|
* <p>
|
|
|
|
* Unit: blocks/tick
|
|
|
|
*/
|
2021-01-25 19:33:53 +01:00
|
|
|
protected double gravityAcceleration;
|
2020-12-11 20:17:33 +01:00
|
|
|
protected int gravityTickCount; // Number of tick where gravity tick was applied
|
2019-08-27 20:49:11 +02:00
|
|
|
|
2020-08-16 19:18:34 +02:00
|
|
|
private final int id;
|
2021-11-01 18:04:00 +01:00
|
|
|
// 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> trackingTarget = this instanceof Player ?
|
|
|
|
EntityTracker.Target.ENTITIES : EntityTracker.Target.class.cast(EntityTracker.Target.PLAYERS);
|
|
|
|
protected final EntityTracker.Update<Entity> trackingUpdate = new EntityTracker.Update<>() {
|
|
|
|
@Override
|
|
|
|
public void add(@NotNull Entity entity) {
|
|
|
|
viewEngine.handleAutoViewAddition(entity);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void remove(@NotNull Entity entity) {
|
|
|
|
viewEngine.handleAutoViewRemoval(entity);
|
|
|
|
}
|
2022-01-14 21:34:20 +01:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void referenceUpdate(@NotNull Point point, @Nullable EntityTracker tracker) {
|
2022-02-13 07:51:47 +01:00
|
|
|
final Instance currentInstance = tracker != null ? instance : null;
|
|
|
|
assert currentInstance == null || currentInstance.getEntityTracker() == tracker :
|
|
|
|
"EntityTracker does not match current instance";
|
|
|
|
viewEngine.updateTracker(currentInstance, point);
|
2022-01-14 21:34:20 +01:00
|
|
|
}
|
2021-11-01 18:04:00 +01:00
|
|
|
};
|
|
|
|
|
2022-02-26 00:10:26 +01:00
|
|
|
protected final EntityView viewEngine = new EntityView(this);
|
|
|
|
protected final Set<Player> viewers = viewEngine.set;
|
2022-03-20 01:47:57 +01:00
|
|
|
private final TagHandler tagHandler = TagHandler.newHandler();
|
2021-12-16 00:15:55 +01:00
|
|
|
private final Scheduler scheduler = Scheduler.newScheduler();
|
2022-03-06 07:29:51 +01:00
|
|
|
private final EventNode<EntityEvent> eventNode;
|
2020-12-10 02:56:56 +01:00
|
|
|
private final Set<Permission> permissions = new CopyOnWriteArraySet<>();
|
2019-08-20 17:41:07 +02:00
|
|
|
|
2019-08-11 03:40:34 +02:00
|
|
|
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
|
2020-08-09 08:16:54 +02:00
|
|
|
private boolean removed;
|
2020-08-17 16:50:23 +02:00
|
|
|
|
2020-08-16 19:18:34 +02:00
|
|
|
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
|
2020-08-17 16:50:23 +02:00
|
|
|
|
2020-11-18 05:13:49 +01:00
|
|
|
// Network synchronization, send the absolute position of the entity each X milliseconds
|
2021-06-30 13:05:52 +02:00
|
|
|
private static final Duration SYNCHRONIZATION_COOLDOWN = Duration.of(1, TimeUnit.MINUTE);
|
2021-06-30 00:59:26 +02:00
|
|
|
private Duration customSynchronizationCooldown;
|
2020-11-18 05:13:49 +01:00
|
|
|
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-05-07 16:00:52 +02:00
|
|
|
|
2020-12-31 12:05:36 +01:00
|
|
|
private final List<TimedPotion> effects = new CopyOnWriteArrayList<>();
|
2020-12-31 01:29:07 +01:00
|
|
|
|
2020-05-07 16:00:52 +02:00
|
|
|
// 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
|
|
|
|
2021-02-25 11:56:10 +01:00
|
|
|
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;
|
2021-08-20 16:18:20 +02:00
|
|
|
this.previousPosition = Pos.ZERO;
|
2021-07-06 20:44:24 +02:00
|
|
|
this.lastSyncedPosition = Pos.ZERO;
|
2021-02-25 11:56:10 +01:00
|
|
|
|
2021-07-30 15:08:06 +02:00
|
|
|
this.entityMeta = EntityTypeImpl.createMeta(entityType, this, this.metadata);
|
2021-02-25 11:56:10 +01:00
|
|
|
|
2022-05-03 22:29:38 +02:00
|
|
|
setBoundingBox(entityType.registry().boundingBox());
|
|
|
|
|
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
|
|
|
|
2021-11-10 16:45:46 +01:00
|
|
|
this.gravityAcceleration = entityType.registry().acceleration();
|
|
|
|
this.gravityDragPerTick = entityType.registry().drag();
|
2022-03-06 07:29:51 +01:00
|
|
|
|
|
|
|
final ServerProcess process = MinecraftServer.process();
|
|
|
|
if (process != null) {
|
|
|
|
this.eventNode = process.eventHandler().map(this, EventFilter.ENTITY);
|
|
|
|
} else {
|
|
|
|
// Local nodes require a server process
|
|
|
|
this.eventNode = null;
|
|
|
|
}
|
2021-02-25 11:56:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public Entity(@NotNull EntityType entityType) {
|
|
|
|
this(entityType, UUID.randomUUID());
|
|
|
|
}
|
|
|
|
|
2020-10-12 04:14:06 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Schedules a task to be run during the next entity tick.
|
2020-10-12 04:14:06 +02:00
|
|
|
*
|
|
|
|
* @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) {
|
2021-12-16 00:15:55 +01:00
|
|
|
this.scheduler.scheduleNextTick(() -> callback.accept(this));
|
2020-06-28 23:11:40 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
2020-11-14 23:24:16 +01: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-12-16 05:29:11 +01:00
|
|
|
return Entity.ENTITY_BY_ID.get(id);
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-11-27 13:29:38 +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-06-01 00:51:31 +02:00
|
|
|
*
|
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() {
|
2020-05-02 23:34:09 +02:00
|
|
|
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
|
2020-11-22 13:56:36 +01:00
|
|
|
* @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
|
|
|
*/
|
2021-08-17 21:58:15 +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-11-01 18:04:00 +01:00
|
|
|
this.previousPosition = this.position;
|
2021-09-22 01:13:05 +02:00
|
|
|
this.position = position;
|
|
|
|
refreshCoordinate(position);
|
2021-05-15 21:07:42 +02:00
|
|
|
synchronizePosition(true);
|
2023-05-21 18:21:24 +02:00
|
|
|
setView(position.yaw(), position.pitch());
|
2019-08-26 00:29:40 +02:00
|
|
|
};
|
|
|
|
|
2021-08-17 21:58:15 +02:00
|
|
|
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 {
|
2021-08-17 21:58:15 +02:00
|
|
|
// 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-05-27 23:13:13 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Changes the view of the entity.
|
2020-05-27 23:13:13 +02:00
|
|
|
*
|
|
|
|
* @param yaw the new yaw
|
|
|
|
* @param pitch the new pitch
|
|
|
|
*/
|
|
|
|
public void setView(float yaw, float pitch) {
|
2022-04-19 21:09:02 +02:00
|
|
|
final Pos currentPosition = this.position;
|
|
|
|
if (currentPosition.sameView(yaw, pitch)) return;
|
|
|
|
this.position = currentPosition.withView(yaw, pitch);
|
2023-07-22 21:10:56 +02:00
|
|
|
synchronizeView();
|
2020-05-27 23:13:13 +02:00
|
|
|
}
|
|
|
|
|
2021-10-17 21:07:59 +02:00
|
|
|
/**
|
2022-04-10 14:13:13 +02:00
|
|
|
* Changes the view of the entity so that it looks in a direction to the given position if
|
|
|
|
* it is different from the entity's current position.
|
2021-10-17 21:07:59 +02:00
|
|
|
*
|
2022-05-12 23:35:46 +02:00
|
|
|
* @param point the point to look at.
|
2021-10-17 21:07:59 +02:00
|
|
|
*/
|
2022-05-12 23:35:46 +02:00
|
|
|
public void lookAt(@NotNull Point point) {
|
|
|
|
final Pos newPosition = this.position.add(0, getEyeHeight(), 0).withLookAt(point);
|
2022-04-19 21:09:02 +02:00
|
|
|
setView(newPosition.yaw(), newPosition.pitch());
|
2021-10-17 21:07:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the view of the entity so that it looks in a direction to the given entity.
|
|
|
|
*
|
|
|
|
* @param entity the entity to look at.
|
2022-11-27 23:01:50 +01:00
|
|
|
* @throws IllegalArgumentException if the entities are not in the same instance
|
2021-10-17 21:07:59 +02:00
|
|
|
*/
|
|
|
|
public void lookAt(@NotNull Entity entity) {
|
2022-11-27 23:01:50 +01:00
|
|
|
Check.argCondition(entity.instance != instance, "Entity cannot look at an entity in another instance");
|
2022-05-12 23:35:46 +02:00
|
|
|
lookAt(entity.position.withY(entity.position.y() + entity.getEyeHeight()));
|
2021-10-17 21:07:59 +02:00
|
|
|
}
|
|
|
|
|
2020-05-23 17:57:56 +02:00
|
|
|
/**
|
2021-11-01 18:04:00 +01:00
|
|
|
* Gets if this entity is automatically sent to surrounding players.
|
|
|
|
* True by default.
|
2020-05-23 17:57:56 +02:00
|
|
|
*
|
|
|
|
* @return true if the entity is automatically viewable for close players, false otherwise
|
|
|
|
*/
|
|
|
|
public boolean isAutoViewable() {
|
2021-11-01 18:04:00 +01:00
|
|
|
return viewEngine.viewableOption.isAuto();
|
2020-05-23 17:57:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-11-01 18:04:00 +01:00
|
|
|
* Decides if this entity should be auto-viewable by nearby players.
|
2020-10-24 10:46:23 +02:00
|
|
|
*
|
2021-11-01 18:04:00 +01:00
|
|
|
* @param autoViewable true to add surrounding players, false to remove
|
2020-10-24 10:46:23 +02:00
|
|
|
* @see #isAutoViewable()
|
2020-05-23 17:57:56 +02:00
|
|
|
*/
|
|
|
|
public void setAutoViewable(boolean autoViewable) {
|
2021-11-01 18:04:00 +01:00
|
|
|
this.viewEngine.viewableOption.updateAuto(autoViewable);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ApiStatus.Experimental
|
2022-03-03 16:32:52 +01:00
|
|
|
public void updateViewableRule(@Nullable Predicate<Player> predicate) {
|
2021-11-01 18:04:00 +01:00
|
|
|
this.viewEngine.viewableOption.updateRule(predicate);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ApiStatus.Experimental
|
|
|
|
public void updateViewableRule() {
|
|
|
|
this.viewEngine.viewableOption.updateRule();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets if surrounding entities are automatically visible by this.
|
|
|
|
* True by default.
|
|
|
|
*
|
|
|
|
* @return true if surrounding entities are visible by this
|
|
|
|
*/
|
|
|
|
@ApiStatus.Experimental
|
|
|
|
public boolean autoViewEntities() {
|
|
|
|
return viewEngine.viewerOption.isAuto();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decides if surrounding entities must be visible.
|
|
|
|
*
|
|
|
|
* @param autoViewer true to add view surrounding entities, false to remove
|
|
|
|
*/
|
|
|
|
@ApiStatus.Experimental
|
|
|
|
public void setAutoViewEntities(boolean autoViewer) {
|
|
|
|
this.viewEngine.viewerOption.updateAuto(autoViewer);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ApiStatus.Experimental
|
2022-03-03 16:32:52 +01:00
|
|
|
public void updateViewerRule(@Nullable Predicate<Entity> predicate) {
|
2021-11-01 18:04:00 +01:00
|
|
|
this.viewEngine.viewerOption.updateRule(predicate);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ApiStatus.Experimental
|
|
|
|
public void updateViewerRule() {
|
|
|
|
this.viewEngine.viewerOption.updateRule();
|
2020-05-23 17:57:56 +02:00
|
|
|
}
|
|
|
|
|
2019-08-20 17:41:07 +02:00
|
|
|
@Override
|
2021-02-25 08:37:02 +01:00
|
|
|
public final boolean addViewer(@NotNull Player player) {
|
2021-11-01 18:04:00 +01:00
|
|
|
if (!viewEngine.manualAdd(player)) return false;
|
|
|
|
updateNewViewer(player);
|
|
|
|
return true;
|
2021-02-25 08:37:02 +01:00
|
|
|
}
|
|
|
|
|
2021-11-01 18:04:00 +01:00
|
|
|
@Override
|
|
|
|
public final boolean removeViewer(@NotNull Player player) {
|
|
|
|
if (!viewEngine.manualRemove(player)) return false;
|
|
|
|
updateOldViewer(player);
|
|
|
|
return true;
|
|
|
|
}
|
2021-02-25 08:37:02 +01:00
|
|
|
|
2021-11-01 18:04:00 +01:00
|
|
|
/**
|
|
|
|
* Called when a new viewer must be shown.
|
|
|
|
* Method can be subject to deadlocking if the target's viewers are also accessed.
|
|
|
|
*
|
|
|
|
* @param player the player to send the packets to
|
|
|
|
*/
|
|
|
|
@ApiStatus.Internal
|
|
|
|
public void updateNewViewer(@NotNull Player player) {
|
|
|
|
player.sendPacket(getEntityType().registry().spawnType().getSpawnPacket(this));
|
|
|
|
if (hasVelocity()) player.sendPacket(getVelocityPacket());
|
2021-12-22 08:06:00 +01:00
|
|
|
player.sendPacket(new LazyPacket(this::getMetadataPacket));
|
2021-11-01 18:04:00 +01:00
|
|
|
// Passengers
|
2021-09-20 22:28:09 +02:00
|
|
|
final Set<Entity> passengers = this.passengers;
|
|
|
|
if (!passengers.isEmpty()) {
|
|
|
|
for (Entity passenger : passengers) {
|
2022-02-21 19:05:11 +01:00
|
|
|
if (passenger != player) passenger.updateNewViewer(player);
|
2021-09-20 22:28:09 +02:00
|
|
|
}
|
2021-11-01 18:04:00 +01:00
|
|
|
player.sendPacket(getPassengersPacket());
|
2021-02-25 08:37:02 +01:00
|
|
|
}
|
2021-03-22 14:54:52 +01:00
|
|
|
// Head position
|
2021-11-01 18:04:00 +01:00
|
|
|
player.sendPacket(new EntityHeadLookPacket(getEntityId(), position.yaw()));
|
2021-02-25 08:37:02 +01:00
|
|
|
}
|
|
|
|
|
2021-11-01 18:04:00 +01:00
|
|
|
/**
|
|
|
|
* Called when a viewer must be destroyed.
|
|
|
|
* Method can be subject to deadlocking if the target's viewers are also accessed.
|
|
|
|
*
|
|
|
|
* @param player the player to send the packets to
|
|
|
|
*/
|
|
|
|
@ApiStatus.Internal
|
|
|
|
public void updateOldViewer(@NotNull Player player) {
|
|
|
|
final Set<Entity> passengers = this.passengers;
|
|
|
|
if (!passengers.isEmpty()) {
|
|
|
|
for (Entity passenger : passengers) {
|
2022-02-21 19:05:11 +01:00
|
|
|
if (passenger != player) passenger.updateOldViewer(player);
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
2021-02-25 08:37:02 +01:00
|
|
|
}
|
2021-11-17 06:31:24 +01:00
|
|
|
player.sendPacket(destroyPacketCache);
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-11-01 18:04:00 +01:00
|
|
|
public @NotNull Set<Player> getViewers() {
|
|
|
|
return viewers;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets if this entity's viewers (surrounding players) can be predicted from surrounding chunks.
|
|
|
|
*/
|
|
|
|
public boolean hasPredictableViewers() {
|
|
|
|
return viewEngine.hasPredictableViewers();
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
|
|
|
|
2021-02-25 08:37:02 +01:00
|
|
|
/**
|
|
|
|
* 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>
|
2022-03-09 19:08:42 +01:00
|
|
|
* Be aware that this only change the visual of the entity, the {@link BoundingBox}
|
2021-02-25 08:37:02 +01:00
|
|
|
* will not be modified.
|
|
|
|
*
|
|
|
|
* @param entityType the new entity type
|
|
|
|
*/
|
2021-09-20 18:42:31 +02:00
|
|
|
public synchronized void switchEntityType(@NotNull EntityType entityType) {
|
|
|
|
this.entityType = entityType;
|
|
|
|
this.metadata = new Metadata(this);
|
|
|
|
this.entityMeta = EntityTypeImpl.createMeta(entityType, this, this.metadata);
|
|
|
|
|
|
|
|
Set<Player> viewers = new HashSet<>(getViewers());
|
2021-11-01 18:04:00 +01:00
|
|
|
getViewers().forEach(this::updateOldViewer);
|
|
|
|
viewers.forEach(this::updateNewViewer);
|
2021-02-25 08:37:02 +01:00
|
|
|
}
|
|
|
|
|
2020-11-14 07:09:36 +01:00
|
|
|
@NotNull
|
|
|
|
@Override
|
2020-12-10 02:56:56 +01:00
|
|
|
public Set<Permission> getAllPermissions() {
|
2020-11-14 07:09:36 +01:00
|
|
|
return permissions;
|
|
|
|
}
|
|
|
|
|
2020-05-29 23:17:14 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Updates the entity, called every tick.
|
2020-10-31 01:38:57 +01:00
|
|
|
* <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) {
|
2021-12-17 00:05:39 +01:00
|
|
|
if (instance == null || isRemoved() || !ChunkUtils.isLoaded(currentChunk))
|
2019-08-23 23:55:09 +02:00
|
|
|
return;
|
|
|
|
|
2020-10-12 04:14:06 +02:00
|
|
|
// scheduled tasks
|
2021-12-16 00:15:55 +01:00
|
|
|
this.scheduler.processTick();
|
|
|
|
if (isRemoved()) return;
|
2020-10-12 04:14:06 +02:00
|
|
|
|
2020-11-26 12:46:49 +01:00
|
|
|
// Entity tick
|
2020-12-10 18:12:05 +01:00
|
|
|
{
|
2021-01-04 03:04:45 +01:00
|
|
|
// Cache the number of "gravity tick"
|
2021-07-30 18:10:35 +02:00
|
|
|
velocityTick();
|
2019-08-27 20:49:11 +02:00
|
|
|
|
2020-05-05 15:55:21 +02:00
|
|
|
// handle block contacts
|
2021-08-22 07:31:47 +02:00
|
|
|
touchTick();
|
2020-05-05 15:55:21 +02:00
|
|
|
|
2020-04-27 20:33:08 +02:00
|
|
|
handleVoid();
|
|
|
|
|
2020-03-29 20:58:30 +02:00
|
|
|
// Call the abstract update method
|
2020-06-01 00:51:31 +02:00
|
|
|
update(time);
|
2019-08-24 20:34:01 +02:00
|
|
|
|
2020-05-07 16:00:52 +02:00
|
|
|
ticks++;
|
2022-02-13 12:34:27 +01:00
|
|
|
EventDispatcher.call(new EntityTickEvent(this));
|
2021-01-03 00:23:41 +01:00
|
|
|
|
|
|
|
// remove expired effects
|
2021-08-22 07:34:11 +02:00
|
|
|
effectTick(time);
|
2020-05-31 00:39:56 +02:00
|
|
|
}
|
|
|
|
// Scheduled synchronization
|
2021-03-31 19:17:37 +02:00
|
|
|
if (!Cooldown.hasCooldown(time, lastAbsoluteSynchronizationTime, getSynchronizationCooldown())) {
|
2021-05-15 21:07:42 +02:00
|
|
|
synchronizePosition(false);
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-30 18:10:35 +02:00
|
|
|
private void velocityTick() {
|
2021-09-11 05:54:26 +02:00
|
|
|
this.gravityTickCount = onGround ? 0 : gravityTickCount + 1;
|
2021-09-21 10:37:36 +02:00
|
|
|
if (vehicle != null) return;
|
|
|
|
|
2021-07-30 18:10:35 +02:00
|
|
|
final boolean noGravity = hasNoGravity();
|
|
|
|
final boolean hasVelocity = hasVelocity();
|
2021-08-20 16:18:20 +02:00
|
|
|
if (!hasVelocity && noGravity) {
|
2021-07-30 18:10:35 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
final float tps = MinecraftServer.TICK_PER_SECOND;
|
2022-04-23 21:37:27 +02:00
|
|
|
final Pos positionBeforeMove = getPosition();
|
2021-07-30 18:10:35 +02:00
|
|
|
final Vec currentVelocity = getVelocity();
|
2022-04-23 21:37:27 +02:00
|
|
|
final boolean wasOnGround = this.onGround;
|
|
|
|
final Vec deltaPos = currentVelocity.div(tps);
|
2021-07-30 18:10:35 +02:00
|
|
|
|
|
|
|
final Pos newPosition;
|
|
|
|
final Vec newVelocity;
|
|
|
|
if (this.hasPhysics) {
|
2022-03-09 19:08:42 +01:00
|
|
|
final var physicsResult = CollisionUtils.handlePhysics(this, deltaPos, lastPhysicsResult);
|
|
|
|
this.lastPhysicsResult = physicsResult;
|
2022-04-23 21:37:27 +02:00
|
|
|
if (!PlayerUtils.isSocketClient(this))
|
|
|
|
this.onGround = physicsResult.isOnGround();
|
|
|
|
|
2021-07-30 18:10:35 +02:00
|
|
|
newPosition = physicsResult.newPosition();
|
|
|
|
newVelocity = physicsResult.newVelocity();
|
|
|
|
} else {
|
|
|
|
newVelocity = deltaPos;
|
|
|
|
newPosition = position.add(currentVelocity.div(20));
|
|
|
|
}
|
|
|
|
|
|
|
|
// World border collision
|
2022-03-28 22:06:25 +02:00
|
|
|
final Pos finalVelocityPosition = CollisionUtils.applyWorldBorder(instance, position, newPosition);
|
|
|
|
final boolean positionChanged = !finalVelocityPosition.samePoint(position);
|
2022-06-04 20:38:31 +02:00
|
|
|
final boolean isPlayer = this instanceof Player;
|
|
|
|
final boolean flying = isPlayer && ((Player) this).isFlying();
|
2022-03-28 22:06:25 +02:00
|
|
|
if (!positionChanged) {
|
2022-05-22 16:46:54 +02:00
|
|
|
if (flying) {
|
|
|
|
this.velocity = Vec.ZERO;
|
|
|
|
return;
|
|
|
|
} else if (hasVelocity || newVelocity.isZero()) {
|
2022-04-23 21:37:27 +02:00
|
|
|
this.velocity = noGravity ? Vec.ZERO : new Vec(
|
|
|
|
0,
|
|
|
|
-gravityAcceleration * tps * (1 - gravityDragPerTick),
|
|
|
|
0
|
|
|
|
);
|
2022-10-07 17:19:29 +02:00
|
|
|
if (this.ticks % VELOCITY_UPDATE_INTERVAL == 0) {
|
|
|
|
if (!isPlayer && !this.lastVelocityWasZero) {
|
|
|
|
sendPacketToViewers(getVelocityPacket());
|
|
|
|
this.lastVelocityWasZero = !hasVelocity;
|
|
|
|
}
|
|
|
|
}
|
2022-03-28 22:06:25 +02:00
|
|
|
return;
|
2021-07-30 18:10:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
|
|
2022-03-28 22:06:25 +02:00
|
|
|
if (positionChanged) {
|
|
|
|
if (entityType == EntityTypes.ITEM || entityType == EntityType.FALLING_BLOCK) {
|
|
|
|
// TODO find other exceptions
|
|
|
|
this.previousPosition = this.position;
|
|
|
|
this.position = finalVelocityPosition;
|
|
|
|
refreshCoordinate(finalVelocityPosition);
|
|
|
|
} else {
|
2022-04-23 21:37:27 +02:00
|
|
|
if (!PlayerUtils.isSocketClient(this))
|
|
|
|
refreshPosition(finalVelocityPosition, true);
|
2022-03-28 22:06:25 +02:00
|
|
|
}
|
2021-08-13 08:01:48 +02:00
|
|
|
}
|
2021-07-30 18:10:35 +02:00
|
|
|
|
|
|
|
// Update velocity
|
2023-06-27 23:40:57 +02:00
|
|
|
if (!noGravity && (hasVelocity || !newVelocity.isZero())) {
|
2022-05-22 16:46:54 +02:00
|
|
|
updateVelocity(wasOnGround, flying, positionBeforeMove, newVelocity);
|
2021-07-30 18:10:35 +02:00
|
|
|
}
|
2023-06-27 23:40:57 +02:00
|
|
|
|
2021-07-30 18:10:35 +02:00
|
|
|
// Verify if velocity packet has to be sent
|
2022-10-07 17:19:29 +02:00
|
|
|
if (this.ticks % VELOCITY_UPDATE_INTERVAL == 0) {
|
|
|
|
if (!isPlayer && (hasVelocity || !lastVelocityWasZero)) {
|
|
|
|
sendPacketToViewers(getVelocityPacket());
|
|
|
|
this.lastVelocityWasZero = !hasVelocity;
|
|
|
|
}
|
2021-07-30 18:10:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-22 16:46:54 +02:00
|
|
|
protected void updateVelocity(boolean wasOnGround, boolean flying, Pos positionBeforeMove, Vec newVelocity) {
|
2022-04-23 21:37:27 +02:00
|
|
|
EntitySpawnType type = entityType.registry().spawnType();
|
|
|
|
final double airDrag = type == EntitySpawnType.LIVING || type == EntitySpawnType.PLAYER ? 0.91 : 0.98;
|
|
|
|
final double drag;
|
|
|
|
if (wasOnGround) {
|
|
|
|
final Chunk chunk = ChunkUtils.retrieve(instance, currentChunk, position);
|
|
|
|
synchronized (chunk) {
|
|
|
|
drag = chunk.getBlock(positionBeforeMove.sub(0, 0.5000001, 0)).registry().friction() * airDrag;
|
|
|
|
}
|
|
|
|
} else drag = airDrag;
|
|
|
|
|
2022-05-22 16:46:54 +02:00
|
|
|
double gravity = flying ? 0 : gravityAcceleration;
|
|
|
|
double gravityDrag = flying ? 0.6 : (1 - gravityDragPerTick);
|
|
|
|
|
2022-04-23 21:37:27 +02:00
|
|
|
this.velocity = newVelocity
|
2022-05-22 16:46:54 +02:00
|
|
|
// Apply gravity and drag
|
2022-04-23 21:37:27 +02:00
|
|
|
.apply((x, y, z) -> new Vec(
|
|
|
|
x * drag,
|
2022-05-22 16:46:54 +02:00
|
|
|
!hasNoGravity() ? (y - gravity) * gravityDrag : y,
|
2022-04-23 21:37:27 +02:00
|
|
|
z * drag
|
|
|
|
))
|
|
|
|
// Convert from block/tick to block/sec
|
|
|
|
.mul(MinecraftServer.TICK_PER_SECOND)
|
|
|
|
// Prevent infinitely decreasing velocity
|
|
|
|
.apply(Vec.Operator.EPSILON);
|
|
|
|
}
|
|
|
|
|
2021-08-22 07:31:47 +02:00
|
|
|
private void touchTick() {
|
2023-05-21 18:21:24 +02:00
|
|
|
if (!hasPhysics) return;
|
|
|
|
|
2021-08-22 07:31:47 +02:00
|
|
|
// TODO do not call every tick (it is pretty expensive)
|
2022-03-09 19:08:42 +01:00
|
|
|
final Pos position = this.position;
|
2022-03-13 17:55:00 +01:00
|
|
|
final BoundingBox boundingBox = this.boundingBox;
|
2022-03-13 23:28:31 +01:00
|
|
|
ChunkCache cache = new ChunkCache(instance, currentChunk);
|
2022-03-13 17:55:00 +01:00
|
|
|
|
2022-03-09 19:08:42 +01:00
|
|
|
final int minX = (int) Math.floor(boundingBox.minX() + position.x());
|
|
|
|
final int maxX = (int) Math.ceil(boundingBox.maxX() + position.x());
|
|
|
|
final int minY = (int) Math.floor(boundingBox.minY() + position.y());
|
|
|
|
final int maxY = (int) Math.ceil(boundingBox.maxY() + position.y());
|
|
|
|
final int minZ = (int) Math.floor(boundingBox.minZ() + position.z());
|
|
|
|
final int maxZ = (int) Math.ceil(boundingBox.maxZ() + position.z());
|
|
|
|
|
2021-08-22 07:31:47 +02:00
|
|
|
for (int y = minY; y <= maxY; y++) {
|
|
|
|
for (int x = minX; x <= maxX; x++) {
|
|
|
|
for (int z = minZ; z <= maxZ; z++) {
|
2022-03-13 23:28:31 +01:00
|
|
|
final Block block = cache.getBlock(x, y, z, Block.Getter.Condition.CACHED);
|
2022-03-13 17:55:00 +01:00
|
|
|
if (block == null) continue;
|
2021-08-22 07:31:47 +02:00
|
|
|
final BlockHandler handler = block.handler();
|
|
|
|
if (handler != null) {
|
2022-03-09 19:08:42 +01:00
|
|
|
// Move a small amount towards the entity. If the entity is within 0.01 blocks of the block, touch will trigger
|
2022-03-21 00:04:54 +01:00
|
|
|
Vec blockPos = new Vec(x, y, z);
|
|
|
|
Point blockEntityVector = (blockPos.sub(position)).normalize().mul(0.01);
|
|
|
|
if (block.registry().collisionShape().intersectBox(position.sub(blockPos).add(blockEntityVector), boundingBox)) {
|
2021-08-22 07:31:47 +02:00
|
|
|
handler.onTouch(new BlockHandler.Touch(block, instance, new Vec(x, y, z), this));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-22 07:34:11 +02:00
|
|
|
private void effectTick(long time) {
|
|
|
|
final List<TimedPotion> effects = this.effects;
|
|
|
|
if (effects.isEmpty()) return;
|
|
|
|
effects.removeIf(timedPotion -> {
|
2021-11-30 17:49:41 +01:00
|
|
|
final long potionTime = (long) timedPotion.getPotion().duration() * MinecraftServer.TICK_MS;
|
2021-08-22 07:34:11 +02:00
|
|
|
// 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-05-07 16:00:52 +02:00
|
|
|
/**
|
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
|
2020-05-07 16:00:52 +02:00
|
|
|
*/
|
|
|
|
public long getAliveTicks() {
|
|
|
|
return ticks;
|
|
|
|
}
|
|
|
|
|
2020-04-27 20:33:08 +02:00
|
|
|
/**
|
|
|
|
* 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)) {
|
2020-04-27 20:33:08 +02:00
|
|
|
remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
2020-11-14 23:24:16 +01: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
|
2020-11-15 08:03:33 +01:00
|
|
|
* @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-09-11 05:31:35 +02:00
|
|
|
public @NotNull 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
|
|
|
*/
|
2021-09-11 05:31:35 +02:00
|
|
|
public @NotNull UUID getUuid() {
|
2019-08-10 08:44:35 +02:00
|
|
|
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
|
|
|
|
*/
|
2021-01-06 19:02:35 +01:00
|
|
|
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);
|
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
|
|
|
/**
|
2022-05-03 22:29:38 +02:00
|
|
|
* Returns the current bounding box (based on pose).
|
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
|
|
|
|
*/
|
2021-09-11 05:31:35 +02:00
|
|
|
public @NotNull BoundingBox getBoundingBox() {
|
2022-05-03 22:29:38 +02:00
|
|
|
// Check if there is a specific bounding box for this pose
|
|
|
|
BoundingBox poseBoundingBox = BoundingBox.fromPose(getPose());
|
|
|
|
return poseBoundingBox == null ? boundingBox : poseBoundingBox;
|
2019-08-30 01:17:46 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 23:17:14 +02:00
|
|
|
/**
|
2022-05-03 22:29:38 +02:00
|
|
|
* Changes the internal entity standing bounding box.
|
|
|
|
* When the pose is not standing, a different bounding box may be used for collision.
|
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
|
|
|
*
|
2022-03-09 19:08:42 +01:00
|
|
|
* @param width the bounding box X size
|
|
|
|
* @param height the bounding box Y size
|
|
|
|
* @param depth the bounding box Z size
|
2020-05-29 23:17:14 +02:00
|
|
|
*/
|
2022-03-09 19:08:42 +01:00
|
|
|
public void setBoundingBox(double width, double height, double depth) {
|
2022-05-03 22:29:38 +02:00
|
|
|
setBoundingBox(new BoundingBox(width, height, depth));
|
2019-08-30 01:17:46 +02:00
|
|
|
}
|
2021-01-23 08:23:24 +01:00
|
|
|
|
2021-01-13 12:57:55 +01:00
|
|
|
/**
|
2022-05-03 22:29:38 +02:00
|
|
|
* Changes the internal entity standing bounding box.
|
|
|
|
* When the pose is not standing, a different bounding box may be used for collision.
|
2021-01-13 12:57:55 +01:00
|
|
|
* <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
|
|
|
}
|
|
|
|
|
2021-04-23 10:17:42 +02:00
|
|
|
@ApiStatus.Internal
|
|
|
|
protected void refreshCurrentChunk(Chunk currentChunk) {
|
|
|
|
this.currentChunk = currentChunk;
|
2022-01-05 09:01:21 +01:00
|
|
|
MinecraftServer.process().dispatcher().updateElement(this, currentChunk);
|
2021-04-23 10:17:42 +02:00
|
|
|
}
|
|
|
|
|
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
|
|
|
*
|
2020-10-31 01:02:54 +01: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
|
|
|
*/
|
2022-11-27 14:47:59 +01:00
|
|
|
public @UnknownNullability Instance getInstance() {
|
2019-08-11 07:42:56 +02:00
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
2020-05-29 23:17:14 +02:00
|
|
|
/**
|
2021-02-25 11:56:10 +01: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
|
2021-02-25 11:56:10 +01:00
|
|
|
* @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,
|
2021-07-11 21:57:04 +02:00
|
|
|
* 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");
|
2021-08-15 00:52:07 +02:00
|
|
|
final Instance previousInstance = this.instance;
|
2021-11-01 18:04:00 +01:00
|
|
|
if (Objects.equals(previousInstance, instance)) {
|
|
|
|
return teleport(spawnPosition); // Already in the instance, teleport to spawn point
|
2019-08-10 08:44:35 +02:00
|
|
|
}
|
2021-11-01 18:04:00 +01:00
|
|
|
AddEntityToInstanceEvent event = new AddEntityToInstanceEvent(instance, this);
|
|
|
|
EventDispatcher.call(event);
|
|
|
|
if (event.isCancelled()) return null; // TODO what to return?
|
|
|
|
|
|
|
|
if (previousInstance != null) removeFromInstance(previousInstance);
|
|
|
|
|
|
|
|
this.isActive = true;
|
2021-07-06 20:44:24 +02:00
|
|
|
this.position = spawnPosition;
|
2021-08-20 16:18:20 +02:00
|
|
|
this.previousPosition = spawnPosition;
|
2019-08-11 07:42:56 +02:00
|
|
|
this.instance = instance;
|
2021-11-01 18:04:00 +01:00
|
|
|
return instance.loadOptionalChunk(spawnPosition).thenAccept(chunk -> {
|
|
|
|
try {
|
|
|
|
Check.notNull(chunk, "Entity has been placed in an unloaded chunk!");
|
|
|
|
refreshCurrentChunk(chunk);
|
|
|
|
if (this instanceof Player player) {
|
|
|
|
instance.getWorldBorder().init(player);
|
|
|
|
player.sendPacket(instance.createTimePacket());
|
|
|
|
}
|
|
|
|
instance.getEntityTracker().register(this, spawnPosition, trackingTarget, trackingUpdate);
|
|
|
|
spawn();
|
|
|
|
EventDispatcher.call(new EntitySpawnEvent(this, instance));
|
|
|
|
} catch (Exception e) {
|
|
|
|
MinecraftServer.getExceptionManager().handleException(e);
|
|
|
|
}
|
2021-07-11 21:57:04 +02:00
|
|
|
});
|
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
|
|
|
/**
|
2021-02-25 11:56:10 +01: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,
|
2021-07-11 21:57:04 +02:00
|
|
|
* this is due to chunks needing to load
|
2021-02-25 11:56:10 +01:00
|
|
|
* @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);
|
2021-02-25 11:56:10 +01:00
|
|
|
}
|
|
|
|
|
2021-11-01 18:04:00 +01:00
|
|
|
private void removeFromInstance(Instance instance) {
|
|
|
|
EventDispatcher.call(new RemoveEntityFromInstanceEvent(instance, this));
|
2021-12-23 02:25:36 +01:00
|
|
|
instance.getEntityTracker().unregister(this, trackingTarget, trackingUpdate);
|
2021-11-25 21:30:58 +01:00
|
|
|
this.viewEngine.forManuals(this::removeViewer);
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
|
2021-02-25 11:56:10 +01:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Gets the entity current velocity.
|
2020-05-29 23:17:14 +02:00
|
|
|
*
|
|
|
|
* @return the entity current velocity
|
|
|
|
*/
|
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) {
|
2020-05-14 18:57:44 +02:00
|
|
|
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();
|
2020-08-09 05:13:36 +02:00
|
|
|
sendPacketToViewersAndSelf(getVelocityPacket());
|
2020-05-14 18:57:44 +02:00
|
|
|
});
|
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
|
|
|
*
|
2022-04-28 16:06:53 +02:00
|
|
|
* @return true if the entity is moving
|
2020-05-29 23:17:14 +02:00
|
|
|
*/
|
|
|
|
public boolean hasVelocity() {
|
2022-04-28 16:06:53 +02:00
|
|
|
if (isOnGround()) {
|
|
|
|
// if the entity is on the ground and only "moves" downwards, it does not have a velocity.
|
|
|
|
return Double.compare(velocity.x(), 0) != 0 || Double.compare(velocity.z(), 0) != 0 || velocity.y() > 0;
|
|
|
|
} else {
|
|
|
|
// The entity does not have velocity if the velocity is zero
|
|
|
|
return !velocity.isZero();
|
|
|
|
}
|
2020-05-29 23:17:14 +02:00
|
|
|
}
|
|
|
|
|
2020-12-06 01:36:37 +01: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() {
|
2020-12-06 01:36:37 +01:00
|
|
|
return gravityDragPerTick;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the gravity acceleration.
|
|
|
|
*
|
|
|
|
* @return the gravity acceleration in block
|
|
|
|
*/
|
2021-01-25 19:33:53 +01:00
|
|
|
public double getGravityAcceleration() {
|
2020-12-06 01:36:37 +01:00
|
|
|
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
|
|
|
|
*/
|
2020-12-11 20:17:33 +01:00
|
|
|
public int getGravityTickCount() {
|
2020-12-06 01:36:37 +01:00
|
|
|
return gravityTickCount;
|
|
|
|
}
|
|
|
|
|
2020-05-29 23:17:14 +02:00
|
|
|
/**
|
2020-10-16 09:12:31 +02:00
|
|
|
* Changes the gravity of the entity.
|
2020-05-29 23:17:14 +02:00
|
|
|
*
|
2021-07-05 09:10:03 +02:00
|
|
|
* @param gravityDragPerTick the gravity drag per tick in block
|
|
|
|
* @param gravityAcceleration the gravity acceleration in block
|
2020-12-06 01:36:37 +01:00
|
|
|
* @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;
|
2020-12-06 01:36:37 +01:00
|
|
|
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}
|
|
|
|
*/
|
2021-01-25 13:47:13 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2023-05-06 20:31:14 +02:00
|
|
|
public double getDistanceSquared(@NotNull Point point) {
|
|
|
|
return getPosition().distanceSquared(point);
|
|
|
|
}
|
|
|
|
|
2021-02-22 06:46:37 +01: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());
|
2021-02-22 06:46:37 +01:00
|
|
|
}
|
|
|
|
|
2020-05-29 23:17:14 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Gets the entity vehicle or null.
|
2020-05-29 23:17:14 +02:00
|
|
|
*
|
|
|
|
* @return the entity vehicle, or null if there is not any
|
|
|
|
*/
|
2021-09-11 05:31:35 +02:00
|
|
|
public @Nullable Entity getVehicle() {
|
2019-08-27 05:23:25 +02:00
|
|
|
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
|
2021-08-23 03:46:10 +02:00
|
|
|
* @throws IllegalStateException if {@link #getInstance()} returns null or the passenger cannot be added
|
2020-05-29 23:17:14 +02:00
|
|
|
*/
|
2020-10-24 10:46:23 +02:00
|
|
|
public void addPassenger(@NotNull Entity entity) {
|
2021-11-01 18:04:00 +01:00
|
|
|
final Instance currentInstance = this.instance;
|
|
|
|
Check.stateCondition(currentInstance == null, "You need to set an instance using Entity#setInstance");
|
2021-08-23 03:46:10 +02:00
|
|
|
Check.stateCondition(entity == getVehicle(), "Cannot add the entity vehicle as a passenger");
|
|
|
|
final Entity vehicle = entity.getVehicle();
|
2021-11-01 18:04:00 +01:00
|
|
|
if (vehicle != null) vehicle.removePassenger(entity);
|
|
|
|
if (!currentInstance.equals(entity.getInstance()))
|
|
|
|
entity.setInstance(currentInstance, position).join();
|
2019-08-20 17:41:07 +02:00
|
|
|
this.passengers.add(entity);
|
|
|
|
entity.vehicle = this;
|
2019-08-30 01:17:46 +02:00
|
|
|
sendPacketToViewersAndSelf(getPassengersPacket());
|
2021-10-01 16:35:42 +02:00
|
|
|
// Updates the position of the new passenger, and then teleports the passenger
|
|
|
|
updatePassengerPosition(position, entity);
|
|
|
|
entity.synchronizePosition(false);
|
2019-08-27 05:23:25 +02:00
|
|
|
}
|
2019-08-26 00:29:40 +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");
|
2021-08-28 11:37:42 +02:00
|
|
|
if (!passengers.remove(entity)) return;
|
2019-08-27 05:23:25 +02:00
|
|
|
entity.vehicle = null;
|
2019-08-30 01:17:46 +02:00
|
|
|
sendPacketToViewersAndSelf(getPassengersPacket());
|
2021-09-20 22:43:54 +02:00
|
|
|
entity.synchronizePosition(false);
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
*/
|
2019-08-20 17:41:07 +02:00
|
|
|
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
|
|
|
|
*/
|
2021-09-11 05:31:35 +02:00
|
|
|
public @NotNull Set<@NotNull Entity> getPassengers() {
|
2019-08-20 17:41:07 +02:00
|
|
|
return Collections.unmodifiableSet(passengers);
|
|
|
|
}
|
|
|
|
|
2021-09-11 05:31:35 +02:00
|
|
|
protected @NotNull SetPassengersPacket getPassengersPacket() {
|
2021-11-30 17:49:41 +01:00
|
|
|
return new SetPassengersPacket(getEntityId(), passengers.stream().map(Entity::getEntityId).toList());
|
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) {
|
2021-02-23 17:18:53 +01:00
|
|
|
this.entityMeta.setSneaking(sneaking);
|
2022-05-03 22:29:38 +02:00
|
|
|
updatePose();
|
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
|
|
|
|
*/
|
2021-09-11 05:31:35 +02:00
|
|
|
public @NotNull 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
|
|
|
}
|
|
|
|
|
2022-05-03 22:29:38 +02:00
|
|
|
protected void updatePose() {
|
|
|
|
if (entityMeta.isFlyingWithElytra()) {
|
|
|
|
setPose(Pose.FALL_FLYING);
|
|
|
|
} else if (entityMeta.isSwimming()) {
|
|
|
|
setPose(Pose.SWIMMING);
|
|
|
|
} else if (this instanceof LivingEntity && ((LivingEntityMeta) entityMeta).isInRiptideSpinAttack()) {
|
|
|
|
setPose(Pose.SPIN_ATTACK);
|
|
|
|
} else if (entityMeta.isSneaking()) {
|
|
|
|
setPose(Pose.SNEAKING);
|
|
|
|
} else {
|
|
|
|
setPose(Pose.STANDING);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
*
|
2021-08-13 06:04:44 +02:00
|
|
|
* @param newPosition the new position
|
2021-07-06 20:44:24 +02:00
|
|
|
*/
|
|
|
|
@ApiStatus.Internal
|
2021-08-13 06:04:44 +02:00
|
|
|
public void refreshPosition(@NotNull final Pos newPosition, boolean ignoreView) {
|
2021-07-13 18:21:32 +02:00
|
|
|
final var previousPosition = this.position;
|
2021-08-13 06:04:44 +02:00
|
|
|
final Pos position = ignoreView ? previousPosition.withCoord(newPosition) : newPosition;
|
2021-08-13 05:59:52 +02:00
|
|
|
if (position.equals(lastSyncedPosition)) return;
|
2021-08-13 06:04:44 +02:00
|
|
|
this.position = position;
|
2021-11-01 18:04:00 +01:00
|
|
|
this.previousPosition = previousPosition;
|
2022-04-23 21:37:27 +02:00
|
|
|
if (!position.samePoint(previousPosition)) refreshCoordinate(position);
|
2021-11-01 18:04:00 +01:00
|
|
|
// Update viewers
|
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;
|
2021-08-25 09:01:13 +02:00
|
|
|
|
|
|
|
final Chunk chunk = getChunk();
|
2021-07-20 03:06:27 +02:00
|
|
|
if (distanceX > 8 || distanceY > 8 || distanceZ > 8) {
|
2021-10-15 06:17:48 +02:00
|
|
|
PacketUtils.prepareViewablePacket(chunk, new EntityTeleportPacket(getEntityId(), position, isOnGround()), this);
|
2021-10-27 16:02:33 +02:00
|
|
|
this.lastAbsoluteSynchronizationTime = System.currentTimeMillis();
|
2021-07-20 03:06:27 +02:00
|
|
|
} else if (positionChange && viewChange) {
|
2021-08-25 09:01:13 +02:00
|
|
|
PacketUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
|
2021-10-15 06:17:48 +02:00
|
|
|
lastSyncedPosition, isOnGround()), this);
|
2021-07-20 03:06:27 +02:00
|
|
|
// Fix head rotation
|
2021-10-15 06:17:48 +02:00
|
|
|
PacketUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), this);
|
2021-07-20 03:06:27 +02:00
|
|
|
} else if (positionChange) {
|
2022-09-05 13:54:58 +02:00
|
|
|
// This is a confusing fix for a confusing issue. If rotation is only sent when the entity actually changes, then spawning an entity
|
|
|
|
// on the ground causes the entity not to update its rotation correctly. It works fine if the entity is spawned in the air. Very weird.
|
|
|
|
PacketUtils.prepareViewablePacket(chunk, EntityPositionAndRotationPacket.getPacket(getEntityId(), position,
|
|
|
|
lastSyncedPosition, onGround), this);
|
2021-07-20 03:06:27 +02:00
|
|
|
} else if (viewChange) {
|
2021-10-15 06:17:48 +02:00
|
|
|
PacketUtils.prepareViewablePacket(chunk, new EntityHeadLookPacket(getEntityId(), position.yaw()), this);
|
|
|
|
PacketUtils.prepareViewablePacket(chunk, new EntityRotationPacket(getEntityId(), position.yaw(), position.pitch(), onGround), this);
|
2021-07-20 03:06:27 +02:00
|
|
|
}
|
|
|
|
this.lastSyncedPosition = position;
|
2021-07-06 20:44:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@ApiStatus.Internal
|
2021-08-13 06:04:44 +02:00
|
|
|
public void refreshPosition(@NotNull final Pos newPosition) {
|
|
|
|
refreshPosition(newPosition, false);
|
2021-07-06 20:44:24 +02:00
|
|
|
}
|
|
|
|
|
2021-10-01 16:35:42 +02:00
|
|
|
/**
|
|
|
|
* @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()}
|
2021-10-15 06:17:48 +02:00
|
|
|
*
|
2021-10-01 16:35:42 +02:00
|
|
|
* @param newPosition The X,Y,Z position of this vehicle
|
2021-10-15 06:17:48 +02:00
|
|
|
* @param passenger The passenger to be moved
|
2021-10-01 16:35:42 +02:00
|
|
|
*/
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
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) {
|
2021-11-01 18:04:00 +01:00
|
|
|
// Passengers update
|
|
|
|
final Set<Entity> passengers = getPassengers();
|
|
|
|
if (!passengers.isEmpty()) {
|
|
|
|
for (Entity passenger : passengers) {
|
2021-10-01 16:35:42 +02:00
|
|
|
updatePassengerPosition(newPosition, passenger);
|
2020-05-23 14:04:53 +02:00
|
|
|
}
|
2019-08-30 01:17:46 +02:00
|
|
|
}
|
2021-11-01 18:04:00 +01:00
|
|
|
// Handle chunk switch
|
2020-07-22 17:39:48 +02:00
|
|
|
final Instance instance = getInstance();
|
2021-11-01 18:04:00 +01:00
|
|
|
assert instance != null;
|
2021-12-23 02:25:36 +01:00
|
|
|
instance.getEntityTracker().move(this, newPosition, trackingTarget, trackingUpdate);
|
2021-11-01 18:04:00 +01:00
|
|
|
final int lastChunkX = currentChunk.getChunkX();
|
|
|
|
final int lastChunkZ = currentChunk.getChunkZ();
|
|
|
|
final int newChunkX = newPosition.chunkX();
|
|
|
|
final int newChunkZ = newPosition.chunkZ();
|
|
|
|
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}", getEntityId(), newPosition);
|
2022-06-04 22:04:57 +02:00
|
|
|
if (this instanceof Player player) player.sendChunkUpdates(newChunk);
|
2021-11-01 18:04:00 +01:00
|
|
|
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.
|
2020-12-29 20:42:41 +01:00
|
|
|
* <p>
|
2022-03-09 19:08:42 +01:00
|
|
|
* Default to {@link BoundingBox#height()}x0.85
|
2020-05-29 23:17:14 +02:00
|
|
|
*
|
|
|
|
* @return the entity eye height
|
|
|
|
*/
|
2021-01-25 13:47:13 +01:00
|
|
|
public double getEyeHeight() {
|
2022-05-03 22:29:38 +02:00
|
|
|
return getPose() == Pose.SLEEPING ? 0.2 : (boundingBox.height() * 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
|
|
|
|
*/
|
2021-09-11 05:31:35 +02:00
|
|
|
public @NotNull List<@NotNull TimedPotion> getActiveEffects() {
|
2021-01-03 00:23:41 +01:00
|
|
|
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) {
|
2021-11-30 17:49:41 +01:00
|
|
|
removeEffect(potion.effect());
|
2021-02-22 06:00:49 +01:00
|
|
|
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 -> {
|
2021-11-30 17:49:41 +01:00
|
|
|
if (timedPotion.getPotion().effect() == effect) {
|
2021-01-03 00:23:41 +01:00
|
|
|
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)
|
2021-11-01 18:04:00 +01:00
|
|
|
Set<Entity> passengers = getPassengers();
|
|
|
|
if (!passengers.isEmpty()) passengers.forEach(this::removePassenger);
|
|
|
|
final Entity vehicle = this.vehicle;
|
|
|
|
if (vehicle != null) vehicle.removePassenger(this);
|
2022-01-05 09:01:21 +01:00
|
|
|
MinecraftServer.process().dispatcher().removeElement(this);
|
2020-08-09 08:16:54 +02:00
|
|
|
this.removed = true;
|
2021-03-26 21:26:35 +01:00
|
|
|
Entity.ENTITY_BY_ID.remove(id);
|
|
|
|
Entity.ENTITY_BY_UUID.remove(uuid);
|
2021-11-01 18:04:00 +01:00
|
|
|
Instance currentInstance = this.instance;
|
|
|
|
if (currentInstance != null) removeFromInstance(currentInstance);
|
2019-08-10 08:44:35 +02:00
|
|
|
}
|
|
|
|
|
2020-08-09 08:16:54 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Gets if this entity has been removed.
|
2020-08-09 08:16:54 +02:00
|
|
|
*
|
|
|
|
* @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
|
|
|
*
|
2021-07-05 09:10:03 +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) {
|
2022-01-30 08:23:34 +01:00
|
|
|
if (temporalUnit == TimeUnit.SERVER_TICK) {
|
|
|
|
scheduleRemove(TaskSchedule.tick((int) delay));
|
|
|
|
} else {
|
|
|
|
scheduleRemove(Duration.of(delay, temporalUnit));
|
|
|
|
}
|
2021-07-01 15:03:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Triggers {@link #remove()} after the specified time.
|
|
|
|
*
|
2021-12-17 00:05:39 +01:00
|
|
|
* @param delay the time before removing the entity
|
2021-07-01 15:03:16 +02:00
|
|
|
*/
|
|
|
|
public void scheduleRemove(Duration delay) {
|
2022-01-30 08:23:34 +01:00
|
|
|
scheduleRemove(TaskSchedule.duration(delay));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void scheduleRemove(TaskSchedule schedule) {
|
|
|
|
this.scheduler.buildTask(this::remove).delay(schedule).schedule();
|
2019-08-30 01:17:46 +02:00
|
|
|
}
|
|
|
|
|
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:05:49 +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.
|
2021-05-16 23:57:42 +02:00
|
|
|
*
|
2021-05-15 21:07:42 +02:00
|
|
|
* @param includeSelf if {@code true} and this is a {@link Player} an additional {@link PlayerPositionAndLookPacket}
|
|
|
|
* will be sent to the player itself
|
2021-05-01 00:05:49 +02:00
|
|
|
*/
|
|
|
|
@ApiStatus.Internal
|
2021-05-15 21:07:42 +02:00
|
|
|
protected void synchronizePosition(boolean includeSelf) {
|
2021-09-22 19:12:48 +02:00
|
|
|
final Pos posCache = this.position;
|
|
|
|
final ServerPacket packet = new EntityTeleportPacket(getEntityId(), posCache, isOnGround());
|
2021-10-15 06:17:48 +02:00
|
|
|
PacketUtils.prepareViewablePacket(currentChunk, packet, this);
|
2021-05-01 00:05:49 +02:00
|
|
|
this.lastAbsoluteSynchronizationTime = System.currentTimeMillis();
|
2021-09-22 19:12:48 +02:00
|
|
|
this.lastSyncedPosition = posCache;
|
2020-05-24 22:21:38 +02:00
|
|
|
}
|
2019-08-31 12:10:46 +02:00
|
|
|
|
2023-07-22 21:10:56 +02:00
|
|
|
private void synchronizeView() {
|
|
|
|
sendPacketToViewersAndSelf(new EntityHeadLookPacket(getEntityId(), position.yaw()));
|
|
|
|
sendPacketToViewersAndSelf(new EntityRotationPacket(getEntityId(), position.yaw(), position.pitch(), onGround));
|
|
|
|
}
|
|
|
|
|
2020-05-24 22:21:38 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Asks for a synchronization (position) to happen during next entity tick.
|
2020-05-24 22:21:38 +02:00
|
|
|
*/
|
|
|
|
public void askSynchronization() {
|
2020-11-18 05:13:49 +01:00
|
|
|
this.lastAbsoluteSynchronizationTime = 0;
|
2019-08-24 20:34:01 +02:00
|
|
|
}
|
|
|
|
|
2021-03-01 11:09:08 +01: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) {
|
2021-03-01 11:09:08 +01:00
|
|
|
this.customSynchronizationCooldown = cooldown;
|
|
|
|
}
|
|
|
|
|
2021-03-11 18:07:04 +01:00
|
|
|
@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);
|
2021-03-01 11:09:08 +01:00
|
|
|
}
|
|
|
|
|
2021-06-20 21:48:07 +02:00
|
|
|
@ApiStatus.Experimental
|
2021-04-26 12:52:02 +02:00
|
|
|
public <T extends Entity> @NotNull Acquirable<T> getAcquirable() {
|
|
|
|
return (Acquirable<T>) acquirable;
|
|
|
|
}
|
|
|
|
|
2021-06-22 02:56:00 +02:00
|
|
|
@Override
|
2022-03-20 01:47:57 +01:00
|
|
|
public @NotNull TagHandler tagHandler() {
|
|
|
|
return tagHandler;
|
2021-06-22 02:56:00 +02:00
|
|
|
}
|
|
|
|
|
2021-12-16 00:15:55 +01:00
|
|
|
@Override
|
|
|
|
public @NotNull Scheduler scheduler() {
|
|
|
|
return scheduler;
|
|
|
|
}
|
|
|
|
|
2022-03-03 07:44:57 +01:00
|
|
|
@Override
|
|
|
|
public @NotNull EntitySnapshot updateSnapshot(@NotNull SnapshotUpdater updater) {
|
|
|
|
final Chunk chunk = currentChunk;
|
|
|
|
final int[] viewersId = this.viewEngine.viewableOption.bitSet.toIntArray();
|
|
|
|
final int[] passengersId = ArrayUtils.mapToIntArray(passengers, Entity::getEntityId);
|
|
|
|
final Entity vehicle = this.vehicle;
|
2022-05-04 13:25:24 +02:00
|
|
|
return new SnapshotImpl.Entity(entityType, uuid, id, position, velocity,
|
2022-03-03 07:44:57 +01:00
|
|
|
updater.reference(instance), chunk.getChunkX(), chunk.getChunkZ(),
|
|
|
|
viewersId, passengersId, vehicle == null ? -1 : vehicle.getEntityId(),
|
2022-03-20 01:47:57 +01:00
|
|
|
tagHandler.readableCopy());
|
2022-03-03 07:44:57 +01:00
|
|
|
}
|
|
|
|
|
2022-03-05 17:01:10 +01:00
|
|
|
@Override
|
|
|
|
@ApiStatus.Experimental
|
|
|
|
public @NotNull EventNode<EntityEvent> eventNode() {
|
|
|
|
return eventNode;
|
|
|
|
}
|
|
|
|
|
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
|
2021-07-05 09:10:03 +02:00
|
|
|
* @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
|
|
|
*/
|
2022-04-23 21:37:27 +02:00
|
|
|
public void takeKnockback(float strength, final double x, final double z) {
|
2021-06-27 23:05:54 +02:00
|
|
|
if (strength > 0) {
|
2021-06-28 19:23:36 +02:00
|
|
|
//TODO check possible side effects of unnatural TPS (other than 20TPS)
|
2022-04-23 21:37:27 +02:00
|
|
|
strength *= MinecraftServer.TICK_PER_SECOND;
|
|
|
|
final Vec velocityModifier = new Vec(x, z).normalize().mul(strength);
|
|
|
|
final double verticalLimit = .4d * MinecraftServer.TICK_PER_SECOND;
|
|
|
|
|
2021-07-06 20:44:24 +02:00
|
|
|
setVelocity(new Vec(velocity.x() / 2d - velocityModifier.x(),
|
2022-04-23 21:37:27 +02:00
|
|
|
onGround ? Math.min(verticalLimit, velocity.y() / 2d + strength) : velocity.y(),
|
2021-07-06 20:44:24 +02:00
|
|
|
velocity.z() / 2d - velocityModifier.z()
|
|
|
|
));
|
2021-06-27 23:05:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-22 15:56:34 +02:00
|
|
|
/**
|
|
|
|
* Gets the line of sight of the entity.
|
|
|
|
*
|
|
|
|
* @param maxDistance The max distance to scan
|
2022-03-09 19:08:42 +01:00
|
|
|
* @return A list of {@link Point points} in this entities line of sight
|
2021-08-22 15:56:34 +02:00
|
|
|
*/
|
|
|
|
public List<Point> getLineOfSight(int maxDistance) {
|
2021-08-22 16:47:52 +02:00
|
|
|
Instance instance = getInstance();
|
|
|
|
if (instance == null) {
|
2022-09-06 15:08:36 +02:00
|
|
|
return List.of();
|
2021-08-22 16:47:52 +02:00
|
|
|
}
|
|
|
|
|
2021-08-22 15:56:34 +02:00
|
|
|
List<Point> blocks = new ArrayList<>();
|
2021-08-22 16:47:52 +02:00
|
|
|
var it = new BlockIterator(this, maxDistance);
|
2021-08-22 15:56:34 +02:00
|
|
|
while (it.hasNext()) {
|
|
|
|
final Point position = it.next();
|
2021-08-22 16:47:52 +02:00
|
|
|
if (!instance.getBlock(position).isAir()) blocks.add(position);
|
2021-08-22 15:56:34 +02:00
|
|
|
}
|
|
|
|
return blocks;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-31 20:28:02 +02:00
|
|
|
* Raycasts current entity's eye position to target eye position.
|
2021-08-22 15:56:34 +02:00
|
|
|
*
|
2022-04-17 06:19:14 +02:00
|
|
|
* @param entity the entity to be checked.
|
2022-03-31 20:28:02 +02:00
|
|
|
* @param exactView if set to TRUE, checks whether target is IN the line of sight of the current one;
|
|
|
|
* otherwise checks if the current entity can rotate so that target will be in its line of sight.
|
|
|
|
* @return true if the ray reaches the target bounding box before hitting a block.
|
2021-08-22 15:56:34 +02:00
|
|
|
*/
|
2022-03-31 20:28:02 +02:00
|
|
|
public boolean hasLineOfSight(Entity entity, boolean exactView) {
|
2021-08-22 16:47:52 +02:00
|
|
|
Instance instance = getInstance();
|
|
|
|
if (instance == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-03-31 20:28:02 +02:00
|
|
|
final Pos start = position.withY(position.y() + getEyeHeight());
|
|
|
|
final Pos end = entity.position.withY(entity.position.y() + entity.getEyeHeight());
|
|
|
|
final Vec direction = exactView ? position.direction() : end.sub(start).asVec().normalize();
|
|
|
|
if (!entity.boundingBox.boundingBoxRayIntersectionCheck(start.asVec(), direction, entity.getPosition())) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return CollisionUtils.isLineOfSightReachingShape(instance, currentChunk, start, end, entity.boundingBox);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param entity the entity to be checked.
|
|
|
|
* @return if the current entity has line of sight to the given one.
|
2022-04-17 06:19:14 +02:00
|
|
|
* @see Entity#hasLineOfSight(Entity, boolean)
|
2022-03-31 20:28:02 +02:00
|
|
|
*/
|
|
|
|
public boolean hasLineOfSight(Entity entity) {
|
|
|
|
return hasLineOfSight(entity, false);
|
2021-08-22 15:56:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets first entity on the line of sight of the current one that matches the given predicate.
|
|
|
|
*
|
2022-04-17 06:19:14 +02:00
|
|
|
* @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
|
2021-08-22 15:56:34 +02:00
|
|
|
* @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();
|
2021-08-22 15:56:34 +02:00
|
|
|
if (instance == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-03-31 20:28:02 +02:00
|
|
|
final Pos start = position.withY(position.y() + getEyeHeight());
|
|
|
|
final Vec startAsVec = start.asVec();
|
|
|
|
final Predicate<Entity> finalPredicate = e -> e != this
|
|
|
|
&& e.boundingBox.boundingBoxRayIntersectionCheck(startAsVec, position.direction(), e.getPosition())
|
|
|
|
&& predicate.test(e)
|
|
|
|
&& CollisionUtils.isLineOfSightReachingShape(instance, currentChunk, start,
|
2022-04-17 06:19:14 +02:00
|
|
|
e.position.withY(e.position.y() + e.getEyeHeight()), e.boundingBox);
|
2021-08-22 15:56:34 +02:00
|
|
|
|
2022-03-09 19:08:42 +01:00
|
|
|
Optional<Entity> nearby = instance.getNearbyEntities(position, range).stream()
|
2022-03-31 20:28:02 +02:00
|
|
|
.filter(finalPredicate)
|
2023-05-06 20:31:14 +02:00
|
|
|
.min(Comparator.comparingDouble(e -> e.getDistanceSquared(this)));
|
2021-08-22 15:56:34 +02:00
|
|
|
|
2022-03-09 19:08:42 +01:00
|
|
|
return nearby.orElse(null);
|
2021-08-22 15:56:34 +02:00
|
|
|
}
|
|
|
|
|
2023-06-27 23:40:57 +02:00
|
|
|
@Override
|
|
|
|
public boolean isOccluded(@NotNull Shape shape, @NotNull BlockFace face) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean intersectBox(@NotNull Point positionRelative, @NotNull BoundingBox boundingBox) {
|
|
|
|
return boundingBox.intersectBox(positionRelative, boundingBox);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean intersectBoxSwept(@NotNull Point rayStart, @NotNull Point rayDirection, @NotNull Point shapePos, @NotNull BoundingBox moving, @NotNull SweepResult finalResult) {
|
|
|
|
return boundingBox.intersectBoxSwept(rayStart, rayDirection, shapePos, moving, finalResult);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @NotNull Point relativeStart() {
|
|
|
|
return boundingBox.relativeStart();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @NotNull Point relativeEnd() {
|
|
|
|
return boundingBox.relativeEnd();
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean hasCollision() {
|
|
|
|
return hasCollision;
|
|
|
|
}
|
|
|
|
|
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,
|
2022-10-10 23:40:10 +02:00
|
|
|
LONG_JUMPING,
|
|
|
|
DYING,
|
|
|
|
CROAKING,
|
|
|
|
USING_TONGUE,
|
2023-07-28 14:49:11 +02:00
|
|
|
SITTING,
|
2022-10-10 23:40:10 +02:00
|
|
|
ROARING,
|
|
|
|
SNIFFING,
|
|
|
|
EMERGING,
|
|
|
|
DIGGING
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
2019-08-10 08:44:35 +02:00
|
|
|
}
|