0
0
mirror of https://github.com/PaperMC/Paper.git synced 2025-02-12 09:18:14 +00:00
Spottedleaf e2f0efd1af Remove nms.Entity#isChunkLoaded
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.
2025-01-28 17:33:48 -08:00

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
}