mirror of
https://github.com/PaperMC/Paper.git
synced 2025-02-12 09:18:14 +00:00
e2f0efd1af
This method was used pre 1.17 era where an Entity was explicitly tied to a (then called) Chunk's entity slices. If an entity was not inside a Chunk, then it was considered invalid as it was not possible to save the entity. In 1.17+, entities are now tied to a separately tracked entity section management system. This system is far more reliable now as it no longer requires a full chunk load to properly track entities for saving. As a result, an Entity if inside the world is always attached to some entity chunk section (except in rare cases in Vanilla which are fixed in Moonrise). As a result, whether the chunk the entity is in is loaded is no longer an indication of whether they are tracked in the world and we can reliably infer that the entity is correctly in the world through the valid field alone. Additionally drop the isInWorld() check, as valid=true implies isInWorld=true. More importantly, the isInWorld() check invokes getHandle which may trip a thread check on Folia. This will fix World#getEntities() and friends exploding on Folia. However, World#getEntities() on Folia still cannot reliably return all entities in the world as actions such as cross-region (not cross-world) teleporting will remove entities from the world.
1780 lines
81 KiB
Diff
1780 lines
81 KiB
Diff
--- a/net/minecraft/world/entity/Entity.java
|
|
+++ b/net/minecraft/world/entity/Entity.java
|
|
@@ -136,6 +_,108 @@
|
|
import org.slf4j.Logger;
|
|
|
|
public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder {
|
|
+
|
|
+ // CraftBukkit start
|
|
+ private static final int CURRENT_LEVEL = 2;
|
|
+ public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation
|
|
+ static boolean isLevelAtLeast(CompoundTag tag, int level) {
|
|
+ return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level;
|
|
+ }
|
|
+
|
|
+ // Paper start - Share random for entities to make them more random
|
|
+ public static RandomSource SHARED_RANDOM = new RandomRandomSource();
|
|
+ private static final class RandomRandomSource extends java.util.Random implements net.minecraft.world.level.levelgen.BitRandomSource {
|
|
+ private boolean locked = false;
|
|
+
|
|
+ @Override
|
|
+ public synchronized void setSeed(long seed) {
|
|
+ if (locked) {
|
|
+ LOGGER.error("Ignoring setSeed on Entity.SHARED_RANDOM", new Throwable());
|
|
+ } else {
|
|
+ super.setSeed(seed);
|
|
+ locked = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public RandomSource fork() {
|
|
+ return new net.minecraft.world.level.levelgen.LegacyRandomSource(this.nextLong());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public net.minecraft.world.level.levelgen.PositionalRandomFactory forkPositional() {
|
|
+ return new net.minecraft.world.level.levelgen.LegacyRandomSource.LegacyPositionalRandomFactory(this.nextLong());
|
|
+ }
|
|
+
|
|
+ // these below are added to fix reobf issues that I don't wanna deal with right now
|
|
+ @Override
|
|
+ public int next(int bits) {
|
|
+ return super.next(bits);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int nextInt(int origin, int bound) {
|
|
+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(origin, bound);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public long nextLong() {
|
|
+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextLong();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int nextInt() {
|
|
+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int nextInt(int bound) {
|
|
+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(bound);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean nextBoolean() {
|
|
+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextBoolean();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public float nextFloat() {
|
|
+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextFloat();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public double nextDouble() {
|
|
+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextDouble();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public double nextGaussian() {
|
|
+ return super.nextGaussian();
|
|
+ }
|
|
+ }
|
|
+ // Paper end - Share random for entities to make them more random
|
|
+ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
|
|
+
|
|
+ private org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity;
|
|
+
|
|
+ public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntity() {
|
|
+ if (this.bukkitEntity == null) {
|
|
+ // Paper start - Folia schedulers
|
|
+ synchronized (this) {
|
|
+ if (this.bukkitEntity == null) {
|
|
+ return this.bukkitEntity = org.bukkit.craftbukkit.entity.CraftEntity.getEntity(this.level.getCraftServer(), this);
|
|
+ }
|
|
+ }
|
|
+ // Paper end - Folia schedulers
|
|
+ }
|
|
+ return this.bukkitEntity;
|
|
+ }
|
|
+ // Paper start
|
|
+ public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntityRaw() {
|
|
+ return this.bukkitEntity;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
public static final String ID_TAG = "id";
|
|
public static final String PASSENGERS_TAG = "Passengers";
|
|
@@ -196,7 +_,7 @@
|
|
public double zOld;
|
|
public boolean noPhysics;
|
|
private boolean wasOnFire;
|
|
- public final RandomSource random = RandomSource.create();
|
|
+ public final RandomSource random = SHARED_RANDOM; // Paper - Share random for entities to make them more random
|
|
public int tickCount;
|
|
private int remainingFireTicks = -this.getFireImmuneTicks();
|
|
public boolean wasTouchingWater;
|
|
@@ -233,7 +_,7 @@
|
|
protected UUID uuid = Mth.createInsecureUUID(this.random);
|
|
protected String stringUUID = this.uuid.toString();
|
|
private boolean hasGlowingTag;
|
|
- private final Set<String> tags = Sets.newHashSet();
|
|
+ private final Set<String> tags = new io.papermc.paper.util.SizeLimitedSet<>(new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(), MAX_ENTITY_TAG_COUNT); // Paper - fully limit tag size - replace set impl
|
|
private final double[] pistonDeltas = new double[]{0.0, 0.0, 0.0};
|
|
private long pistonDeltasGameTime;
|
|
private EntityDimensions dimensions;
|
|
@@ -250,6 +_,59 @@
|
|
private final List<Entity.Movement> movementThisTick = new ArrayList<>();
|
|
private final Set<BlockState> blocksInside = new ReferenceArraySet<>();
|
|
private final LongSet visitedBlocks = new LongOpenHashSet();
|
|
+ // CraftBukkit start
|
|
+ public boolean forceDrops;
|
|
+ public boolean persist = true;
|
|
+ public boolean visibleByDefault = true;
|
|
+ public boolean valid;
|
|
+ public boolean inWorld = false;
|
|
+ public boolean generation;
|
|
+ public int maxAirTicks = this.getDefaultMaxAirSupply(); // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
|
|
+ @Nullable // Paper - Refresh ProjectileSource for projectiles
|
|
+ public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only
|
|
+ public boolean lastDamageCancelled; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled
|
|
+ public boolean persistentInvisibility = false;
|
|
+ public BlockPos lastLavaContact;
|
|
+ // Marks an entity, that it was removed by a plugin via Entity#remove
|
|
+ // Main use case currently is for SPIGOT-7487, preventing dropping of leash when leash is removed
|
|
+ public boolean pluginRemoved = false;
|
|
+ protected int numCollisions = 0; // Paper - Cap entity collisions
|
|
+ public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals
|
|
+ public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one
|
|
+ // Paper start - Entity origin API
|
|
+ @javax.annotation.Nullable
|
|
+ private org.bukkit.util.Vector origin;
|
|
+ @javax.annotation.Nullable
|
|
+ private UUID originWorld;
|
|
+ public boolean freezeLocked = false; // Paper - Freeze Tick Lock API
|
|
+ public boolean fixedPose = false; // Paper - Expand Pose API
|
|
+ private final int despawnTime; // Paper - entity despawn time limit
|
|
+ public final io.papermc.paper.entity.activation.ActivationType activationType = io.papermc.paper.entity.activation.ActivationType.activationTypeFor(this); // Paper - EAR 2/tracking ranges
|
|
+
|
|
+ public void setOrigin(@javax.annotation.Nonnull org.bukkit.Location location) {
|
|
+ this.origin = location.toVector();
|
|
+ this.originWorld = location.getWorld().getUID();
|
|
+ }
|
|
+
|
|
+ @javax.annotation.Nullable
|
|
+ public org.bukkit.util.Vector getOriginVector() {
|
|
+ return this.origin != null ? this.origin.clone() : null;
|
|
+ }
|
|
+
|
|
+ @javax.annotation.Nullable
|
|
+ public UUID getOriginWorld() {
|
|
+ return this.originWorld;
|
|
+ }
|
|
+ // Paper end - Entity origin API
|
|
+ public float getBukkitYaw() {
|
|
+ return this.yRot;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ // Paper start
|
|
+ public final AABB getBoundingBoxAt(double x, double y, double z) {
|
|
+ return this.dimensions.makeBoundingBox(x, y, z);
|
|
+ }
|
|
+ // Paper end
|
|
|
|
public Entity(EntityType<?> entityType, Level level) {
|
|
this.type = entityType;
|
|
@@ -271,6 +_,7 @@
|
|
this.entityData = builder.build();
|
|
this.setPos(0.0, 0.0, 0.0);
|
|
this.eyeHeight = this.dimensions.eyeHeight();
|
|
+ this.despawnTime = type == EntityType.PLAYER ? -1 : level.paperConfig().entities.spawning.despawnTime.getOrDefault(type, io.papermc.paper.configuration.type.number.IntOr.Disabled.DISABLED).or(-1); // Paper - entity despawn time limit
|
|
}
|
|
|
|
public boolean isColliding(BlockPos pos, BlockState state) {
|
|
@@ -284,6 +_,12 @@
|
|
return team != null && team.getColor().getColor() != null ? team.getColor().getColor() : 16777215;
|
|
}
|
|
|
|
+ // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
|
|
+ public int getDefaultMaxAirSupply() {
|
|
+ return Entity.TOTAL_AIR_SUPPLY;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
public boolean isSpectator() {
|
|
return false;
|
|
}
|
|
@@ -324,7 +_,7 @@
|
|
}
|
|
|
|
public boolean addTag(String tag) {
|
|
- return this.tags.size() < 1024 && this.tags.add(tag);
|
|
+ return this.tags.add(tag); // Paper - fully limit tag size - replace set impl
|
|
}
|
|
|
|
public boolean removeTag(String tag) {
|
|
@@ -332,12 +_,18 @@
|
|
}
|
|
|
|
public void kill(ServerLevel level) {
|
|
- this.remove(Entity.RemovalReason.KILLED);
|
|
+ this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause
|
|
this.gameEvent(GameEvent.ENTITY_DIE);
|
|
}
|
|
|
|
public final void discard() {
|
|
- this.remove(Entity.RemovalReason.DISCARDED);
|
|
+ // CraftBukkit start - add Bukkit remove cause
|
|
+ this.discard(null);
|
|
+ }
|
|
+
|
|
+ public final void discard(org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
|
|
+ this.remove(Entity.RemovalReason.DISCARDED, cause);
|
|
+ // CraftBukkit end
|
|
}
|
|
|
|
protected abstract void defineSynchedData(SynchedEntityData.Builder builder);
|
|
@@ -346,6 +_,48 @@
|
|
return this.entityData;
|
|
}
|
|
|
|
+ // CraftBukkit start
|
|
+ public void refreshEntityData(ServerPlayer to) {
|
|
+ List<SynchedEntityData.DataValue<?>> list = this.entityData.packAll(); // Paper - Update EVERYTHING not just not default
|
|
+
|
|
+ if (list != null && to.getBukkitEntity().canSee(this.getBukkitEntity())) { // Paper
|
|
+ to.connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket(this.getId(), list));
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ // Paper start
|
|
+ // This method should only be used if the data of an entity could have become desynced
|
|
+ // due to interactions on the client.
|
|
+ public void resendPossiblyDesyncedEntityData(net.minecraft.server.level.ServerPlayer player) {
|
|
+ if (player.getBukkitEntity().canSee(this.getBukkitEntity())) {
|
|
+ ServerLevel world = (net.minecraft.server.level.ServerLevel)this.level();
|
|
+ net.minecraft.server.level.ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.getId());
|
|
+ if (tracker == null) {
|
|
+ return;
|
|
+ }
|
|
+ final net.minecraft.server.level.ServerEntity serverEntity = tracker.serverEntity;
|
|
+ final List<net.minecraft.network.protocol.Packet<? super net.minecraft.network.protocol.game.ClientGamePacketListener>> list = new java.util.ArrayList<>();
|
|
+ serverEntity.sendPairingData(player, list::add);
|
|
+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundBundlePacket(list));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // This method allows you to specifically resend certain data accessor keys to the client
|
|
+ public void resendPossiblyDesyncedDataValues(List<EntityDataAccessor<?>> keys, ServerPlayer to) {
|
|
+ if (!to.getBukkitEntity().canSee(this.getBukkitEntity())) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final List<SynchedEntityData.DataValue<?>> values = new java.util.ArrayList<>(keys.size());
|
|
+ for (final EntityDataAccessor<?> key : keys) {
|
|
+ final SynchedEntityData.DataItem<?> synchedValue = this.entityData.getItem(key);
|
|
+ values.add(synchedValue.value());
|
|
+ }
|
|
+
|
|
+ to.connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket(this.id, values));
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public boolean equals(Object object) {
|
|
return object instanceof Entity && ((Entity)object).id == this.id;
|
|
@@ -357,7 +_,13 @@
|
|
}
|
|
|
|
public void remove(Entity.RemovalReason reason) {
|
|
- this.setRemoved(reason);
|
|
+ // CraftBukkit start - add Bukkit remove cause
|
|
+ this.setRemoved(reason, null);
|
|
+ }
|
|
+
|
|
+ public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause eventCause) {
|
|
+ this.setRemoved(reason, eventCause);
|
|
+ // CraftBukkit end
|
|
}
|
|
|
|
public void onClientRemoval() {
|
|
@@ -367,6 +_,17 @@
|
|
}
|
|
|
|
public void setPose(Pose pose) {
|
|
+ if (this.fixedPose) return; // Paper - Expand Pose API
|
|
+ // CraftBukkit start
|
|
+ if (pose == this.getPose()) {
|
|
+ return;
|
|
+ }
|
|
+ // Paper start - Don't fire sync event during generation
|
|
+ if (!this.generation) {
|
|
+ this.level.getCraftServer().getPluginManager().callEvent(new org.bukkit.event.entity.EntityPoseChangeEvent(this.getBukkitEntity(), org.bukkit.entity.Pose.values()[pose.ordinal()]));
|
|
+ }
|
|
+ // Paper end - Don't fire sync event during generation
|
|
+ // CraftBukkit end
|
|
this.entityData.set(DATA_POSE, pose);
|
|
}
|
|
|
|
@@ -390,6 +_,32 @@
|
|
}
|
|
|
|
public void setRot(float yRot, float xRot) {
|
|
+ // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0
|
|
+ if (Float.isNaN(yRot)) {
|
|
+ yRot = 0;
|
|
+ }
|
|
+
|
|
+ if (yRot == Float.POSITIVE_INFINITY || yRot == Float.NEGATIVE_INFINITY) {
|
|
+ if (this instanceof ServerPlayer) {
|
|
+ this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid yaw");
|
|
+ ((org.bukkit.craftbukkit.entity.CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite yaw (Hacking?)");
|
|
+ }
|
|
+ yRot = 0;
|
|
+ }
|
|
+
|
|
+ // pitch was sometimes set to NaN, so we need to set it back to 0
|
|
+ if (Float.isNaN(xRot)) {
|
|
+ xRot = 0;
|
|
+ }
|
|
+
|
|
+ if (xRot == Float.POSITIVE_INFINITY || xRot == Float.NEGATIVE_INFINITY) {
|
|
+ if (this instanceof ServerPlayer) {
|
|
+ this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid pitch");
|
|
+ ((org.bukkit.craftbukkit.entity.CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite pitch (Hacking?)");
|
|
+ }
|
|
+ xRot = 0;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
this.setYRot(yRot % 360.0F);
|
|
this.setXRot(xRot % 360.0F);
|
|
}
|
|
@@ -399,8 +_,8 @@
|
|
}
|
|
|
|
public void setPos(double x, double y, double z) {
|
|
- this.setPosRaw(x, y, z);
|
|
- this.setBoundingBox(this.makeBoundingBox());
|
|
+ this.setPosRaw(x, y, z, true); // Paper - Block invalid positions and bounding box; force update
|
|
+ // this.setBoundingBox(this.makeBoundingBox()); // Paper - Block invalid positions and bounding box; move into setPosRaw
|
|
}
|
|
|
|
protected final AABB makeBoundingBox() {
|
|
@@ -430,12 +_,28 @@
|
|
}
|
|
|
|
public void tick() {
|
|
+ // Paper start - entity despawn time limit
|
|
+ if (this.despawnTime >= 0 && this.tickCount >= this.despawnTime) {
|
|
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN);
|
|
+ return;
|
|
+ }
|
|
+ // Paper end - entity despawn time limit
|
|
this.baseTick();
|
|
}
|
|
|
|
+ // CraftBukkit start
|
|
+ public void postTick() {
|
|
+ // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle
|
|
+ if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities
|
|
+ this.handlePortal();
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
public void baseTick() {
|
|
ProfilerFiller profilerFiller = Profiler.get();
|
|
profilerFiller.push("entityBaseTick");
|
|
+ if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Prevent entity loading causing async lookups
|
|
this.inBlockState = null;
|
|
if (this.isPassenger() && this.getVehicle().isRemoved()) {
|
|
this.stopRiding();
|
|
@@ -445,7 +_,7 @@
|
|
this.boardingCooldown--;
|
|
}
|
|
|
|
- this.handlePortal();
|
|
+ if (this instanceof ServerPlayer) this.handlePortal(); // CraftBukkit - // Moved up to postTick
|
|
if (this.canSpawnSprintParticle()) {
|
|
this.spawnSprintParticle();
|
|
}
|
|
@@ -470,7 +_,7 @@
|
|
this.setRemainingFireTicks(this.remainingFireTicks - 1);
|
|
}
|
|
|
|
- if (this.getTicksFrozen() > 0) {
|
|
+ if (this.getTicksFrozen() > 0 && !this.freezeLocked) { // Paper - Freeze Tick Lock API
|
|
this.setTicksFrozen(0);
|
|
this.level().levelEvent(null, 1009, this.blockPosition, 1);
|
|
}
|
|
@@ -482,6 +_,10 @@
|
|
if (this.isInLava()) {
|
|
this.lavaHurt();
|
|
this.fallDistance *= 0.5F;
|
|
+ // CraftBukkit start
|
|
+ } else {
|
|
+ this.lastLavaContact = null;
|
|
+ // CraftBukkit end
|
|
}
|
|
|
|
this.checkBelowWorld();
|
|
@@ -502,7 +_,12 @@
|
|
}
|
|
|
|
public void checkBelowWorld() {
|
|
- if (this.getY() < this.level().getMinY() - 64) {
|
|
+ if (!this.level.getWorld().isVoidDamageEnabled()) return; // Paper - check if void damage is enabled on the world
|
|
+ // Paper start - Configurable nether ceiling damage
|
|
+ if (this.getY() < (this.level.getMinY() + this.level.getWorld().getVoidDamageMinBuildHeightOffset()) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER // Paper - use configured min build height offset
|
|
+ && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v)
|
|
+ && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) {
|
|
+ // Paper end - Configurable nether ceiling damage
|
|
this.onBelowWorld();
|
|
}
|
|
}
|
|
@@ -531,9 +_,24 @@
|
|
|
|
public void lavaHurt() {
|
|
if (!this.fireImmune()) {
|
|
- this.igniteForSeconds(15.0F);
|
|
+ // CraftBukkit start - Fallen in lava TODO: this event spams!
|
|
+ if (this instanceof net.minecraft.world.entity.LivingEntity && this.remainingFireTicks <= 0) {
|
|
+ // not on fire yet
|
|
+ org.bukkit.block.Block damager = (this.lastLavaContact == null) ? null : org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.lastLavaContact);
|
|
+ org.bukkit.entity.Entity damagee = this.getBukkitEntity();
|
|
+ org.bukkit.event.entity.EntityCombustEvent combustEvent = new org.bukkit.event.entity.EntityCombustByBlockEvent(damager, damagee, 15);
|
|
+ this.level.getCraftServer().getPluginManager().callEvent(combustEvent);
|
|
+
|
|
+ if (!combustEvent.isCancelled()) {
|
|
+ this.igniteForSeconds(combustEvent.getDuration(), false);
|
|
+ }
|
|
+ } else {
|
|
+ // This will be called every single tick the entity is in lava, so don't throw an event
|
|
+ this.igniteForSeconds(15.0F, false);
|
|
+ }
|
|
+ // CraftBukkit end
|
|
if (this.level() instanceof ServerLevel serverLevel
|
|
- && this.hurtServer(serverLevel, this.damageSources().lava(), 4.0F)
|
|
+ && this.hurtServer(serverLevel, this.damageSources().lava().directBlock(this.level, this.lastLavaContact), 4.0F) // CraftBukkit - we also don't throw an event unless the object in lava is living, to save on some event calls
|
|
&& this.shouldPlayLavaHurtSound()
|
|
&& !this.isSilent()) {
|
|
serverLevel.playSound(
|
|
@@ -548,7 +_,23 @@
|
|
}
|
|
|
|
public final void igniteForSeconds(float seconds) {
|
|
- this.igniteForTicks(Mth.floor(seconds * 20.0F));
|
|
+ // CraftBukkit start
|
|
+ this.igniteForSeconds(seconds, true);
|
|
+ }
|
|
+
|
|
+ public final void igniteForSeconds(float f, boolean callEvent) {
|
|
+ if (callEvent) {
|
|
+ org.bukkit.event.entity.EntityCombustEvent event = new org.bukkit.event.entity.EntityCombustEvent(this.getBukkitEntity(), f);
|
|
+ this.level.getCraftServer().getPluginManager().callEvent(event);
|
|
+
|
|
+ if (event.isCancelled()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ f = event.getDuration();
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ this.igniteForTicks(Mth.floor(f * 20.0F));
|
|
}
|
|
|
|
public void igniteForTicks(int ticks) {
|
|
@@ -570,7 +_,7 @@
|
|
}
|
|
|
|
protected void onBelowWorld() {
|
|
- this.discard();
|
|
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.OUT_OF_WORLD); // CraftBukkit - add Bukkit remove cause
|
|
}
|
|
|
|
public boolean isFree(double x, double y, double z) {
|
|
@@ -626,7 +_,43 @@
|
|
return this.onGround;
|
|
}
|
|
|
|
+ // Paper start - detailed watchdog information
|
|
+ public final Object posLock = new Object(); // Paper - log detailed entity tick information
|
|
+
|
|
+ private Vec3 moveVector;
|
|
+ private double moveStartX;
|
|
+ private double moveStartY;
|
|
+ private double moveStartZ;
|
|
+
|
|
+ public final Vec3 getMoveVector() {
|
|
+ return this.moveVector;
|
|
+ }
|
|
+
|
|
+ public final double getMoveStartX() {
|
|
+ return this.moveStartX;
|
|
+ }
|
|
+
|
|
+ public final double getMoveStartY() {
|
|
+ return this.moveStartY;
|
|
+ }
|
|
+
|
|
+ public final double getMoveStartZ() {
|
|
+ return this.moveStartZ;
|
|
+ }
|
|
+ // Paper end - detailed watchdog information
|
|
+
|
|
public void move(MoverType type, Vec3 movement) {
|
|
+ final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity
|
|
+ // Paper start - detailed watchdog information
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot move an entity off-main");
|
|
+ synchronized (this.posLock) {
|
|
+ this.moveStartX = this.getX();
|
|
+ this.moveStartY = this.getY();
|
|
+ this.moveStartZ = this.getZ();
|
|
+ this.moveVector = movement;
|
|
+ }
|
|
+ try {
|
|
+ // Paper end - detailed watchdog information
|
|
if (this.noPhysics) {
|
|
this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
|
|
} else {
|
|
@@ -701,6 +_,28 @@
|
|
}
|
|
}
|
|
|
|
+ // CraftBukkit start
|
|
+ if (this.horizontalCollision && this.getBukkitEntity() instanceof org.bukkit.entity.Vehicle) {
|
|
+ org.bukkit.entity.Vehicle vehicle = (org.bukkit.entity.Vehicle) this.getBukkitEntity();
|
|
+ org.bukkit.block.Block bl = this.level.getWorld().getBlockAt(Mth.floor(this.getX()), Mth.floor(this.getY()), Mth.floor(this.getZ()));
|
|
+
|
|
+ if (movement.x > vec3.x) {
|
|
+ bl = bl.getRelative(org.bukkit.block.BlockFace.EAST);
|
|
+ } else if (movement.x < vec3.x) {
|
|
+ bl = bl.getRelative(org.bukkit.block.BlockFace.WEST);
|
|
+ } else if (movement.z > vec3.z) {
|
|
+ bl = bl.getRelative(org.bukkit.block.BlockFace.SOUTH);
|
|
+ } else if (movement.z < vec3.z) {
|
|
+ bl = bl.getRelative(org.bukkit.block.BlockFace.NORTH);
|
|
+ }
|
|
+
|
|
+ if (!bl.getType().isAir()) {
|
|
+ org.bukkit.event.vehicle.VehicleBlockCollisionEvent event = new org.bukkit.event.vehicle.VehicleBlockCollisionEvent(vehicle, bl, org.bukkit.craftbukkit.util.CraftVector.toBukkit(originalMovement)); // Paper - Expose pre-collision velocity
|
|
+ this.level.getCraftServer().getPluginManager().callEvent(event);
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
if (!this.level().isClientSide() || this.isControlledByLocalInstance()) {
|
|
Entity.MovementEmission movementEmission = this.getMovementEmission();
|
|
if (movementEmission.emitsAnything() && !this.isPassenger()) {
|
|
@@ -713,6 +_,13 @@
|
|
profilerFiller.pop();
|
|
}
|
|
}
|
|
+ // Paper start - detailed watchdog information
|
|
+ } finally {
|
|
+ synchronized (this.posLock) { // Paper
|
|
+ this.moveVector = null;
|
|
+ } // Paper
|
|
+ }
|
|
+ // Paper end - detailed watchdog information
|
|
}
|
|
|
|
private void applyMovementEmissionAndPlaySound(Entity.MovementEmission movementEmission, Vec3 movement, BlockPos pos, BlockState state) {
|
|
@@ -850,7 +_,7 @@
|
|
}
|
|
|
|
protected BlockPos getOnPos(float yOffset) {
|
|
- if (this.mainSupportingBlockPos.isPresent()) {
|
|
+ if (this.mainSupportingBlockPos.isPresent() && this.level().getChunkIfLoadedImmediately(this.mainSupportingBlockPos.get()) != null) { // Paper - ensure no loads
|
|
BlockPos blockPos = this.mainSupportingBlockPos.get();
|
|
if (!(yOffset > 1.0E-5F)) {
|
|
return blockPos;
|
|
@@ -1049,6 +_,20 @@
|
|
return SoundEvents.GENERIC_SPLASH;
|
|
}
|
|
|
|
+ // CraftBukkit start - Add delegate methods
|
|
+ public SoundEvent getSwimSound0() {
|
|
+ return this.getSwimSound();
|
|
+ }
|
|
+
|
|
+ public SoundEvent getSwimSplashSound0() {
|
|
+ return this.getSwimSplashSound();
|
|
+ }
|
|
+
|
|
+ public SoundEvent getSwimHighSpeedSplashSound0() {
|
|
+ return this.getSwimHighSpeedSplashSound();
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
public void recordMovementThroughBlocks(Vec3 oldPosition, Vec3 position) {
|
|
this.movementThisTick.add(new Entity.Movement(oldPosition, position));
|
|
}
|
|
@@ -1485,6 +_,7 @@
|
|
this.setXRot(Mth.clamp(xRot, -90.0F, 90.0F) % 360.0F);
|
|
this.yRotO = this.getYRot();
|
|
this.xRotO = this.getXRot();
|
|
+ this.setYHeadRot(yRot); // Paper - Update head rotation
|
|
}
|
|
|
|
public void absMoveTo(double x, double y, double z) {
|
|
@@ -1494,6 +_,7 @@
|
|
this.yo = y;
|
|
this.zo = d1;
|
|
this.setPos(d, y, d1);
|
|
+ if (this.valid) this.level.getChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); // CraftBukkit
|
|
}
|
|
|
|
public void moveTo(Vec3 vec) {
|
|
@@ -1513,11 +_,19 @@
|
|
}
|
|
|
|
public void moveTo(double x, double y, double z, float yRot, float xRot) {
|
|
+ // Paper start - Fix Entity Teleportation and cancel velocity if teleported
|
|
+ if (!preserveMotion) {
|
|
+ this.deltaMovement = Vec3.ZERO;
|
|
+ } else {
|
|
+ this.preserveMotion = false;
|
|
+ }
|
|
+ // Paper end - Fix Entity Teleportation and cancel velocity if teleported
|
|
this.setPosRaw(x, y, z);
|
|
this.setYRot(yRot);
|
|
this.setXRot(xRot);
|
|
this.setOldPosAndRot();
|
|
this.reapplyPosition();
|
|
+ this.setYHeadRot(yRot); // Paper - Update head rotation
|
|
}
|
|
|
|
public final void setOldPosAndRot() {
|
|
@@ -1584,6 +_,7 @@
|
|
public void push(Entity entity) {
|
|
if (!this.isPassengerOfSameVehicle(entity)) {
|
|
if (!entity.noPhysics && !this.noPhysics) {
|
|
+ if (this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper - Collision option for requiring a player participant
|
|
double d = entity.getX() - this.getX();
|
|
double d1 = entity.getZ() - this.getZ();
|
|
double max = Mth.absMax(d, d1);
|
|
@@ -1617,7 +_,21 @@
|
|
}
|
|
|
|
public void push(double x, double y, double z) {
|
|
- this.setDeltaMovement(this.getDeltaMovement().add(x, y, z));
|
|
+ // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
|
|
+ this.push(x, y, z, null);
|
|
+ }
|
|
+
|
|
+ public void push(double x, double y, double z, @Nullable Entity pushingEntity) {
|
|
+ org.bukkit.util.Vector delta = new org.bukkit.util.Vector(x, y, z);
|
|
+ if (pushingEntity != null) {
|
|
+ io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent event = new io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent(this.getBukkitEntity(), io.papermc.paper.event.entity.EntityKnockbackEvent.Cause.PUSH, pushingEntity.getBukkitEntity(), delta);
|
|
+ if (!event.callEvent()) {
|
|
+ return;
|
|
+ }
|
|
+ delta = event.getKnockback();
|
|
+ }
|
|
+ this.setDeltaMovement(this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ()));
|
|
+ // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent
|
|
this.hasImpulse = true;
|
|
}
|
|
|
|
@@ -1724,8 +_,20 @@
|
|
}
|
|
|
|
public boolean isPushable() {
|
|
+ // Paper start - Climbing should not bypass cramming gamerule
|
|
+ return isCollidable(false);
|
|
+ }
|
|
+
|
|
+ public boolean isCollidable(boolean ignoreClimbing) {
|
|
+ // Paper end - Climbing should not bypass cramming gamerule
|
|
return false;
|
|
}
|
|
+
|
|
+ // CraftBukkit start - collidable API
|
|
+ public boolean canCollideWithBukkit(Entity entity) {
|
|
+ return this.isPushable();
|
|
+ }
|
|
+ // CraftBukkit end
|
|
|
|
public void awardKillScore(Entity entity, DamageSource damageSource) {
|
|
if (entity instanceof ServerPlayer) {
|
|
@@ -1752,15 +_,22 @@
|
|
}
|
|
|
|
public boolean saveAsPassenger(CompoundTag compound) {
|
|
- if (this.removalReason != null && !this.removalReason.shouldSave()) {
|
|
+ // CraftBukkit start - allow excluding certain data when saving
|
|
+ // Paper start - Raw entity serialization API
|
|
+ return this.saveAsPassenger(compound, true, false, false);
|
|
+ }
|
|
+ public boolean saveAsPassenger(CompoundTag compound, boolean includeAll, boolean includeNonSaveable, boolean forceSerialization) {
|
|
+ // Paper end - Raw entity serialization API
|
|
+ // CraftBukkit end
|
|
+ if (this.removalReason != null && !this.removalReason.shouldSave() && !forceSerialization) { // Paper - Raw entity serialization API
|
|
return false;
|
|
} else {
|
|
- String encodeId = this.getEncodeId();
|
|
- if (encodeId == null) {
|
|
+ String encodeId = this.getEncodeId(includeNonSaveable); // Paper - Raw entity serialization API
|
|
+ if ((!this.persist && !forceSerialization) || encodeId == null) { // CraftBukkit - persist flag // Paper - Raw entity serialization API
|
|
return false;
|
|
} else {
|
|
compound.putString("id", encodeId);
|
|
- this.saveWithoutId(compound);
|
|
+ this.saveWithoutId(compound, includeAll, includeNonSaveable, forceSerialization); // CraftBukkit - pass on includeAll // Paper - Raw entity serialization API
|
|
return true;
|
|
}
|
|
}
|
|
@@ -1771,15 +_,37 @@
|
|
}
|
|
|
|
public CompoundTag saveWithoutId(CompoundTag compound) {
|
|
+ // CraftBukkit start - allow excluding certain data when saving
|
|
+ // Paper start - Raw entity serialization API
|
|
+ return this.saveWithoutId(compound, true, false, false);
|
|
+ }
|
|
+
|
|
+ public CompoundTag saveWithoutId(CompoundTag compound, boolean includeAll, boolean includeNonSaveable, boolean forceSerialization) {
|
|
+ // Paper end - Raw entity serialization API
|
|
+ // CraftBukkit end
|
|
try {
|
|
- if (this.vehicle != null) {
|
|
- compound.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
|
|
- } else {
|
|
- compound.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ()));
|
|
- }
|
|
+ // CraftBukkit start - selectively save position
|
|
+ if (includeAll) {
|
|
+ if (this.vehicle != null) {
|
|
+ compound.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
|
|
+ } else {
|
|
+ compound.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ()));
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
|
|
Vec3 deltaMovement = this.getDeltaMovement();
|
|
compound.put("Motion", this.newDoubleList(deltaMovement.x, deltaMovement.y, deltaMovement.z));
|
|
+ // CraftBukkit start - Checking for NaN pitch/yaw and resetting to zero
|
|
+ // TODO: make sure this is the best way to address this.
|
|
+ if (Float.isNaN(this.yRot)) {
|
|
+ this.yRot = 0;
|
|
+ }
|
|
+
|
|
+ if (Float.isNaN(this.xRot)) {
|
|
+ this.xRot = 0;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
compound.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot()));
|
|
compound.putFloat("FallDistance", this.fallDistance);
|
|
compound.putShort("Fire", (short)this.remainingFireTicks);
|
|
@@ -1787,7 +_,29 @@
|
|
compound.putBoolean("OnGround", this.onGround());
|
|
compound.putBoolean("Invulnerable", this.invulnerable);
|
|
compound.putInt("PortalCooldown", this.portalCooldown);
|
|
- compound.putUUID("UUID", this.getUUID());
|
|
+ // CraftBukkit start - selectively save uuid and world
|
|
+ if (includeAll) {
|
|
+ compound.putUUID("UUID", this.getUUID());
|
|
+ // PAIL: Check above UUID reads 1.8 properly, ie: UUIDMost / UUIDLeast
|
|
+ compound.putLong("WorldUUIDLeast", this.level.getWorld().getUID().getLeastSignificantBits());
|
|
+ compound.putLong("WorldUUIDMost", this.level.getWorld().getUID().getMostSignificantBits());
|
|
+ }
|
|
+ compound.putInt("Bukkit.updateLevel", Entity.CURRENT_LEVEL);
|
|
+ if (!this.persist) {
|
|
+ compound.putBoolean("Bukkit.persist", this.persist);
|
|
+ }
|
|
+ if (!this.visibleByDefault) {
|
|
+ compound.putBoolean("Bukkit.visibleByDefault", this.visibleByDefault);
|
|
+ }
|
|
+ if (this.persistentInvisibility) {
|
|
+ compound.putBoolean("Bukkit.invisible", this.persistentInvisibility);
|
|
+ }
|
|
+ // SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
|
|
+ if (this.maxAirTicks != this.getDefaultMaxAirSupply()) {
|
|
+ compound.putInt("Bukkit.MaxAirSupply", this.getMaxAirSupply());
|
|
+ }
|
|
+ compound.putInt("Spigot.ticksLived", this.tickCount);
|
|
+ // CraftBukkit end
|
|
Component customName = this.getCustomName();
|
|
if (customName != null) {
|
|
compound.putString("CustomName", Component.Serializer.toJson(customName, this.registryAccess()));
|
|
@@ -1828,13 +_,13 @@
|
|
compound.put("Tags", listTag);
|
|
}
|
|
|
|
- this.addAdditionalSaveData(compound);
|
|
+ this.addAdditionalSaveData(compound, includeAll); // CraftBukkit - pass on includeAll
|
|
if (this.isVehicle()) {
|
|
ListTag listTag = new ListTag();
|
|
|
|
for (Entity entity : this.getPassengers()) {
|
|
CompoundTag compoundTag = new CompoundTag();
|
|
- if (entity.saveAsPassenger(compoundTag)) {
|
|
+ if (entity.saveAsPassenger(compoundTag, includeAll, includeNonSaveable, forceSerialization)) { // CraftBukkit - pass on includeAll // Paper - Raw entity serialization API
|
|
listTag.add(compoundTag);
|
|
}
|
|
}
|
|
@@ -1844,6 +_,33 @@
|
|
}
|
|
}
|
|
|
|
+ // CraftBukkit start - stores eventually existing bukkit values
|
|
+ if (this.bukkitEntity != null) {
|
|
+ this.bukkitEntity.storeBukkitValues(compound);
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ // Paper start
|
|
+ if (this.origin != null) {
|
|
+ UUID originWorld = this.originWorld != null ? this.originWorld : this.level != null ? this.level.getWorld().getUID() : null;
|
|
+ if (originWorld != null) {
|
|
+ compound.putUUID("Paper.OriginWorld", originWorld);
|
|
+ }
|
|
+ compound.put("Paper.Origin", this.newDoubleList(this.origin.getX(), this.origin.getY(), this.origin.getZ()));
|
|
+ }
|
|
+ if (this.spawnReason != null) {
|
|
+ compound.putString("Paper.SpawnReason", this.spawnReason.name());
|
|
+ }
|
|
+ // Save entity's from mob spawner status
|
|
+ if (this.spawnedViaMobSpawner) {
|
|
+ compound.putBoolean("Paper.FromMobSpawner", true);
|
|
+ }
|
|
+ if (this.fromNetherPortal) {
|
|
+ compound.putBoolean("Paper.FromNetherPortal", true);
|
|
+ }
|
|
+ if (this.freezeLocked) {
|
|
+ compound.putBoolean("Paper.FreezeLock", true);
|
|
+ }
|
|
+ // Paper end
|
|
return compound;
|
|
} catch (Throwable var9) {
|
|
CrashReport crashReport = CrashReport.forThrowable(var9, "Saving entity NBT");
|
|
@@ -1930,6 +_,69 @@
|
|
} else {
|
|
throw new IllegalStateException("Entity has invalid rotation");
|
|
}
|
|
+ // CraftBukkit start
|
|
+ // Spigot start
|
|
+ if (this instanceof net.minecraft.world.entity.LivingEntity) {
|
|
+ this.tickCount = compound.getInt("Spigot.ticksLived");
|
|
+ }
|
|
+ // Spigot end
|
|
+ this.persist = !compound.contains("Bukkit.persist") || compound.getBoolean("Bukkit.persist");
|
|
+ this.visibleByDefault = !compound.contains("Bukkit.visibleByDefault") || compound.getBoolean("Bukkit.visibleByDefault");
|
|
+ // SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
|
|
+ if (compound.contains("Bukkit.MaxAirSupply")) {
|
|
+ this.maxAirTicks = compound.getInt("Bukkit.MaxAirSupply");
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
+ // CraftBukkit start
|
|
+ // Paper - move world parsing/loading to PlayerList#placeNewPlayer
|
|
+ this.getBukkitEntity().readBukkitValues(compound);
|
|
+ if (compound.contains("Bukkit.invisible")) {
|
|
+ boolean bukkitInvisible = compound.getBoolean("Bukkit.invisible");
|
|
+ this.setInvisible(bukkitInvisible);
|
|
+ this.persistentInvisibility = bukkitInvisible;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
+ // Paper start
|
|
+ ListTag originTag = compound.getList("Paper.Origin", net.minecraft.nbt.Tag.TAG_DOUBLE);
|
|
+ if (!originTag.isEmpty()) {
|
|
+ UUID originWorld = null;
|
|
+ if (compound.contains("Paper.OriginWorld")) {
|
|
+ originWorld = compound.getUUID("Paper.OriginWorld");
|
|
+ } else if (this.level != null) {
|
|
+ originWorld = this.level.getWorld().getUID();
|
|
+ }
|
|
+ this.originWorld = originWorld;
|
|
+ origin = new org.bukkit.util.Vector(originTag.getDouble(0), originTag.getDouble(1), originTag.getDouble(2));
|
|
+ }
|
|
+
|
|
+ spawnedViaMobSpawner = compound.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status
|
|
+ fromNetherPortal = compound.getBoolean("Paper.FromNetherPortal");
|
|
+ if (compound.contains("Paper.SpawnReason")) {
|
|
+ String spawnReasonName = compound.getString("Paper.SpawnReason");
|
|
+ try {
|
|
+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName);
|
|
+ } catch (Exception ignored) {
|
|
+ LOGGER.error("Unknown SpawnReason " + spawnReasonName + " for " + this);
|
|
+ }
|
|
+ }
|
|
+ if (spawnReason == null) {
|
|
+ if (spawnedViaMobSpawner) {
|
|
+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER;
|
|
+ } else if (this instanceof Mob && (this instanceof net.minecraft.world.entity.animal.Animal || this instanceof net.minecraft.world.entity.animal.AbstractFish) && !((Mob) this).removeWhenFarAway(0.0)) {
|
|
+ if (!compound.getBoolean("PersistenceRequired")) {
|
|
+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (spawnReason == null) {
|
|
+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT;
|
|
+ }
|
|
+ if (compound.contains("Paper.FreezeLock")) {
|
|
+ freezeLocked = compound.getBoolean("Paper.FreezeLock");
|
|
+ }
|
|
+ // Paper end
|
|
} catch (Throwable var17) {
|
|
CrashReport crashReport = CrashReport.forThrowable(var17, "Loading entity NBT");
|
|
CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being loaded");
|
|
@@ -1944,10 +_,21 @@
|
|
|
|
@Nullable
|
|
public final String getEncodeId() {
|
|
+ // Paper start - Raw entity serialization API
|
|
+ return getEncodeId(false);
|
|
+ }
|
|
+ public final @Nullable String getEncodeId(boolean includeNonSaveable) {
|
|
+ // Paper end - Raw entity serialization API
|
|
EntityType<?> type = this.getType();
|
|
ResourceLocation key = EntityType.getKey(type);
|
|
- return type.canSerialize() && key != null ? key.toString() : null;
|
|
- }
|
|
+ return (type.canSerialize() || includeNonSaveable) && key != null ? key.toString() : null; // Paper - Raw entity serialization API
|
|
+ }
|
|
+
|
|
+ // CraftBukkit start - allow excluding certain data when saving
|
|
+ protected void addAdditionalSaveData(CompoundTag tag, boolean includeAll) {
|
|
+ this.addAdditionalSaveData(tag);
|
|
+ }
|
|
+ // CraftBukkit end
|
|
|
|
protected abstract void readAdditionalSaveData(CompoundTag tag);
|
|
|
|
@@ -1990,11 +_,61 @@
|
|
|
|
@Nullable
|
|
public ItemEntity spawnAtLocation(ServerLevel level, ItemStack stack, float yOffset) {
|
|
+ // Paper start - Restore vanilla drops behavior
|
|
+ return this.spawnAtLocation(level, stack, yOffset, null);
|
|
+ }
|
|
+ public record DefaultDrop(Item item, org.bukkit.inventory.ItemStack stack, @Nullable java.util.function.Consumer<ItemStack> dropConsumer) {
|
|
+ public DefaultDrop(final ItemStack stack, final java.util.function.Consumer<ItemStack> dropConsumer) {
|
|
+ this(stack.getItem(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), dropConsumer);
|
|
+ }
|
|
+
|
|
+ public void runConsumer(final java.util.function.Consumer<org.bukkit.inventory.ItemStack> fallback) {
|
|
+ if (this.dropConsumer == null || org.bukkit.craftbukkit.inventory.CraftItemType.bukkitToMinecraft(this.stack.getType()) != this.item) {
|
|
+ fallback.accept(this.stack);
|
|
+ } else {
|
|
+ this.dropConsumer.accept(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(this.stack));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ @Nullable
|
|
+ public ItemEntity spawnAtLocation(ServerLevel level, ItemStack stack, float yOffset, @Nullable java.util.function.Consumer<? super ItemEntity> delayedAddConsumer) {
|
|
+ // Paper end - Restore vanilla drops behavior
|
|
if (stack.isEmpty()) {
|
|
return null;
|
|
} else {
|
|
- ItemEntity itemEntity = new ItemEntity(level, this.getX(), this.getY() + yOffset, this.getZ(), stack);
|
|
+ // CraftBukkit start - Capture drops for death event
|
|
+ if (this instanceof net.minecraft.world.entity.LivingEntity && !this.forceDrops) {
|
|
+ // Paper start - Restore vanilla drops behavior
|
|
+ ((net.minecraft.world.entity.LivingEntity) this).drops.add(new net.minecraft.world.entity.Entity.DefaultDrop(stack, itemStack -> {
|
|
+ ItemEntity itemEntity = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), itemStack); // stack is copied before consumer
|
|
+ itemEntity.setDefaultPickUpDelay();
|
|
+ this.level.addFreshEntity(itemEntity);
|
|
+ if (delayedAddConsumer != null) delayedAddConsumer.accept(itemEntity);
|
|
+ }));
|
|
+ // Paper end - Restore vanilla drops behavior
|
|
+ return null;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ ItemEntity itemEntity = new ItemEntity(level, this.getX(), this.getY() + yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original
|
|
+ stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe
|
|
+
|
|
itemEntity.setDefaultPickUpDelay();
|
|
+ itemEntity.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer)
|
|
+ // Paper start - Call EntityDropItemEvent
|
|
+ return this.spawnAtLocation(level, itemEntity);
|
|
+ }
|
|
+ }
|
|
+ @Nullable
|
|
+ public ItemEntity spawnAtLocation(ServerLevel level, ItemEntity itemEntity) {
|
|
+ {
|
|
+ // Paper end - Call EntityDropItemEvent
|
|
+ // CraftBukkit start
|
|
+ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
|
|
+ org.bukkit.Bukkit.getPluginManager().callEvent(event);
|
|
+ if (event.isCancelled()) {
|
|
+ return null;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
level.addFreshEntity(itemEntity);
|
|
return itemEntity;
|
|
}
|
|
@@ -2028,7 +_,16 @@
|
|
if (this.isAlive() && this instanceof Leashable leashable) {
|
|
if (leashable.getLeashHolder() == player) {
|
|
if (!this.level().isClientSide()) {
|
|
- if (player.hasInfiniteMaterials()) {
|
|
+ // CraftBukkit start - fire PlayerUnleashEntityEvent
|
|
+ // Paper start - Expand EntityUnleashEvent
|
|
+ org.bukkit.event.player.PlayerUnleashEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials());
|
|
+ if (event.isCancelled()) {
|
|
+ // Paper end - Expand EntityUnleashEvent
|
|
+ ((ServerPlayer) player).connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder()));
|
|
+ return InteractionResult.PASS;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ if (!event.isDropLeash()) { // Paper - Expand EntityUnleashEvent
|
|
leashable.removeLeash();
|
|
} else {
|
|
leashable.dropLeash();
|
|
@@ -2043,6 +_,13 @@
|
|
ItemStack itemInHand = player.getItemInHand(hand);
|
|
if (itemInHand.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) {
|
|
if (!this.level().isClientSide()) {
|
|
+ // CraftBukkit start - fire PlayerLeashEntityEvent
|
|
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLeashEntityEvent(this, player, player, hand).isCancelled()) {
|
|
+ ((ServerPlayer) player).connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder()));
|
|
+ player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
|
|
+ return InteractionResult.PASS;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
leashable.setLeashedTo(player, true);
|
|
}
|
|
|
|
@@ -2116,11 +_,11 @@
|
|
}
|
|
|
|
public boolean startRiding(Entity vehicle, boolean force) {
|
|
- if (vehicle == this.vehicle) {
|
|
+ if (vehicle == this.vehicle || vehicle.level != this.level) { // Paper - Ensure entity passenger world matches ridden entity (bad plugins)
|
|
return false;
|
|
} else if (!vehicle.couldAcceptPassenger()) {
|
|
return false;
|
|
- } else if (!this.level().isClientSide() && !vehicle.type.canSerialize()) {
|
|
+ } else if (!force && !this.level().isClientSide() && !vehicle.type.canSerialize()) { // SPIGOT-7947: Allow force riding all entities
|
|
return false;
|
|
} else {
|
|
for (Entity entity = vehicle; entity.vehicle != null; entity = entity.vehicle) {
|
|
@@ -2130,6 +_,27 @@
|
|
}
|
|
|
|
if (force || this.canRide(vehicle) && vehicle.canAddPassenger(this)) {
|
|
+ // CraftBukkit start
|
|
+ if (vehicle.getBukkitEntity() instanceof org.bukkit.entity.Vehicle && this.getBukkitEntity() instanceof org.bukkit.entity.LivingEntity) {
|
|
+ org.bukkit.event.vehicle.VehicleEnterEvent event = new org.bukkit.event.vehicle.VehicleEnterEvent((org.bukkit.entity.Vehicle) vehicle.getBukkitEntity(), this.getBukkitEntity());
|
|
+ // Suppress during worldgen
|
|
+ if (this.valid) {
|
|
+ org.bukkit.Bukkit.getPluginManager().callEvent(event);
|
|
+ }
|
|
+ if (event.isCancelled()) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ org.bukkit.event.entity.EntityMountEvent event = new org.bukkit.event.entity.EntityMountEvent(this.getBukkitEntity(), vehicle.getBukkitEntity());
|
|
+ // Suppress during worldgen
|
|
+ if (this.valid) {
|
|
+ org.bukkit.Bukkit.getPluginManager().callEvent(event);
|
|
+ }
|
|
+ if (event.isCancelled()) {
|
|
+ return false;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
if (this.isPassenger()) {
|
|
this.stopRiding();
|
|
}
|
|
@@ -2158,15 +_,26 @@
|
|
}
|
|
|
|
public void removeVehicle() {
|
|
+ // Paper start - Force entity dismount during teleportation
|
|
+ this.removeVehicle(false);
|
|
+ }
|
|
+ public void removeVehicle(boolean suppressCancellation) {
|
|
+ // Paper end - Force entity dismount during teleportation
|
|
if (this.vehicle != null) {
|
|
Entity entity = this.vehicle;
|
|
this.vehicle = null;
|
|
- entity.removePassenger(this);
|
|
+ if (!entity.removePassenger(this, suppressCancellation)) this.vehicle = entity; // CraftBukkit // Paper - Force entity dismount during teleportation
|
|
}
|
|
}
|
|
|
|
public void stopRiding() {
|
|
- this.removeVehicle();
|
|
+ // Paper start - Force entity dismount during teleportation
|
|
+ this.stopRiding(false);
|
|
+ }
|
|
+
|
|
+ public void stopRiding(boolean suppressCancellation) {
|
|
+ this.removeVehicle(suppressCancellation);
|
|
+ // Paper end - Force entity dismount during teleportation
|
|
}
|
|
|
|
protected void addPassenger(Entity passenger) {
|
|
@@ -2190,10 +_,43 @@
|
|
}
|
|
}
|
|
|
|
- protected void removePassenger(Entity passenger) {
|
|
+ // Paper start - Force entity dismount during teleportation
|
|
+ protected boolean removePassenger(Entity passenger) {
|
|
+ return removePassenger(passenger, false);
|
|
+ }
|
|
+ protected boolean removePassenger(Entity passenger, boolean suppressCancellation) { // CraftBukkit
|
|
+ // Paper end - Force entity dismount during teleportation
|
|
if (passenger.getVehicle() == this) {
|
|
throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)");
|
|
} else {
|
|
+ // CraftBukkit start
|
|
+ org.bukkit.craftbukkit.entity.CraftEntity craft = (org.bukkit.craftbukkit.entity.CraftEntity) passenger.getBukkitEntity().getVehicle();
|
|
+ Entity orig = craft == null ? null : craft.getHandle();
|
|
+ if (this.getBukkitEntity() instanceof org.bukkit.entity.Vehicle && passenger.getBukkitEntity() instanceof org.bukkit.entity.LivingEntity) {
|
|
+ org.bukkit.event.vehicle.VehicleExitEvent event = new org.bukkit.event.vehicle.VehicleExitEvent(
|
|
+ (org.bukkit.entity.Vehicle) this.getBukkitEntity(),
|
|
+ (org.bukkit.entity.LivingEntity) passenger.getBukkitEntity(), !suppressCancellation // Paper - Force entity dismount during teleportation
|
|
+ );
|
|
+ // Suppress during worldgen
|
|
+ if (this.valid) {
|
|
+ org.bukkit.Bukkit.getPluginManager().callEvent(event);
|
|
+ }
|
|
+ org.bukkit.craftbukkit.entity.CraftEntity craftn = (org.bukkit.craftbukkit.entity.CraftEntity) passenger.getBukkitEntity().getVehicle();
|
|
+ Entity n = craftn == null ? null : craftn.getHandle();
|
|
+ if (event.isCancelled() || n != orig) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ org.bukkit.event.entity.EntityDismountEvent event = new org.bukkit.event.entity.EntityDismountEvent(passenger.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation
|
|
+ // Suppress during worldgen
|
|
+ if (this.valid) {
|
|
+ org.bukkit.Bukkit.getPluginManager().callEvent(event);
|
|
+ }
|
|
+ if (event.isCancelled()) {
|
|
+ return false;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
if (this.passengers.size() == 1 && this.passengers.get(0) == passenger) {
|
|
this.passengers = ImmutableList.of();
|
|
} else {
|
|
@@ -2203,6 +_,7 @@
|
|
passenger.boardingCooldown = 60;
|
|
this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger);
|
|
}
|
|
+ return true; // CraftBukkit
|
|
}
|
|
|
|
protected boolean canAddPassenger(Entity passenger) {
|
|
@@ -2295,8 +_,8 @@
|
|
TeleportTransition portalDestination = this.portalProcess.getPortalDestination(serverLevel, this);
|
|
if (portalDestination != null) {
|
|
ServerLevel level = portalDestination.newLevel();
|
|
- if (serverLevel.getServer().isLevelEnabled(level)
|
|
- && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level))) {
|
|
+ if (this instanceof ServerPlayer // CraftBukkit - always call event for players
|
|
+ || (level != null && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level)))) { // CraftBukkit
|
|
this.teleport(portalDestination);
|
|
}
|
|
}
|
|
@@ -2377,7 +_,7 @@
|
|
}
|
|
|
|
public boolean isCrouching() {
|
|
- return this.hasPose(Pose.CROUCHING);
|
|
+ return this.hasPose(net.minecraft.world.entity.Pose.CROUCHING);
|
|
}
|
|
|
|
public boolean isSprinting() {
|
|
@@ -2393,7 +_,7 @@
|
|
}
|
|
|
|
public boolean isVisuallySwimming() {
|
|
- return this.hasPose(Pose.SWIMMING);
|
|
+ return this.hasPose(net.minecraft.world.entity.Pose.SWIMMING);
|
|
}
|
|
|
|
public boolean isVisuallyCrawling() {
|
|
@@ -2401,6 +_,13 @@
|
|
}
|
|
|
|
public void setSwimming(boolean swimming) {
|
|
+ // CraftBukkit start
|
|
+ if (this.valid && this.isSwimming() != swimming && this instanceof net.minecraft.world.entity.LivingEntity) {
|
|
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callToggleSwimEvent((net.minecraft.world.entity.LivingEntity) this, swimming).isCancelled()) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ // CraftBukkit end
|
|
this.setSharedFlag(4, swimming);
|
|
}
|
|
|
|
@@ -2439,6 +_,7 @@
|
|
|
|
@Nullable
|
|
public PlayerTeam getTeam() {
|
|
+ if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof Player)) { return null; } // Paper - Perf: Disable Scoreboards for non players by default
|
|
return this.level().getScoreboard().getPlayersTeam(this.getScoreboardName());
|
|
}
|
|
|
|
@@ -2455,7 +_,11 @@
|
|
}
|
|
|
|
public void setInvisible(boolean invisible) {
|
|
- this.setSharedFlag(5, invisible);
|
|
+ // CraftBukkit - start
|
|
+ if (!this.persistentInvisibility) { // Prevent Minecraft from removing our invisibility flag
|
|
+ this.setSharedFlag(5, invisible);
|
|
+ }
|
|
+ // CraftBukkit - end
|
|
}
|
|
|
|
public boolean getSharedFlag(int flag) {
|
|
@@ -2472,7 +_,7 @@
|
|
}
|
|
|
|
public int getMaxAirSupply() {
|
|
- return 300;
|
|
+ return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
|
|
}
|
|
|
|
public int getAirSupply() {
|
|
@@ -2480,7 +_,18 @@
|
|
}
|
|
|
|
public void setAirSupply(int air) {
|
|
- this.entityData.set(DATA_AIR_SUPPLY_ID, air);
|
|
+ // CraftBukkit start
|
|
+ org.bukkit.event.entity.EntityAirChangeEvent event = new org.bukkit.event.entity.EntityAirChangeEvent(this.getBukkitEntity(), air);
|
|
+ // Suppress during worldgen
|
|
+ if (this.valid) {
|
|
+ event.getEntity().getServer().getPluginManager().callEvent(event);
|
|
+ }
|
|
+ if (event.isCancelled() && this.getAirSupply() != air) {
|
|
+ this.entityData.markDirty(Entity.DATA_AIR_SUPPLY_ID);
|
|
+ return;
|
|
+ }
|
|
+ this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount());
|
|
+ // CraftBukkit end
|
|
}
|
|
|
|
public int getTicksFrozen() {
|
|
@@ -2506,11 +_,43 @@
|
|
|
|
public void thunderHit(ServerLevel level, LightningBolt lightning) {
|
|
this.setRemainingFireTicks(this.remainingFireTicks + 1);
|
|
+ // CraftBukkit start
|
|
+ final org.bukkit.entity.Entity thisBukkitEntity = this.getBukkitEntity();
|
|
+ final org.bukkit.entity.Entity stormBukkitEntity = lightning.getBukkitEntity();
|
|
+ final org.bukkit.plugin.PluginManager pluginManager = org.bukkit.Bukkit.getPluginManager();
|
|
+ // CraftBukkit end
|
|
if (this.remainingFireTicks == 0) {
|
|
- this.igniteForSeconds(8.0F);
|
|
- }
|
|
-
|
|
- this.hurtServer(level, this.damageSources().lightningBolt(), 5.0F);
|
|
+ // CraftBukkit start - Call a combust event when lightning strikes
|
|
+ org.bukkit.event.entity.EntityCombustByEntityEvent entityCombustEvent = new org.bukkit.event.entity.EntityCombustByEntityEvent(stormBukkitEntity, thisBukkitEntity, 8.0F);
|
|
+ pluginManager.callEvent(entityCombustEvent);
|
|
+ if (!entityCombustEvent.isCancelled()) {
|
|
+ this.igniteForSeconds(entityCombustEvent.getDuration(), false);
|
|
+ // Paper start - fix EntityCombustEvent cancellation
|
|
+ } else {
|
|
+ this.setRemainingFireTicks(this.remainingFireTicks - 1);
|
|
+ // Paper end - fix EntityCombustEvent cancellation
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ }
|
|
+
|
|
+ // CraftBukkit start
|
|
+ if (thisBukkitEntity instanceof org.bukkit.entity.Hanging) {
|
|
+ org.bukkit.event.hanging.HangingBreakByEntityEvent hangingEvent = new org.bukkit.event.hanging.HangingBreakByEntityEvent((org.bukkit.entity.Hanging) thisBukkitEntity, stormBukkitEntity);
|
|
+ pluginManager.callEvent(hangingEvent);
|
|
+
|
|
+ if (hangingEvent.isCancelled()) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (this.fireImmune()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!this.hurtServer(level, this.damageSources().lightningBolt().customEventDamager(lightning), 5.0F)) { // Paper - fix DamageSource API
|
|
+ return;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
}
|
|
|
|
public void onAboveBubbleCol(boolean downwards) {
|
|
@@ -2636,26 +_,30 @@
|
|
return this.removalReason != null
|
|
? String.format(
|
|
Locale.ROOT,
|
|
- "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f, removed=%s]",
|
|
+ "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b, removed=%s]", // Paper - add more info
|
|
this.getClass().getSimpleName(),
|
|
this.getName().getString(),
|
|
this.id,
|
|
+ this.uuid, // Paper - add more info
|
|
string,
|
|
this.getX(),
|
|
this.getY(),
|
|
this.getZ(),
|
|
+ this.chunkPosition(), this.tickCount, this.valid, // Paper - add more info
|
|
this.removalReason
|
|
)
|
|
: String.format(
|
|
Locale.ROOT,
|
|
- "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]",
|
|
+ "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b]", // Paper - add more info
|
|
this.getClass().getSimpleName(),
|
|
this.getName().getString(),
|
|
this.id,
|
|
+ this.uuid, // Paper - add more info
|
|
string,
|
|
this.getX(),
|
|
this.getY(),
|
|
- this.getZ()
|
|
+ this.getZ(),
|
|
+ this.chunkPosition(), this.tickCount, this.valid // Paper - add more info
|
|
);
|
|
}
|
|
|
|
@@ -2679,6 +_,13 @@
|
|
}
|
|
|
|
public void restoreFrom(Entity entity) {
|
|
+ // Paper start - Forward CraftEntity in teleport command
|
|
+ org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity = entity.bukkitEntity;
|
|
+ if (bukkitEntity != null) {
|
|
+ bukkitEntity.setHandle(this);
|
|
+ this.bukkitEntity = bukkitEntity;
|
|
+ }
|
|
+ // Paper end - Forward CraftEntity in teleport command
|
|
CompoundTag compoundTag = entity.saveWithoutId(new CompoundTag());
|
|
compoundTag.remove("Dimension");
|
|
this.load(compoundTag);
|
|
@@ -2688,7 +_,56 @@
|
|
|
|
@Nullable
|
|
public Entity teleport(TeleportTransition teleportTransition) {
|
|
+ // Paper start - Fix item duplication and teleport issues
|
|
+ if ((!this.isAlive() || !this.valid) && (teleportTransition.newLevel() != this.level)) {
|
|
+ LOGGER.warn("Illegal Entity Teleport " + this + " to " + teleportTransition.newLevel() + ":" + teleportTransition.position(), new Throwable());
|
|
+ return null;
|
|
+ }
|
|
+ // Paper end - Fix item duplication and teleport issues
|
|
if (this.level() instanceof ServerLevel serverLevel && !this.isRemoved()) {
|
|
+ // CraftBukkit start
|
|
+ PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
|
|
+ Vec3 velocity = absolutePosition.deltaMovement(); // Paper
|
|
+ org.bukkit.Location to = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(absolutePosition.position(), teleportTransition.newLevel().getWorld(), absolutePosition.yRot(), absolutePosition.xRot());
|
|
+ // Paper start - gateway-specific teleport event
|
|
+ final org.bukkit.event.entity.EntityTeleportEvent teleEvent;
|
|
+ if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.level.getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) {
|
|
+ teleEvent = new com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent(this.getBukkitEntity(), this.getBukkitEntity().getLocation(), to, new org.bukkit.craftbukkit.block.CraftEndGateway(to.getWorld(), theEndGatewayBlockEntity));
|
|
+ teleEvent.callEvent();
|
|
+ } else {
|
|
+ teleEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTeleportEvent(this, to);
|
|
+ }
|
|
+ // Paper end - gateway-specific teleport event
|
|
+ if (teleEvent.isCancelled() || teleEvent.getTo() == null) {
|
|
+ return null;
|
|
+ }
|
|
+ if (!to.equals(teleEvent.getTo())) {
|
|
+ to = teleEvent.getTo();
|
|
+ teleportTransition = new TeleportTransition(((org.bukkit.craftbukkit.CraftWorld) to.getWorld()).getHandle(), org.bukkit.craftbukkit.util.CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch(), teleportTransition.missingRespawnBlock(), teleportTransition.asPassenger(), Set.of(), teleportTransition.postTeleportTransition(), teleportTransition.cause());
|
|
+ // Paper start - Call EntityPortalExitEvent
|
|
+ velocity = Vec3.ZERO;
|
|
+ }
|
|
+ if (this.portalProcess != null) { // if in a portal
|
|
+ org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity = this.getBukkitEntity();
|
|
+ org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent(
|
|
+ bukkitEntity,
|
|
+ bukkitEntity.getLocation(), to.clone(),
|
|
+ bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(velocity)
|
|
+ );
|
|
+ event.callEvent();
|
|
+
|
|
+ // Only change the target if actually needed, since we reset relative flags
|
|
+ if (!event.isCancelled() && event.getTo() != null && (!event.getTo().equals(event.getFrom()) || !event.getAfter().equals(event.getBefore()))) {
|
|
+ to = event.getTo().clone();
|
|
+ velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter());
|
|
+ teleportTransition = new TeleportTransition(((org.bukkit.craftbukkit.CraftWorld) to.getWorld()).getHandle(), org.bukkit.craftbukkit.util.CraftLocation.toVec3D(to), velocity, to.getYaw(), to.getPitch(), teleportTransition.missingRespawnBlock(), teleportTransition.asPassenger(), Set.of(), teleportTransition.postTeleportTransition(), teleportTransition.cause());
|
|
+ }
|
|
+ }
|
|
+ if (this.isRemoved()) {
|
|
+ return null;
|
|
+ }
|
|
+ // Paper end - Call EntityPortalExitEvent
|
|
+ // CraftBukkit end
|
|
ServerLevel level = teleportTransition.newLevel();
|
|
boolean flag = level.dimension() != serverLevel.dimension();
|
|
if (!teleportTransition.asPassenger()) {
|
|
@@ -2737,10 +_,19 @@
|
|
profilerFiller.pop();
|
|
return null;
|
|
} else {
|
|
+ // Paper start - Fix item duplication and teleport issues
|
|
+ if (this instanceof Leashable leashable) {
|
|
+ leashable.dropLeash(); // Paper drop lead
|
|
+ }
|
|
+ // Paper end - Fix item duplication and teleport issues
|
|
entityx.restoreFrom(this);
|
|
this.removeAfterChangingDimensions();
|
|
+ // CraftBukkit start - Forward the CraftEntity to the new entity
|
|
+ //this.getBukkitEntity().setHandle(entity);
|
|
+ //entity.bukkitEntity = this.getBukkitEntity(); // Paper - forward CraftEntity in teleport command; moved to Entity#restoreFrom
|
|
+ // CraftBukkit end
|
|
entityx.teleportSetPosition(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives());
|
|
- level.addDuringTeleport(entityx);
|
|
+ if (this.inWorld) level.addDuringTeleport(entityx); // CraftBukkit - Don't spawn the new entity if the current entity isn't spawned
|
|
|
|
for (Entity entity2 : list) {
|
|
entity2.startRiding(entityx, true);
|
|
@@ -2814,9 +_,17 @@
|
|
}
|
|
|
|
protected void removeAfterChangingDimensions() {
|
|
- this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION);
|
|
- if (this instanceof Leashable leashable) {
|
|
- leashable.removeLeash();
|
|
+ this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION, null); // CraftBukkit - add Bukkit remove cause
|
|
+ if (this instanceof Leashable leashable && leashable.isLeashed()) { // Paper - only call if it is leashed
|
|
+ // Paper start - Expand EntityUnleashEvent
|
|
+ final org.bukkit.event.entity.EntityUnleashEvent event = new org.bukkit.event.entity.EntityUnleashEvent(this.getBukkitEntity(), org.bukkit.event.entity.EntityUnleashEvent.UnleashReason.UNKNOWN, false); // CraftBukkit
|
|
+ event.callEvent();
|
|
+ if (!event.isDropLeash()) {
|
|
+ leashable.removeLeash();
|
|
+ } else {
|
|
+ leashable.dropLeash();
|
|
+ }
|
|
+ // Paper end - Expand EntityUnleashEvent
|
|
}
|
|
}
|
|
|
|
@@ -2824,11 +_,34 @@
|
|
return PortalShape.getRelativePosition(portal, axis, this.position(), this.getDimensions(this.getPose()));
|
|
}
|
|
|
|
+ // CraftBukkit start
|
|
+ public org.bukkit.craftbukkit.event.CraftPortalEvent callPortalEvent(Entity entity, org.bukkit.Location exit, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, int searchRadius, int creationRadius) {
|
|
+ org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity();
|
|
+ org.bukkit.Location enter = bukkitEntity.getLocation();
|
|
+
|
|
+ // Paper start
|
|
+ final org.bukkit.PortalType portalType = switch (cause) {
|
|
+ case END_PORTAL -> org.bukkit.PortalType.ENDER;
|
|
+ case NETHER_PORTAL -> org.bukkit.PortalType.NETHER;
|
|
+ case END_GATEWAY -> org.bukkit.PortalType.END_GATEWAY; // not actually used yet
|
|
+ default -> org.bukkit.PortalType.CUSTOM;
|
|
+ };
|
|
+ org.bukkit.event.entity.EntityPortalEvent event = new org.bukkit.event.entity.EntityPortalEvent(bukkitEntity, enter, exit, searchRadius, true, creationRadius, portalType);
|
|
+ // Paper end
|
|
+ event.getEntity().getServer().getPluginManager().callEvent(event);
|
|
+ if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !entity.isAlive()) {
|
|
+ return null;
|
|
+ }
|
|
+ return new org.bukkit.craftbukkit.event.CraftPortalEvent(event);
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
public boolean canUsePortal(boolean allowPassengers) {
|
|
return (allowPassengers || !this.isPassenger()) && this.isAlive();
|
|
}
|
|
|
|
public boolean canTeleport(Level fromLevel, Level toLevel) {
|
|
+ if (!this.isAlive() || !this.valid) return false; // Paper - Fix item duplication and teleport issues
|
|
if (fromLevel.dimension() == Level.END && toLevel.dimension() == Level.OVERWORLD) {
|
|
for (Entity entity : this.getPassengers()) {
|
|
if (entity instanceof ServerPlayer serverPlayer && !serverPlayer.seenCredits) {
|
|
@@ -2936,9 +_,14 @@
|
|
return this.entityData.get(DATA_CUSTOM_NAME_VISIBLE);
|
|
}
|
|
|
|
- public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera) {
|
|
+ // CraftBukkit start
|
|
+ public final boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera) {
|
|
+ return this.teleportTo(level, x, y, z, relativeMovements, yaw, pitch, setCamera, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN);
|
|
+ }
|
|
+ public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<Relative> relativeMovements, float yaw, float pitch, boolean setCamera, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
|
|
+ // CraftBukkit end
|
|
float f = Mth.clamp(pitch, -90.0F, 90.0F);
|
|
- Entity entity = this.teleport(new TeleportTransition(level, new Vec3(x, y, z), Vec3.ZERO, yaw, f, relativeMovements, TeleportTransition.DO_NOTHING));
|
|
+ Entity entity = this.teleport(new TeleportTransition(level, new Vec3(x, y, z), Vec3.ZERO, yaw, f, relativeMovements, TeleportTransition.DO_NOTHING, cause)); // CraftBukkit
|
|
return entity != null;
|
|
}
|
|
|
|
@@ -3052,7 +_,26 @@
|
|
}
|
|
|
|
public final void setBoundingBox(AABB bb) {
|
|
- this.bb = bb;
|
|
+ // CraftBukkit start - block invalid bounding boxes
|
|
+ double minX = bb.minX,
|
|
+ minY = bb.minY,
|
|
+ minZ = bb.minZ,
|
|
+ maxX = bb.maxX,
|
|
+ maxY = bb.maxY,
|
|
+ maxZ = bb.maxZ;
|
|
+ double len = bb.maxX - bb.minX;
|
|
+ if (len < 0) maxX = minX;
|
|
+ if (len > 64) maxX = minX + 64.0;
|
|
+
|
|
+ len = bb.maxY - bb.minY;
|
|
+ if (len < 0) maxY = minY;
|
|
+ if (len > 64) maxY = minY + 64.0;
|
|
+
|
|
+ len = bb.maxZ - bb.minZ;
|
|
+ if (len < 0) maxZ = minZ;
|
|
+ if (len > 64) maxZ = minZ + 64.0;
|
|
+ this.bb = new AABB(minX, minY, minZ, maxX, maxY, maxZ);
|
|
+ // CraftBukkit end
|
|
}
|
|
|
|
public final float getEyeHeight(Pose pose) {
|
|
@@ -3096,6 +_,12 @@
|
|
}
|
|
|
|
public void stopSeenByPlayer(ServerPlayer serverPlayer) {
|
|
+ // Paper start - entity tracking events
|
|
+ // Since this event cannot be cancelled, we should call it here to catch all "un-tracks"
|
|
+ if (io.papermc.paper.event.player.PlayerUntrackEntityEvent.getHandlerList().getRegisteredListeners().length > 0) {
|
|
+ new io.papermc.paper.event.player.PlayerUntrackEntityEvent(serverPlayer.getBukkitEntity(), this.getBukkitEntity()).callEvent();
|
|
+ }
|
|
+ // Paper end - entity tracking events
|
|
}
|
|
|
|
public float rotate(Rotation transformRotation) {
|
|
@@ -3129,7 +_,7 @@
|
|
}
|
|
|
|
@Nullable
|
|
- public LivingEntity getControllingPassenger() {
|
|
+ public net.minecraft.world.entity.LivingEntity getControllingPassenger() {
|
|
return null;
|
|
}
|
|
|
|
@@ -3161,21 +_,32 @@
|
|
}
|
|
|
|
private Stream<Entity> getIndirectPassengersStream() {
|
|
+ if (this.passengers.isEmpty()) { return Stream.of(); } // Paper - Optimize indirect passenger iteration
|
|
return this.passengers.stream().flatMap(Entity::getSelfAndPassengers);
|
|
}
|
|
|
|
@Override
|
|
public Stream<Entity> getSelfAndPassengers() {
|
|
+ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration
|
|
return Stream.concat(Stream.of(this), this.getIndirectPassengersStream());
|
|
}
|
|
|
|
@Override
|
|
public Stream<Entity> getPassengersAndSelf() {
|
|
+ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration
|
|
return Stream.concat(this.passengers.stream().flatMap(Entity::getPassengersAndSelf), Stream.of(this));
|
|
}
|
|
|
|
public Iterable<Entity> getIndirectPassengers() {
|
|
- return () -> this.getIndirectPassengersStream().iterator();
|
|
+ // Paper start - Optimize indirect passenger iteration
|
|
+ if (this.passengers.isEmpty()) { return ImmutableList.of(); }
|
|
+ ImmutableList.Builder<Entity> indirectPassengers = ImmutableList.builder();
|
|
+ for (Entity passenger : this.passengers) {
|
|
+ indirectPassengers.add(passenger);
|
|
+ indirectPassengers.addAll(passenger.getIndirectPassengers());
|
|
+ }
|
|
+ return indirectPassengers.build();
|
|
+ // Paper end - Optimize indirect passenger iteration
|
|
}
|
|
|
|
public int countPlayerPassengers() {
|
|
@@ -3183,6 +_,7 @@
|
|
}
|
|
|
|
public boolean hasExactlyOnePlayerPassenger() {
|
|
+ if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration
|
|
return this.countPlayerPassengers() == 1;
|
|
}
|
|
|
|
@@ -3260,9 +_,38 @@
|
|
return 1;
|
|
}
|
|
|
|
+ // CraftBukkit start
|
|
+ private final CommandSource commandSource = new CommandSource() {
|
|
+
|
|
+ @Override
|
|
+ public void sendSystemMessage(Component message) {
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
|
|
+ return Entity.this.getBukkitEntity();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean acceptsSuccess() {
|
|
+ return ((ServerLevel) Entity.this.level()).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_SENDCOMMANDFEEDBACK);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean acceptsFailure() {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean shouldInformAdmins() {
|
|
+ return true;
|
|
+ }
|
|
+ };
|
|
+ // CraftBukkit end
|
|
+
|
|
public CommandSourceStack createCommandSourceStackForNameResolution(ServerLevel level) {
|
|
return new CommandSourceStack(
|
|
- CommandSource.NULL, this.position(), this.getRotationVector(), level, 0, this.getName().getString(), this.getDisplayName(), level.getServer(), this
|
|
+ this.commandSource, this.position(), this.getRotationVector(), level, 0, this.getName().getString(), this.getDisplayName(), level.getServer(), this // CraftBukkit
|
|
);
|
|
}
|
|
|
|
@@ -3320,6 +_,11 @@
|
|
vec3 = vec3.add(flow);
|
|
i++;
|
|
}
|
|
+ // CraftBukkit start - store last lava contact location
|
|
+ if (fluidTag == FluidTags.LAVA) {
|
|
+ this.lastLavaContact = mutableBlockPos.immutable();
|
|
+ }
|
|
+ // CraftBukkit end
|
|
}
|
|
}
|
|
}
|
|
@@ -3417,7 +_,9 @@
|
|
}
|
|
|
|
public void setDeltaMovement(Vec3 deltaMovement) {
|
|
+ synchronized (this.posLock) { // Paper - detailed watchdog information
|
|
this.deltaMovement = deltaMovement;
|
|
+ } // Paper - detailed watchdog information
|
|
}
|
|
|
|
public void addDeltaMovement(Vec3 addend) {
|
|
@@ -3480,9 +_,43 @@
|
|
return this.getZ((2.0 * this.random.nextDouble() - 1.0) * scale);
|
|
}
|
|
|
|
+ // Paper start - Block invalid positions and bounding box
|
|
+ public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) {
|
|
+ if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ String entityInfo;
|
|
+ try {
|
|
+ entityInfo = entity.toString();
|
|
+ } catch (Exception ex) {
|
|
+ entityInfo = "[Entity info unavailable] ";
|
|
+ }
|
|
+ LOGGER.error("New entity position is invalid! Tried to set invalid position ({},{},{}) for entity {} located at {}, entity info: {}", newX, newY, newZ, entity.getClass().getName(), entity.position, entityInfo, new Throwable());
|
|
+ return false;
|
|
+ }
|
|
public final void setPosRaw(double x, double y, double z) {
|
|
+ this.setPosRaw(x, y, z, false);
|
|
+ }
|
|
+ public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
|
|
+ if (!checkPosition(this, x, y, z)) {
|
|
+ return;
|
|
+ }
|
|
+ // Paper end - Block invalid positions and bounding box
|
|
+ // Paper start - Fix MC-4
|
|
+ if (this instanceof ItemEntity) {
|
|
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) {
|
|
+ // encode/decode from ClientboundMoveEntityPacket
|
|
+ x = Mth.lfloor(x * 4096.0) * (1 / 4096.0);
|
|
+ y = Mth.lfloor(y * 4096.0) * (1 / 4096.0);
|
|
+ z = Mth.lfloor(z * 4096.0) * (1 / 4096.0);
|
|
+ }
|
|
+ }
|
|
+ // Paper end - Fix MC-4
|
|
if (this.position.x != x || this.position.y != y || this.position.z != z) {
|
|
+ synchronized (this.posLock) { // Paper - detailed watchdog information
|
|
this.position = new Vec3(x, y, z);
|
|
+ } // Paper - detailed watchdog information
|
|
int floor = Mth.floor(x);
|
|
int floor1 = Mth.floor(y);
|
|
int floor2 = Mth.floor(z);
|
|
@@ -3496,6 +_,12 @@
|
|
|
|
this.levelCallback.onMove();
|
|
}
|
|
+ // Paper start - Block invalid positions and bounding box; don't allow desync of pos and AABB
|
|
+ // hanging has its own special logic
|
|
+ if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) {
|
|
+ this.setBoundingBox(this.makeBoundingBox());
|
|
+ }
|
|
+ // Paper end - Block invalid positions and bounding box
|
|
}
|
|
|
|
public void checkDespawn() {
|
|
@@ -3583,6 +_,15 @@
|
|
|
|
@Override
|
|
public final void setRemoved(Entity.RemovalReason removalReason) {
|
|
+ // CraftBukkit start - add Bukkit remove cause
|
|
+ this.setRemoved(removalReason, null);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public final void setRemoved(Entity.RemovalReason removalReason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) {
|
|
+ org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause);
|
|
+ // CraftBukkit end
|
|
+ final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
|
|
if (this.removalReason == null) {
|
|
this.removalReason = removalReason;
|
|
}
|
|
@@ -3594,12 +_,28 @@
|
|
this.getPassengers().forEach(Entity::stopRiding);
|
|
this.levelCallback.onRemove(removalReason);
|
|
this.onRemoval(removalReason);
|
|
+ // Paper start - Folia schedulers
|
|
+ if (!(this instanceof ServerPlayer) && removalReason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) {
|
|
+ // Players need to be special cased, because they are regularly removed from the world
|
|
+ this.retireScheduler();
|
|
+ }
|
|
+ // Paper end - Folia schedulers
|
|
}
|
|
|
|
public void unsetRemoved() {
|
|
this.removalReason = null;
|
|
}
|
|
|
|
+ // Paper start - Folia schedulers
|
|
+ /**
|
|
+ * Invoked only when the entity is truly removed from the server, never to be added to any world.
|
|
+ */
|
|
+ public final void retireScheduler() {
|
|
+ // we need to force create the bukkit entity so that the scheduler can be retired...
|
|
+ this.getBukkitEntity().taskScheduler.retire();
|
|
+ }
|
|
+ // Paper end - Folia schedulers
|
|
+
|
|
@Override
|
|
public void setLevelCallback(EntityInLevelCallback levelCallback) {
|
|
this.levelCallback = levelCallback;
|
|
@@ -3723,4 +_,14 @@
|
|
return this.save;
|
|
}
|
|
}
|
|
+
|
|
+ // Paper start - Expose entity id counter
|
|
+ public static int nextEntityId() {
|
|
+ return ENTITY_COUNTER.incrementAndGet();
|
|
+ }
|
|
+
|
|
+ public boolean isTicking() {
|
|
+ return ((ServerLevel) this.level).isPositionEntityTicking(this.blockPosition());
|
|
+ }
|
|
+ // Paper end - Expose entity id counter
|
|
}
|