2020-04-24 03:25:58 +02:00
|
|
|
package net.minestom.server.entity;
|
2019-08-10 08:44:35 +02:00
|
|
|
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.MinecraftServer;
|
|
|
|
import net.minestom.server.Viewable;
|
|
|
|
import net.minestom.server.collision.BoundingBox;
|
|
|
|
import net.minestom.server.collision.CollisionUtils;
|
|
|
|
import net.minestom.server.data.Data;
|
|
|
|
import net.minestom.server.data.DataContainer;
|
|
|
|
import net.minestom.server.event.Event;
|
2020-04-28 01:20:11 +02:00
|
|
|
import net.minestom.server.event.EventCallback;
|
2020-05-15 18:03:28 +02:00
|
|
|
import net.minestom.server.event.entity.EntityDeathEvent;
|
2020-05-08 06:54:33 +02:00
|
|
|
import net.minestom.server.event.entity.EntitySpawnEvent;
|
2020-05-07 16:00:52 +02:00
|
|
|
import net.minestom.server.event.entity.EntityTickEvent;
|
2020-05-14 18:57:44 +02:00
|
|
|
import net.minestom.server.event.entity.EntityVelocityEvent;
|
2020-05-08 06:54:33 +02:00
|
|
|
import net.minestom.server.event.handler.EventHandler;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.instance.Chunk;
|
|
|
|
import net.minestom.server.instance.Instance;
|
2020-05-05 15:55:21 +02:00
|
|
|
import net.minestom.server.instance.block.CustomBlock;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.network.packet.PacketWriter;
|
|
|
|
import net.minestom.server.network.packet.server.play.*;
|
|
|
|
import net.minestom.server.network.player.PlayerConnection;
|
2020-04-28 01:20:11 +02:00
|
|
|
import net.minestom.server.utils.Vector;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.utils.*;
|
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;
|
2019-08-10 08:44:35 +02:00
|
|
|
|
2020-04-28 01:20:11 +02:00
|
|
|
import java.util.*;
|
2019-08-20 17:41:07 +02:00
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
2020-04-28 01:20:11 +02:00
|
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
2019-08-20 17:41:07 +02:00
|
|
|
import java.util.concurrent.CopyOnWriteArraySet;
|
2019-08-12 08:30:59 +02:00
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
2019-09-02 06:02:12 +02:00
|
|
|
import java.util.function.Consumer;
|
2019-08-10 08:44:35 +02:00
|
|
|
|
2020-05-08 06:54:33 +02:00
|
|
|
public abstract class Entity implements Viewable, EventHandler, DataContainer {
|
2019-08-10 08:44:35 +02:00
|
|
|
|
2019-09-03 07:36:04 +02:00
|
|
|
private static Map<Integer, Entity> entityById = new ConcurrentHashMap<>();
|
2019-08-12 08:30:59 +02:00
|
|
|
private static AtomicInteger lastEntityId = new AtomicInteger();
|
|
|
|
|
2019-08-19 17:04:19 +02:00
|
|
|
// Metadata
|
|
|
|
protected static final byte METADATA_BYTE = 0;
|
|
|
|
protected static final byte METADATA_VARINT = 1;
|
|
|
|
protected static final byte METADATA_FLOAT = 2;
|
|
|
|
protected static final byte METADATA_STRING = 3;
|
|
|
|
protected static final byte METADATA_CHAT = 4;
|
|
|
|
protected static final byte METADATA_OPTCHAT = 5;
|
|
|
|
protected static final byte METADATA_SLOT = 6;
|
|
|
|
protected static final byte METADATA_BOOLEAN = 7;
|
2019-09-10 06:59:15 +02:00
|
|
|
protected static final byte METADATA_POSE = 18;
|
2019-08-19 17:04:19 +02:00
|
|
|
|
2019-08-11 07:42:56 +02:00
|
|
|
protected Instance instance;
|
2019-08-21 16:50:52 +02:00
|
|
|
protected Position position;
|
2019-08-25 20:03:43 +02:00
|
|
|
protected float lastX, lastY, lastZ;
|
2019-08-20 22:40:57 +02:00
|
|
|
protected float lastYaw, lastPitch;
|
2019-08-10 08:44:35 +02:00
|
|
|
private int id;
|
2019-08-20 17:41:07 +02:00
|
|
|
|
2019-08-30 01:17:46 +02:00
|
|
|
private BoundingBox boundingBox;
|
|
|
|
|
2019-08-20 17:41:07 +02:00
|
|
|
protected Entity vehicle;
|
2019-08-25 20:03:43 +02:00
|
|
|
// Velocity
|
|
|
|
protected Vector velocity = new Vector(); // Movement in block per second
|
2019-08-27 20:49:11 +02:00
|
|
|
protected float gravityDragPerTick;
|
2020-05-21 00:33:56 +02:00
|
|
|
protected float eyeHeight;
|
2019-08-27 20:49:11 +02:00
|
|
|
|
2020-05-23 17:57:56 +02:00
|
|
|
private boolean autoViewable;
|
2019-08-20 17:41:07 +02:00
|
|
|
private Set<Player> viewers = new CopyOnWriteArraySet<>();
|
2019-08-23 23:55:09 +02:00
|
|
|
private Data data;
|
2019-08-20 17:41:07 +02:00
|
|
|
private Set<Entity> passengers = new CopyOnWriteArraySet<>();
|
|
|
|
|
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
|
|
|
|
private boolean shouldRemove;
|
2019-08-21 16:50:52 +02:00
|
|
|
private long scheduledRemoveTime;
|
|
|
|
private int entityType;
|
|
|
|
private long lastUpdate;
|
2020-04-28 01:20:11 +02:00
|
|
|
private Map<Class<? extends Event>, List<EventCallback>> eventCallbacks = new ConcurrentHashMap<>();
|
2020-05-02 23:34:09 +02:00
|
|
|
protected long lastVelocityUpdateTime; // Reset velocity to 0 after countdown
|
2019-08-24 20:34:01 +02:00
|
|
|
|
|
|
|
// Synchronization
|
2019-08-27 05:23:25 +02:00
|
|
|
private long synchronizationDelay = 1500; // In ms
|
2019-08-24 20:34:01 +02:00
|
|
|
private long lastSynchronizationTime;
|
|
|
|
|
2019-08-21 16:50:52 +02:00
|
|
|
// Metadata
|
|
|
|
protected boolean onFire;
|
|
|
|
protected boolean crouched;
|
2019-08-19 17:04:19 +02:00
|
|
|
protected boolean UNUSED_METADATA;
|
|
|
|
protected boolean sprinting;
|
|
|
|
protected boolean swimming;
|
|
|
|
protected boolean invisible;
|
|
|
|
protected boolean glowing;
|
|
|
|
protected boolean usingElytra;
|
|
|
|
protected int air = 300;
|
|
|
|
protected String customName = "";
|
|
|
|
protected boolean customNameVisible;
|
|
|
|
protected boolean silent;
|
|
|
|
protected boolean noGravity;
|
|
|
|
protected Pose pose = Pose.STANDING;
|
2020-05-07 16:00:52 +02:00
|
|
|
|
|
|
|
|
2020-05-02 23:34:09 +02:00
|
|
|
private long velocityUpdatePeriod;
|
|
|
|
protected boolean onGround;
|
2019-08-19 17:04:19 +02:00
|
|
|
|
2020-05-07 16:00:52 +02:00
|
|
|
// Tick related
|
|
|
|
private long ticks;
|
|
|
|
private final EntityTickEvent tickEvent = new EntityTickEvent(this);
|
|
|
|
|
2020-04-09 14:25:42 +02:00
|
|
|
public Entity(int entityType, Position spawnPosition) {
|
2019-08-10 08:44:35 +02:00
|
|
|
this.id = generateId();
|
2019-08-19 17:04:19 +02:00
|
|
|
this.entityType = entityType;
|
2019-08-10 08:44:35 +02:00
|
|
|
this.uuid = UUID.randomUUID();
|
2020-04-09 14:25:42 +02:00
|
|
|
this.position = spawnPosition.clone();
|
2019-08-21 16:50:52 +02:00
|
|
|
|
2019-08-30 01:17:46 +02:00
|
|
|
setBoundingBox(0, 0, 0);
|
|
|
|
|
2020-05-23 17:57:56 +02:00
|
|
|
setAutoViewable(true);
|
|
|
|
|
2019-09-03 07:36:04 +02:00
|
|
|
entityById.put(id, this);
|
2020-05-02 23:34:09 +02:00
|
|
|
setVelocityUpdatePeriod(5);
|
2019-08-10 08:44:35 +02:00
|
|
|
}
|
|
|
|
|
2020-03-29 20:58:30 +02:00
|
|
|
public Entity(int entityType) {
|
|
|
|
this(entityType, new Position());
|
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* @param id the entity unique id ({@link #getEntityId()})
|
|
|
|
* @return the entity having the specified id, null if not found
|
|
|
|
*/
|
2019-08-21 16:50:52 +02:00
|
|
|
public static Entity getEntity(int id) {
|
2020-05-15 18:03:28 +02:00
|
|
|
return entityById.getOrDefault(id, null);
|
2019-08-21 16:50:52 +02:00
|
|
|
}
|
2019-08-19 17:04:19 +02:00
|
|
|
|
2019-08-10 08:44:35 +02:00
|
|
|
private static int generateId() {
|
2019-08-12 08:30:59 +02:00
|
|
|
return lastEntityId.incrementAndGet();
|
2019-08-10 08:44:35 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* Called each tick
|
|
|
|
*/
|
2019-08-19 17:04:19 +02:00
|
|
|
public abstract void update();
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* Called when a new instance is set
|
|
|
|
*/
|
2019-08-24 20:34:01 +02:00
|
|
|
public abstract void spawn();
|
|
|
|
|
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 */;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if now is a good time to send a velocity update packet
|
2020-05-05 21:04:05 +02:00
|
|
|
*
|
2020-05-02 23:34:09 +02:00
|
|
|
* @param time
|
2020-05-05 21:04:05 +02:00
|
|
|
* @return
|
2020-05-02 23:34:09 +02:00
|
|
|
*/
|
|
|
|
protected boolean shouldSendVelocityUpdate(long time) {
|
2020-05-05 21:04:05 +02:00
|
|
|
return (time - lastVelocityUpdateTime) >= velocityUpdatePeriod;
|
2020-05-02 23:34:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the period, in ms, between two velocity update packets
|
2020-05-05 21:04:05 +02:00
|
|
|
*
|
2020-05-02 23:34:09 +02:00
|
|
|
* @return period, in ms, between two velocity update packets
|
|
|
|
*/
|
|
|
|
public long getVelocityUpdatePeriod() {
|
|
|
|
return velocityUpdatePeriod;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the period, in ms, between two velocity update packets
|
2020-05-05 21:04:05 +02:00
|
|
|
*
|
2020-05-02 23:34:09 +02:00
|
|
|
* @param velocityUpdatePeriod period, in ms, between two velocity update packets
|
|
|
|
*/
|
|
|
|
public void setVelocityUpdatePeriod(long velocityUpdatePeriod) {
|
|
|
|
this.velocityUpdatePeriod = velocityUpdatePeriod;
|
2020-03-29 20:58:30 +02:00
|
|
|
}
|
2019-08-27 20:49:11 +02:00
|
|
|
|
2019-08-25 20:03:43 +02:00
|
|
|
public void teleport(Position position, Runnable callback) {
|
2020-05-23 04:20:01 +02:00
|
|
|
Check.stateCondition(instance == null, "You need to use Entity#setInstance before teleporting an entity!");
|
2019-08-21 16:50:52 +02:00
|
|
|
|
2019-08-26 00:29:40 +02:00
|
|
|
Runnable runnable = () -> {
|
|
|
|
refreshPosition(position.getX(), position.getY(), position.getZ());
|
|
|
|
refreshView(position.getYaw(), position.getPitch());
|
|
|
|
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
|
|
|
|
entityTeleportPacket.entityId = getEntityId();
|
|
|
|
entityTeleportPacket.position = position;
|
2019-08-27 20:49:11 +02:00
|
|
|
entityTeleportPacket.onGround = isOnGround();
|
2019-08-26 00:29:40 +02:00
|
|
|
sendPacketToViewers(entityTeleportPacket);
|
|
|
|
};
|
|
|
|
|
2019-08-25 20:03:43 +02:00
|
|
|
if (instance.hasEnabledAutoChunkLoad()) {
|
|
|
|
instance.loadChunk(position, chunk -> {
|
2019-08-26 00:29:40 +02:00
|
|
|
runnable.run();
|
2019-08-25 20:03:43 +02:00
|
|
|
if (callback != null)
|
|
|
|
callback.run();
|
|
|
|
});
|
|
|
|
} else {
|
2019-08-27 20:49:11 +02:00
|
|
|
if (ChunkUtils.isChunkUnloaded(instance, position.getX(), position.getZ()))
|
2019-08-25 20:03:43 +02:00
|
|
|
return;
|
2019-08-26 00:29:40 +02:00
|
|
|
runnable.run();
|
2019-08-25 20:03:43 +02:00
|
|
|
if (callback != null)
|
|
|
|
callback.run();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void teleport(Position position) {
|
|
|
|
teleport(position, null);
|
2019-08-21 16:50:52 +02:00
|
|
|
}
|
|
|
|
|
2020-05-23 17:57:56 +02:00
|
|
|
/**
|
|
|
|
* When set to true, the entity will automatically get new viewers when they come too close
|
|
|
|
* This can be use to complete control over which player can see it, without having to deal with
|
|
|
|
* raw packets
|
|
|
|
* <p>
|
|
|
|
* True by default for all entities
|
|
|
|
* When set to false, it is important to mention that the players will not be removed automatically from its viewers
|
|
|
|
* list, you would have to do that manually when being too far
|
|
|
|
*
|
|
|
|
* @return true if the entity is automatically viewable for close players, false otherwise
|
|
|
|
*/
|
|
|
|
public boolean isAutoViewable() {
|
|
|
|
return autoViewable;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param autoViewable should the entity be automatically viewable for close players
|
|
|
|
*/
|
|
|
|
public void setAutoViewable(boolean autoViewable) {
|
|
|
|
this.autoViewable = autoViewable;
|
|
|
|
}
|
|
|
|
|
2019-08-20 17:41:07 +02:00
|
|
|
@Override
|
2020-05-23 17:57:56 +02:00
|
|
|
public boolean addViewer(Player player) {
|
|
|
|
boolean result = this.viewers.add(player);
|
2019-09-01 06:18:41 +02:00
|
|
|
player.viewableEntities.add(this);
|
2019-08-30 01:17:46 +02:00
|
|
|
PlayerConnection playerConnection = player.getPlayerConnection();
|
|
|
|
playerConnection.sendPacket(getVelocityPacket());
|
2019-08-31 07:54:53 +02:00
|
|
|
playerConnection.sendPacket(getPassengersPacket());
|
2020-05-06 22:35:32 +02:00
|
|
|
playerConnection.sendPacket(getMetadataPacket());
|
2020-05-23 17:57:56 +02:00
|
|
|
|
|
|
|
return result;
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-05-23 17:57:56 +02:00
|
|
|
public boolean removeViewer(Player player) {
|
|
|
|
boolean result;
|
2019-08-20 17:41:07 +02:00
|
|
|
synchronized (viewers) {
|
2020-05-23 17:57:56 +02:00
|
|
|
result = viewers.remove(player);
|
|
|
|
if (!result)
|
|
|
|
return false;
|
|
|
|
|
2019-08-20 22:40:57 +02:00
|
|
|
DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket();
|
|
|
|
destroyEntitiesPacket.entityIds = new int[]{getEntityId()};
|
|
|
|
player.getPlayerConnection().sendPacket(destroyEntitiesPacket);
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
2019-09-01 06:18:41 +02:00
|
|
|
player.viewableEntities.remove(this);
|
2020-05-23 17:57:56 +02:00
|
|
|
return result;
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Set<Player> getViewers() {
|
|
|
|
return Collections.unmodifiableSet(viewers);
|
|
|
|
}
|
|
|
|
|
2019-08-23 23:55:09 +02:00
|
|
|
@Override
|
|
|
|
public Data getData() {
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setData(Data data) {
|
|
|
|
this.data = data;
|
|
|
|
}
|
|
|
|
|
2020-02-09 15:34:09 +01:00
|
|
|
public void tick(long time) {
|
2019-08-23 23:55:09 +02:00
|
|
|
if (instance == null)
|
|
|
|
return;
|
|
|
|
|
2019-08-21 16:50:52 +02:00
|
|
|
if (scheduledRemoveTime != 0) { // Any entity with scheduled remove does not update
|
2020-02-09 15:34:09 +01:00
|
|
|
boolean finished = time >= scheduledRemoveTime;
|
2019-08-21 16:50:52 +02:00
|
|
|
if (finished) {
|
|
|
|
remove();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2019-08-23 23:55:09 +02:00
|
|
|
|
|
|
|
if (shouldRemove()) {
|
|
|
|
remove();
|
|
|
|
return;
|
2020-05-09 18:24:14 +02:00
|
|
|
}
|
|
|
|
|
2020-05-17 01:42:07 +02:00
|
|
|
boolean chunkUnloaded = ChunkUtils.isChunkUnloaded(instance, position.getX(), position.getZ());
|
|
|
|
if (chunkUnloaded) {
|
2020-05-09 18:24:14 +02:00
|
|
|
// No update for entities in unloaded chunk
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldUpdate(time)) {
|
2019-08-29 02:15:52 +02:00
|
|
|
this.lastUpdate = time;
|
2019-08-24 20:34:01 +02:00
|
|
|
|
|
|
|
// Velocity
|
2020-05-02 23:34:09 +02:00
|
|
|
if (!(this instanceof Player)) {
|
|
|
|
final float tps = MinecraftServer.TICK_PER_SECOND;
|
|
|
|
float newX = position.getX() + velocity.getX() / tps;
|
|
|
|
float newY = position.getY() + velocity.getY() / tps;
|
|
|
|
float newZ = position.getZ() + velocity.getZ() / tps;
|
|
|
|
|
|
|
|
Position newPosition = new Position(newX, newY, newZ);
|
|
|
|
|
2020-05-17 01:42:07 +02:00
|
|
|
chunkUnloaded = ChunkUtils.isChunkUnloaded(instance, newX, newZ);
|
|
|
|
if (chunkUnloaded)
|
|
|
|
return;
|
|
|
|
|
2020-05-02 23:34:09 +02:00
|
|
|
if (!(this instanceof Player) && !noGravity) { // players handle gravity by themselves
|
2020-05-05 21:04:05 +02:00
|
|
|
velocity.setY(velocity.getY() - gravityDragPerTick * tps);
|
2019-09-14 18:00:18 +02:00
|
|
|
}
|
2020-05-02 23:34:09 +02:00
|
|
|
|
|
|
|
Vector newVelocityOut = new Vector();
|
|
|
|
Vector deltaPos = new Vector(
|
2020-05-05 21:04:05 +02:00
|
|
|
getVelocity().getX() / tps,
|
|
|
|
getVelocity().getY() / tps,
|
|
|
|
getVelocity().getZ() / tps
|
2020-05-02 23:34:09 +02:00
|
|
|
);
|
|
|
|
onGround = CollisionUtils.handlePhysics(this, deltaPos, newPosition, newVelocityOut);
|
|
|
|
|
|
|
|
refreshPosition(newPosition);
|
|
|
|
velocity.copy(newVelocityOut);
|
2020-05-03 15:54:12 +02:00
|
|
|
velocity.multiply(tps);
|
2020-05-02 23:34:09 +02:00
|
|
|
|
|
|
|
float drag;
|
2020-05-05 21:04:05 +02:00
|
|
|
if (onGround) {
|
2020-05-02 23:34:09 +02:00
|
|
|
drag = 0.5f; // ground drag
|
|
|
|
} else {
|
|
|
|
drag = 0.98f; // air drag
|
2019-08-24 20:34:01 +02:00
|
|
|
}
|
2020-05-03 15:54:12 +02:00
|
|
|
velocity.setX(velocity.getX() * drag);
|
|
|
|
velocity.setZ(velocity.getZ() * drag);
|
2020-05-02 23:34:09 +02:00
|
|
|
|
2019-08-24 20:34:01 +02:00
|
|
|
|
2020-05-02 23:34:09 +02:00
|
|
|
sendSynchronization();
|
|
|
|
if (shouldSendVelocityUpdate(time)) {
|
|
|
|
sendPacketToViewers(getVelocityPacket());
|
|
|
|
lastVelocityUpdateTime = time;
|
2019-08-27 20:49:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 15:55:21 +02:00
|
|
|
// handle block contacts
|
|
|
|
int minX = (int) Math.floor(boundingBox.getMinX());
|
|
|
|
int maxX = (int) Math.ceil(boundingBox.getMaxX());
|
|
|
|
int minY = (int) Math.floor(boundingBox.getMinY());
|
|
|
|
int maxY = (int) Math.ceil(boundingBox.getMaxY());
|
|
|
|
int minZ = (int) Math.floor(boundingBox.getMinZ());
|
|
|
|
int maxZ = (int) Math.ceil(boundingBox.getMaxZ());
|
2020-05-05 21:04:05 +02:00
|
|
|
BlockPosition tmpPosition = new BlockPosition(0, 0, 0); // allow reuse
|
2020-05-05 15:55:21 +02:00
|
|
|
for (int y = minY; y <= maxY; y++) {
|
|
|
|
for (int x = minX; x <= maxX; x++) {
|
|
|
|
for (int z = minZ; z <= maxZ; z++) {
|
2020-05-17 01:42:07 +02:00
|
|
|
chunkUnloaded = ChunkUtils.isChunkUnloaded(instance, x, z);
|
|
|
|
if (chunkUnloaded)
|
|
|
|
continue;
|
2020-05-05 15:55:21 +02:00
|
|
|
CustomBlock customBlock = instance.getCustomBlock(x, y, z);
|
2020-05-05 21:04:05 +02:00
|
|
|
if (customBlock != null) {
|
2020-05-05 15:55:21 +02:00
|
|
|
tmpPosition.setX(x);
|
|
|
|
tmpPosition.setY(y);
|
|
|
|
tmpPosition.setZ(z);
|
|
|
|
// checks that we are actually in the block, and not just here because of a rounding error
|
2020-05-05 21:04:05 +02:00
|
|
|
if (boundingBox.intersect(tmpPosition)) {
|
2020-05-05 15:55:21 +02:00
|
|
|
// TODO: replace with check with custom block bounding box
|
|
|
|
customBlock.handleContact(instance, tmpPosition, this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-27 20:33:08 +02:00
|
|
|
handleVoid();
|
|
|
|
|
2020-03-29 20:58:30 +02:00
|
|
|
// Call the abstract update method
|
2019-08-19 17:04:19 +02:00
|
|
|
update();
|
2019-08-24 20:34:01 +02:00
|
|
|
|
2020-05-07 16:00:52 +02:00
|
|
|
ticks++;
|
|
|
|
callEvent(EntityTickEvent.class, tickEvent); // reuse tickEvent to avoid recreating it each tick
|
|
|
|
|
2019-08-26 00:29:40 +02:00
|
|
|
// Scheduled synchronization
|
2019-08-24 20:34:01 +02:00
|
|
|
if (time - lastSynchronizationTime >= synchronizationDelay) {
|
2020-02-09 15:34:09 +01:00
|
|
|
lastSynchronizationTime = time;
|
2019-08-30 01:17:46 +02:00
|
|
|
sendSynchronization();
|
2019-08-24 20:34:01 +02:00
|
|
|
}
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
2019-08-23 23:55:09 +02:00
|
|
|
|
|
|
|
if (shouldRemove()) {
|
|
|
|
remove();
|
|
|
|
}
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
|
|
|
|
2020-05-07 16:00:52 +02:00
|
|
|
/**
|
|
|
|
* Returns the number of ticks this entity has been active for
|
2020-05-08 06:54:33 +02:00
|
|
|
*
|
2020-05-07 16:00:52 +02:00
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
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-08 06:54:33 +02:00
|
|
|
@Override
|
2020-04-28 01:20:11 +02:00
|
|
|
public <E extends Event> void addEventCallback(Class<E> eventClass, EventCallback<E> eventCallback) {
|
|
|
|
List<EventCallback> callbacks = getEventCallbacks(eventClass);
|
|
|
|
callbacks.add(eventCallback);
|
|
|
|
this.eventCallbacks.put(eventClass, callbacks);
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
|
|
|
|
2020-05-08 06:54:33 +02:00
|
|
|
@Override
|
2020-04-28 01:20:11 +02:00
|
|
|
public <E extends Event> List<EventCallback> getEventCallbacks(Class<E> eventClass) {
|
|
|
|
return eventCallbacks.getOrDefault(eventClass, new CopyOnWriteArrayList<>());
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* Each entity has an unique id which will change after a restart
|
|
|
|
* All entities can be retrieved by calling {@link Entity#getEntity(int)}
|
|
|
|
*
|
|
|
|
* @return the unique entity id
|
|
|
|
*/
|
2019-08-10 08:44:35 +02:00
|
|
|
public int getEntityId() {
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2019-08-19 17:04:19 +02:00
|
|
|
public int getEntityType() {
|
|
|
|
return entityType;
|
|
|
|
}
|
|
|
|
|
2019-08-10 08:44:35 +02:00
|
|
|
public UUID getUuid() {
|
|
|
|
return uuid;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isActive() {
|
|
|
|
return isActive;
|
|
|
|
}
|
|
|
|
|
2019-08-30 01:17:46 +02:00
|
|
|
public BoundingBox getBoundingBox() {
|
|
|
|
return boundingBox;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setBoundingBox(float x, float y, float z) {
|
|
|
|
this.boundingBox = new BoundingBox(this, x, y, z);
|
|
|
|
}
|
|
|
|
|
2019-08-11 07:42:56 +02:00
|
|
|
public Instance getInstance() {
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setInstance(Instance instance) {
|
2020-05-23 04:20:01 +02:00
|
|
|
Check.notNull(instance, "instance cannot be null!");
|
|
|
|
Check.stateCondition(!MinecraftServer.getInstanceManager().getInstances().contains(instance),
|
|
|
|
"Instances need to be registered with InstanceManager#createInstanceContainer or InstanceManager#createSharedInstance");
|
2020-05-17 03:15:47 +02:00
|
|
|
|
2019-08-11 07:42:56 +02:00
|
|
|
if (this.instance != null) {
|
|
|
|
this.instance.removeEntity(this);
|
2019-08-10 08:44:35 +02:00
|
|
|
}
|
2019-08-11 07:42:56 +02:00
|
|
|
|
|
|
|
this.isActive = true;
|
|
|
|
this.instance = instance;
|
|
|
|
instance.addEntity(this);
|
2019-08-24 20:34:01 +02:00
|
|
|
spawn();
|
2020-05-05 21:04:05 +02:00
|
|
|
EntitySpawnEvent entitySpawnEvent = new EntitySpawnEvent(instance);
|
|
|
|
callEvent(EntitySpawnEvent.class, entitySpawnEvent);
|
2019-08-24 20:34:01 +02:00
|
|
|
}
|
|
|
|
|
2019-08-25 20:03:43 +02:00
|
|
|
public Vector getVelocity() {
|
|
|
|
return velocity;
|
|
|
|
}
|
|
|
|
|
2020-05-02 23:34:09 +02:00
|
|
|
public void setVelocity(Vector velocity) {
|
2020-05-14 18:57:44 +02:00
|
|
|
EntityVelocityEvent entityVelocityEvent = new EntityVelocityEvent(this, velocity);
|
|
|
|
callCancellableEvent(EntityVelocityEvent.class, entityVelocityEvent, () -> {
|
|
|
|
this.velocity.copy(entityVelocityEvent.getVelocity());
|
2020-05-21 00:36:50 +02:00
|
|
|
sendPacketToViewers(getVelocityPacket());
|
2020-05-14 18:57:44 +02:00
|
|
|
});
|
2019-08-10 08:44:35 +02:00
|
|
|
}
|
|
|
|
|
2019-08-27 20:49:11 +02:00
|
|
|
public void setGravity(float gravityDragPerTick) {
|
|
|
|
this.gravityDragPerTick = gravityDragPerTick;
|
|
|
|
}
|
|
|
|
|
2019-08-19 17:04:19 +02:00
|
|
|
public float getDistance(Entity entity) {
|
2019-08-21 16:50:52 +02:00
|
|
|
return getPosition().getDistance(entity.getPosition());
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
|
|
|
|
2019-08-27 05:23:25 +02:00
|
|
|
public Entity getVehicle() {
|
|
|
|
return vehicle;
|
|
|
|
}
|
|
|
|
|
2019-08-20 17:41:07 +02:00
|
|
|
public void addPassenger(Entity entity) {
|
2020-05-23 04:20:01 +02:00
|
|
|
Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance");
|
|
|
|
|
2019-08-27 05:23:25 +02:00
|
|
|
if (entity.getVehicle() != null) {
|
|
|
|
entity.getVehicle().removePassenger(entity);
|
|
|
|
}
|
|
|
|
|
2019-08-20 17:41:07 +02:00
|
|
|
this.passengers.add(entity);
|
|
|
|
entity.vehicle = this;
|
2019-08-26 00:29:40 +02:00
|
|
|
|
2019-08-30 01:17:46 +02:00
|
|
|
sendPacketToViewersAndSelf(getPassengersPacket());
|
2019-08-27 05:23:25 +02:00
|
|
|
}
|
2019-08-26 00:29:40 +02:00
|
|
|
|
2019-08-27 05:23:25 +02:00
|
|
|
public void removePassenger(Entity entity) {
|
2020-05-23 04:20:01 +02:00
|
|
|
Check.stateCondition(instance == null, "You need to set an instance using Entity#setInstance");
|
|
|
|
|
2019-08-27 05:23:25 +02:00
|
|
|
if (!passengers.contains(entity))
|
|
|
|
return;
|
|
|
|
this.passengers.remove(entity);
|
|
|
|
entity.vehicle = null;
|
2019-08-30 01:17:46 +02:00
|
|
|
sendPacketToViewersAndSelf(getPassengersPacket());
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean hasPassenger() {
|
|
|
|
return !passengers.isEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
public Set<Entity> getPassengers() {
|
|
|
|
return Collections.unmodifiableSet(passengers);
|
|
|
|
}
|
|
|
|
|
2019-08-30 01:17:46 +02:00
|
|
|
protected SetPassengersPacket getPassengersPacket() {
|
2019-08-27 05:23:25 +02:00
|
|
|
SetPassengersPacket passengersPacket = new SetPassengersPacket();
|
|
|
|
passengersPacket.vehicleEntityId = getEntityId();
|
|
|
|
|
|
|
|
int[] passengers = new int[this.passengers.size()];
|
|
|
|
int counter = 0;
|
|
|
|
for (Entity passenger : this.passengers) {
|
|
|
|
passengers[counter++] = passenger.getEntityId();
|
|
|
|
}
|
|
|
|
|
|
|
|
passengersPacket.passengersId = passengers;
|
2019-08-30 01:17:46 +02:00
|
|
|
return passengersPacket;
|
2019-08-27 05:23:25 +02:00
|
|
|
}
|
|
|
|
|
2019-08-21 16:50:52 +02:00
|
|
|
public void triggerStatus(byte status) {
|
|
|
|
EntityStatusPacket statusPacket = new EntityStatusPacket();
|
|
|
|
statusPacket.entityId = getEntityId();
|
|
|
|
statusPacket.status = status;
|
2020-04-20 23:43:09 +02:00
|
|
|
sendPacketToViewersAndSelf(statusPacket);
|
2019-08-21 16:50:52 +02:00
|
|
|
}
|
|
|
|
|
2019-08-20 22:40:57 +02:00
|
|
|
public void setOnFire(boolean fire) {
|
|
|
|
this.onFire = fire;
|
2019-08-23 15:37:38 +02:00
|
|
|
sendMetadataIndex(0);
|
|
|
|
}
|
|
|
|
|
2019-08-27 20:49:11 +02:00
|
|
|
public boolean isOnFire() {
|
|
|
|
return onFire;
|
|
|
|
}
|
|
|
|
|
2019-08-24 20:34:01 +02:00
|
|
|
public void setGlowing(boolean glowing) {
|
|
|
|
this.glowing = glowing;
|
|
|
|
sendMetadataIndex(0);
|
|
|
|
}
|
|
|
|
|
2019-08-27 20:49:11 +02:00
|
|
|
public boolean isGlowing() {
|
|
|
|
return glowing;
|
|
|
|
}
|
|
|
|
|
2019-08-23 15:37:38 +02:00
|
|
|
public void setNoGravity(boolean noGravity) {
|
|
|
|
this.noGravity = noGravity;
|
|
|
|
sendMetadataIndex(5);
|
2019-08-20 22:40:57 +02:00
|
|
|
}
|
|
|
|
|
2019-08-27 20:49:11 +02:00
|
|
|
public boolean hasNoGravity() {
|
|
|
|
return noGravity;
|
2019-08-21 16:50:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void refreshPosition(float x, float y, float z) {
|
|
|
|
this.lastX = position.getX();
|
|
|
|
this.lastY = position.getY();
|
|
|
|
this.lastZ = position.getZ();
|
|
|
|
position.setX(x);
|
|
|
|
position.setY(y);
|
|
|
|
position.setZ(z);
|
2019-08-11 07:42:56 +02:00
|
|
|
|
2020-05-23 14:04:53 +02:00
|
|
|
if (hasPassenger()) {
|
|
|
|
for (Entity passenger : getPassengers()) {
|
|
|
|
passenger.refreshPosition(x, y, z);
|
|
|
|
}
|
2019-08-30 01:17:46 +02:00
|
|
|
}
|
|
|
|
|
2019-08-11 07:42:56 +02:00
|
|
|
Instance instance = getInstance();
|
|
|
|
if (instance != null) {
|
|
|
|
Chunk lastChunk = instance.getChunkAt(lastX, lastZ);
|
|
|
|
Chunk newChunk = instance.getChunkAt(x, z);
|
2019-08-11 08:57:23 +02:00
|
|
|
if (lastChunk != null && newChunk != null && lastChunk != newChunk) {
|
2019-08-24 21:41:43 +02:00
|
|
|
synchronized (instance) {
|
|
|
|
instance.removeEntityFromChunk(this, lastChunk);
|
|
|
|
instance.addEntityToChunk(this, newChunk);
|
2019-08-30 01:17:46 +02:00
|
|
|
}
|
2020-05-23 17:57:56 +02:00
|
|
|
updateView(lastChunk, newChunk);
|
2019-08-11 07:42:56 +02:00
|
|
|
}
|
|
|
|
}
|
2019-08-10 08:44:35 +02:00
|
|
|
}
|
|
|
|
|
2019-08-27 20:49:11 +02:00
|
|
|
public void refreshPosition(Position position) {
|
|
|
|
refreshPosition(position.getX(), position.getY(), position.getZ());
|
|
|
|
}
|
|
|
|
|
2020-05-23 17:57:56 +02:00
|
|
|
private void updateView(Chunk lastChunk, Chunk newChunk) {
|
|
|
|
boolean isPlayer = this instanceof Player;
|
|
|
|
|
|
|
|
if (isPlayer)
|
|
|
|
((Player) this).onChunkChange(lastChunk, newChunk); // Refresh loaded chunk
|
2019-08-30 01:17:46 +02:00
|
|
|
|
|
|
|
// Refresh entity viewable list
|
2020-02-17 17:33:53 +01:00
|
|
|
long[] lastVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), MinecraftServer.ENTITY_VIEW_DISTANCE);
|
|
|
|
long[] updatedVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), MinecraftServer.ENTITY_VIEW_DISTANCE);
|
2019-08-30 01:17:46 +02:00
|
|
|
|
|
|
|
int[] oldChunksEntity = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunksEntity, updatedVisibleChunksEntity);
|
|
|
|
for (int index : oldChunksEntity) {
|
|
|
|
int[] chunkPos = ChunkUtils.getChunkCoord(lastVisibleChunksEntity[index]);
|
|
|
|
Chunk chunk = instance.getChunk(chunkPos[0], chunkPos[1]);
|
|
|
|
if (chunk == null)
|
|
|
|
continue;
|
|
|
|
instance.getChunkEntities(chunk).forEach(ent -> {
|
|
|
|
if (ent instanceof Player) {
|
|
|
|
Player player = (Player) ent;
|
2020-05-23 17:57:56 +02:00
|
|
|
if (isAutoViewable())
|
|
|
|
removeViewer(player);
|
2019-08-30 01:17:46 +02:00
|
|
|
if (isPlayer) {
|
2020-05-23 17:57:56 +02:00
|
|
|
player.removeViewer((Player) this);
|
2019-08-30 01:17:46 +02:00
|
|
|
}
|
|
|
|
} else if (isPlayer) {
|
2020-05-23 17:57:56 +02:00
|
|
|
ent.removeViewer((Player) this);
|
2019-08-30 01:17:46 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
int[] newChunksEntity = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunksEntity, lastVisibleChunksEntity);
|
|
|
|
for (int index : newChunksEntity) {
|
|
|
|
int[] chunkPos = ChunkUtils.getChunkCoord(updatedVisibleChunksEntity[index]);
|
|
|
|
Chunk chunk = instance.getChunk(chunkPos[0], chunkPos[1]);
|
|
|
|
if (chunk == null)
|
|
|
|
continue;
|
|
|
|
instance.getChunkEntities(chunk).forEach(ent -> {
|
|
|
|
if (ent instanceof Player) {
|
|
|
|
Player player = (Player) ent;
|
2020-05-23 17:57:56 +02:00
|
|
|
if (isAutoViewable())
|
|
|
|
addViewer(player);
|
|
|
|
if (this instanceof Player) {
|
|
|
|
player.addViewer((Player) this);
|
2019-08-30 01:17:46 +02:00
|
|
|
}
|
|
|
|
} else if (isPlayer) {
|
2020-05-23 17:57:56 +02:00
|
|
|
ent.addViewer((Player) this);
|
2019-08-30 01:17:46 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-20 22:40:57 +02:00
|
|
|
public void refreshView(float yaw, float pitch) {
|
2019-08-21 16:50:52 +02:00
|
|
|
this.lastYaw = position.getYaw();
|
|
|
|
this.lastPitch = position.getPitch();
|
|
|
|
position.setYaw(yaw);
|
|
|
|
position.setPitch(pitch);
|
2019-08-20 22:40:57 +02:00
|
|
|
}
|
|
|
|
|
2020-05-23 14:04:53 +02:00
|
|
|
/**
|
|
|
|
* Ask for a synchronization (position) to happen during next entity update
|
|
|
|
*/
|
|
|
|
public void askSynchronization() {
|
|
|
|
this.lastSynchronizationTime = 0;
|
|
|
|
}
|
|
|
|
|
2019-08-20 22:40:57 +02:00
|
|
|
public void refreshSneaking(boolean sneaking) {
|
|
|
|
this.crouched = sneaking;
|
2019-09-10 06:59:15 +02:00
|
|
|
this.pose = sneaking ? Pose.SNEAKING : Pose.STANDING;
|
2019-08-23 15:37:38 +02:00
|
|
|
sendMetadataIndex(0);
|
2019-09-10 06:59:15 +02:00
|
|
|
sendMetadataIndex(6);
|
2019-08-20 22:40:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void refreshSprinting(boolean sprinting) {
|
|
|
|
this.sprinting = sprinting;
|
2019-08-23 15:37:38 +02:00
|
|
|
sendMetadataIndex(0);
|
2019-08-20 22:40:57 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* @return the current position of the entity
|
|
|
|
*/
|
2019-08-21 16:50:52 +02:00
|
|
|
public Position getPosition() {
|
|
|
|
return position;
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
|
|
|
|
2020-05-21 00:33:56 +02:00
|
|
|
public float getEyeHeight() {
|
|
|
|
return eyeHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setEyeHeight(float eyeHeight) {
|
|
|
|
this.eyeHeight = eyeHeight;
|
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* @param position the checked position chunk
|
|
|
|
* @return true if the entity is in the same chunk as {@code position}
|
|
|
|
*/
|
2020-02-13 15:14:41 +01:00
|
|
|
public boolean sameChunk(Position position) {
|
|
|
|
Position pos = getPosition();
|
2020-05-01 23:05:47 +02:00
|
|
|
int chunkX1 = ChunkUtils.getChunkCoordinate((int) Math.floor(pos.getX()));
|
|
|
|
int chunkZ1 = ChunkUtils.getChunkCoordinate((int) Math.floor(pos.getZ()));
|
2020-02-13 15:14:41 +01:00
|
|
|
|
2020-05-01 23:05:47 +02:00
|
|
|
int chunkX2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getX()));
|
|
|
|
int chunkZ2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getZ()));
|
2020-02-13 15:14:41 +01:00
|
|
|
|
|
|
|
return chunkX1 == chunkX2 && chunkZ1 == chunkZ2;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean sameChunk(Entity entity) {
|
|
|
|
return sameChunk(entity.getPosition());
|
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* Remove the entity from the server immediately
|
|
|
|
* WARNING: this do not trigger the {@link EntityDeathEvent} event
|
|
|
|
*/
|
2019-08-10 08:44:35 +02:00
|
|
|
public void remove() {
|
|
|
|
this.shouldRemove = true;
|
2019-09-03 07:36:04 +02:00
|
|
|
entityById.remove(id);
|
2019-08-24 01:05:52 +02:00
|
|
|
if (instance != null)
|
|
|
|
instance.removeEntity(this);
|
2019-08-10 08:44:35 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* Trigger {@link #remove()} after the specified time
|
|
|
|
*
|
|
|
|
* @param delay
|
|
|
|
* @param timeUnit to determine the delay unit
|
|
|
|
*/
|
|
|
|
public void scheduleRemove(long delay, TimeUnit timeUnit) {
|
|
|
|
delay = timeUnit.toMilliseconds(delay);
|
|
|
|
|
2019-08-21 16:50:52 +02:00
|
|
|
if (delay == 0) { // Cancel the scheduled remove
|
|
|
|
this.scheduledRemoveTime = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.scheduledRemoveTime = System.currentTimeMillis() + delay;
|
2019-08-20 22:40:57 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* @return true if {@link #scheduleRemove(long, TimeUnit)} has been called, false otherwise
|
|
|
|
*/
|
2019-08-30 01:17:46 +02:00
|
|
|
public boolean isRemoveScheduled() {
|
|
|
|
return scheduledRemoveTime != 0;
|
|
|
|
}
|
|
|
|
|
2019-08-24 20:34:01 +02:00
|
|
|
protected EntityVelocityPacket getVelocityPacket() {
|
2020-02-17 17:33:53 +01:00
|
|
|
final float strength = 8000f / MinecraftServer.TICK_PER_SECOND;
|
2019-08-24 20:34:01 +02:00
|
|
|
EntityVelocityPacket velocityPacket = new EntityVelocityPacket();
|
|
|
|
velocityPacket.entityId = getEntityId();
|
2019-08-25 20:03:43 +02:00
|
|
|
velocityPacket.velocityX = (short) (velocity.getX() * strength);
|
|
|
|
velocityPacket.velocityY = (short) (velocity.getY() * strength);
|
|
|
|
velocityPacket.velocityZ = (short) (velocity.getZ() * strength);
|
2019-08-24 20:34:01 +02:00
|
|
|
return velocityPacket;
|
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* Used to sync entities together, and sent when adding viewers
|
|
|
|
*
|
|
|
|
* @return The {@link EntityMetaDataPacket} related to this entity
|
|
|
|
*/
|
2019-08-22 14:52:32 +02:00
|
|
|
public EntityMetaDataPacket getMetadataPacket() {
|
|
|
|
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
|
|
|
|
metaDataPacket.entityId = getEntityId();
|
2019-09-02 06:02:12 +02:00
|
|
|
metaDataPacket.consumer = getMetadataConsumer();
|
2019-08-22 14:52:32 +02:00
|
|
|
return metaDataPacket;
|
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* Should be override when wanting to add a new metadata index
|
|
|
|
*
|
|
|
|
* @return The consumer used to write {@link EntityMetaDataPacket} in {@link #getMetadataPacket()}
|
|
|
|
*/
|
2020-04-16 16:40:29 +02:00
|
|
|
public Consumer<PacketWriter> getMetadataConsumer() {
|
2019-09-02 06:02:12 +02:00
|
|
|
return packet -> {
|
|
|
|
fillMetadataIndex(packet, 0);
|
|
|
|
fillMetadataIndex(packet, 1);
|
|
|
|
fillMetadataIndex(packet, 5);
|
2019-09-10 06:59:15 +02:00
|
|
|
fillMetadataIndex(packet, 6);
|
2019-09-02 06:02:12 +02:00
|
|
|
};
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* Send a {@link EntityMetaDataPacket} containing only the specified index
|
|
|
|
* The index is wrote using {@link #fillMetadataIndex(PacketWriter, int)}
|
|
|
|
*
|
|
|
|
* @param index
|
|
|
|
*/
|
2019-08-23 15:37:38 +02:00
|
|
|
protected void sendMetadataIndex(int index) {
|
2019-08-20 22:40:57 +02:00
|
|
|
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
|
|
|
|
metaDataPacket.entityId = getEntityId();
|
2019-09-02 06:02:12 +02:00
|
|
|
metaDataPacket.consumer = packet -> {
|
|
|
|
fillMetadataIndex(packet, index);
|
|
|
|
};
|
2019-08-27 05:23:25 +02:00
|
|
|
|
|
|
|
sendPacketToViewersAndSelf(metaDataPacket);
|
2019-08-20 22:40:57 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* Used to fill/write a specific metadata index
|
|
|
|
* The proper use to add a new metadata index is to override this and add your case
|
|
|
|
* Then you can also override {@link #getMetadataConsumer()} and fill your newly added index
|
|
|
|
*
|
|
|
|
* @param packet the packet writer
|
|
|
|
* @param index the index to fill/write
|
|
|
|
*/
|
2020-05-09 21:50:48 +02:00
|
|
|
protected void fillMetadataIndex(PacketWriter packet, int index) {
|
2019-08-19 17:04:19 +02:00
|
|
|
switch (index) {
|
|
|
|
case 0:
|
2019-09-02 06:02:12 +02:00
|
|
|
fillStateMetadata(packet);
|
2019-08-19 17:04:19 +02:00
|
|
|
break;
|
|
|
|
case 1:
|
2019-09-02 06:02:12 +02:00
|
|
|
fillAirTickMetaData(packet);
|
2019-08-19 17:04:19 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
2019-09-02 06:02:12 +02:00
|
|
|
fillCustomNameMetaData(packet);
|
2019-08-19 17:04:19 +02:00
|
|
|
break;
|
2019-08-23 15:37:38 +02:00
|
|
|
case 5:
|
2019-09-02 06:02:12 +02:00
|
|
|
fillNoGravityMetaData(packet);
|
2019-08-23 15:37:38 +02:00
|
|
|
break;
|
2019-09-10 06:59:15 +02:00
|
|
|
case 6:
|
|
|
|
fillPoseMetaData(packet);
|
|
|
|
break;
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-16 16:40:29 +02:00
|
|
|
private void fillStateMetadata(PacketWriter packet) {
|
|
|
|
packet.writeByte((byte) 0);
|
|
|
|
packet.writeByte(METADATA_BYTE);
|
2019-08-19 17:04:19 +02:00
|
|
|
byte index0 = 0;
|
|
|
|
if (onFire)
|
|
|
|
index0 += 1;
|
|
|
|
if (crouched)
|
|
|
|
index0 += 2;
|
|
|
|
if (UNUSED_METADATA)
|
|
|
|
index0 += 4;
|
|
|
|
if (sprinting)
|
|
|
|
index0 += 8;
|
|
|
|
if (swimming)
|
|
|
|
index0 += 16;
|
|
|
|
if (invisible)
|
|
|
|
index0 += 32;
|
|
|
|
if (glowing)
|
|
|
|
index0 += 64;
|
|
|
|
if (usingElytra)
|
|
|
|
index0 += 128;
|
2020-04-16 16:40:29 +02:00
|
|
|
packet.writeByte(index0);
|
2019-09-02 06:02:12 +02:00
|
|
|
}
|
|
|
|
|
2020-04-16 16:40:29 +02:00
|
|
|
private void fillAirTickMetaData(PacketWriter packet) {
|
|
|
|
packet.writeByte((byte) 1);
|
|
|
|
packet.writeByte(METADATA_VARINT);
|
|
|
|
packet.writeVarInt(air);
|
2019-09-02 06:02:12 +02:00
|
|
|
}
|
|
|
|
|
2020-04-16 16:40:29 +02:00
|
|
|
private void fillCustomNameMetaData(PacketWriter packet) {
|
|
|
|
packet.writeByte((byte) 2);
|
|
|
|
packet.writeByte(METADATA_CHAT);
|
|
|
|
packet.writeSizedString(customName);
|
2019-09-02 06:02:12 +02:00
|
|
|
}
|
|
|
|
|
2020-04-16 16:40:29 +02:00
|
|
|
private void fillNoGravityMetaData(PacketWriter packet) {
|
|
|
|
packet.writeByte((byte) 5);
|
|
|
|
packet.writeByte(METADATA_BOOLEAN);
|
|
|
|
packet.writeBoolean(noGravity);
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
|
|
|
|
2020-04-16 16:40:29 +02:00
|
|
|
private void fillPoseMetaData(PacketWriter packet) {
|
|
|
|
packet.writeByte((byte) 6);
|
|
|
|
packet.writeByte(METADATA_POSE);
|
|
|
|
packet.writeVarInt(pose.ordinal());
|
2019-09-10 06:59:15 +02:00
|
|
|
}
|
|
|
|
|
2019-08-30 01:17:46 +02:00
|
|
|
protected void sendSynchronization() {
|
2019-08-24 20:34:01 +02:00
|
|
|
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
|
|
|
|
entityTeleportPacket.entityId = getEntityId();
|
|
|
|
entityTeleportPacket.position = getPosition();
|
2019-08-27 20:49:11 +02:00
|
|
|
entityTeleportPacket.onGround = isOnGround();
|
2019-08-24 20:34:01 +02:00
|
|
|
sendPacketToViewers(entityTeleportPacket);
|
2019-08-31 12:10:46 +02:00
|
|
|
|
|
|
|
if (!passengers.isEmpty())
|
|
|
|
sendPacketToViewers(getPassengersPacket());
|
2019-08-24 20:34:01 +02:00
|
|
|
}
|
|
|
|
|
2020-02-09 15:34:09 +01:00
|
|
|
private boolean shouldUpdate(long time) {
|
2020-02-17 17:33:53 +01:00
|
|
|
return (float) (time - lastUpdate) >= MinecraftServer.TICK_MS * 0.9f; // Margin of error
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public enum Pose {
|
|
|
|
STANDING,
|
|
|
|
FALL_FLYING,
|
|
|
|
SLEEPING,
|
|
|
|
SWIMMING,
|
|
|
|
SPIN_ATTACK,
|
|
|
|
SNEAKING,
|
2020-04-09 14:25:42 +02:00
|
|
|
DYING
|
2019-08-19 17:04:19 +02:00
|
|
|
}
|
|
|
|
|
2019-08-10 08:44:35 +02:00
|
|
|
protected boolean shouldRemove() {
|
|
|
|
return shouldRemove;
|
|
|
|
}
|
|
|
|
}
|