diff options
Diffstat (limited to 'paper-server/patches/unapplied/net/minecraft/world/entity/Entity.java.patch')
-rw-r--r-- | paper-server/patches/unapplied/net/minecraft/world/entity/Entity.java.patch | 1994 |
1 files changed, 1994 insertions, 0 deletions
diff --git a/paper-server/patches/unapplied/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/entity/Entity.java.patch new file mode 100644 index 0000000000..a274089f56 --- /dev/null +++ b/paper-server/patches/unapplied/net/minecraft/world/entity/Entity.java.patch @@ -0,0 +1,1994 @@ +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -59,6 +59,8 @@ + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientGamePacketListener; + import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; ++import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; ++import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; + import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket; + import net.minecraft.network.protocol.game.VecDeltaCodec; + import net.minecraft.network.syncher.EntityDataAccessor; +@@ -101,8 +103,6 @@ + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.ClipContext; + import net.minecraft.world.level.Explosion; +-import net.minecraft.world.level.ItemLike; +-import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.FenceGateBlock; +@@ -138,9 +138,153 @@ + import net.minecraft.world.scores.ScoreHolder; + import net.minecraft.world.scores.Team; + import org.slf4j.Logger; ++import net.minecraft.world.level.GameRules; ++import net.minecraft.world.level.ItemLike; ++import net.minecraft.world.level.Level; ++import org.bukkit.Bukkit; ++import org.bukkit.Location; ++import org.bukkit.Server; ++import org.bukkit.block.BlockFace; ++import org.bukkit.command.CommandSender; ++import org.bukkit.entity.Hanging; ++import org.bukkit.entity.LivingEntity; ++import org.bukkit.entity.Vehicle; ++import org.bukkit.event.entity.EntityCombustByEntityEvent; ++import org.bukkit.event.hanging.HangingBreakByEntityEvent; ++import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; ++import org.bukkit.event.vehicle.VehicleEnterEvent; ++import org.bukkit.event.vehicle.VehicleExitEvent; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.event.CraftPortalEvent; ++import org.bukkit.craftbukkit.util.CraftLocation; ++import org.bukkit.entity.Pose; ++import org.bukkit.event.entity.EntityAirChangeEvent; ++import org.bukkit.event.entity.EntityCombustEvent; ++import org.bukkit.event.entity.EntityDismountEvent; ++import org.bukkit.event.entity.EntityDropItemEvent; ++import org.bukkit.event.entity.EntityMountEvent; ++import org.bukkit.event.entity.EntityPortalEvent; ++import org.bukkit.event.entity.EntityPoseChangeEvent; ++import org.bukkit.event.entity.EntityRemoveEvent; ++import org.bukkit.event.entity.EntityTeleportEvent; ++import org.bukkit.event.entity.EntityUnleashEvent; ++import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; ++import org.bukkit.event.player.PlayerTeleportEvent; ++import org.bukkit.plugin.PluginManager; ++// CraftBukkit end + + 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 CraftEntity bukkitEntity; ++ ++ public CraftEntity getBukkitEntity() { ++ if (this.bukkitEntity == null) { ++ // Paper start - Folia schedulers ++ synchronized (this) { ++ if (this.bukkitEntity == null) { ++ return this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); ++ } ++ } ++ // Paper end - Folia schedulers ++ } ++ return this.bukkitEntity; ++ } ++ // Paper start ++ public CraftEntity getBukkitEntityRaw() { ++ return this.bukkitEntity; ++ } ++ // Paper end ++ ++ // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() ++ public int getDefaultMaxAirSupply() { ++ return Entity.TOTAL_AIR_SUPPLY; ++ } ++ // CraftBukkit end ++ + private static final Logger LOGGER = LogUtils.getLogger(); + public static final String ID_TAG = "id"; + public static final String PASSENGERS_TAG = "Passengers"; +@@ -224,7 +368,7 @@ + private static final EntityDataAccessor<Boolean> DATA_CUSTOM_NAME_VISIBLE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN); + private static final EntityDataAccessor<Boolean> DATA_SILENT = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN); + private static final EntityDataAccessor<Boolean> DATA_NO_GRAVITY = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN); +- protected static final EntityDataAccessor<Pose> DATA_POSE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.POSE); ++ protected static final EntityDataAccessor<net.minecraft.world.entity.Pose> DATA_POSE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.POSE); + private static final EntityDataAccessor<Integer> DATA_TICKS_FROZEN = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.INT); + private EntityInLevelCallback levelCallback; + private final VecDeltaCodec packetPositionCodec; +@@ -253,15 +397,78 @@ + private final List<Entity.Movement> movementThisTick; + private final Set<BlockState> blocksInside; + private final LongSet visitedBlocks; ++ // 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; ++ // Spigot start ++ public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); ++ public final boolean defaultActivationState; ++ public long activatedTick = Integer.MIN_VALUE; ++ public void inactiveTick() { } ++ // Spigot end ++ 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 void setOrigin(@javax.annotation.Nonnull 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; ++ } ++ ++ public boolean isChunkLoaded() { ++ return this.level.hasChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); ++ } ++ // 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<?> type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); ++ this.despawnTime = type == EntityType.PLAYER ? -1 : world.paperConfig().entities.spawning.despawnTime.getOrDefault(type, io.papermc.paper.configuration.type.number.IntOr.Disabled.DISABLED).or(-1); // Paper - entity despawn time limit + this.passengers = ImmutableList.of(); + this.deltaMovement = Vec3.ZERO; + this.bb = Entity.INITIAL_AABB; + this.stuckSpeedMultiplier = Vec3.ZERO; + this.nextStep = 1.0F; +- this.random = RandomSource.create(); ++ this.random = SHARED_RANDOM; // Paper - Share random for entities to make them more random + this.remainingFireTicks = -this.getFireImmuneTicks(); + this.fluidHeight = new Object2DoubleArrayMap(2); + this.fluidOnEyes = new HashSet(); +@@ -270,7 +477,7 @@ + this.packetPositionCodec = new VecDeltaCodec(); + this.uuid = Mth.createInsecureUUID(this.random); + this.stringUUID = this.uuid.toString(); +- this.tags = Sets.newHashSet(); ++ this.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 + this.pistonDeltas = new double[]{0.0D, 0.0D, 0.0D}; + this.mainSupportingBlockPos = Optional.empty(); + this.onGroundNoBlocks = false; +@@ -284,6 +491,13 @@ + this.position = Vec3.ZERO; + this.blockPosition = BlockPos.ZERO; + this.chunkPosition = ChunkPos.ZERO; ++ // Spigot start ++ if (world != null) { ++ this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, world.spigotConfig); ++ } else { ++ this.defaultActivationState = false; ++ } ++ // Spigot end + SynchedEntityData.Builder datawatcher_a = new SynchedEntityData.Builder(this); + + datawatcher_a.define(Entity.DATA_SHARED_FLAGS_ID, (byte) 0); +@@ -292,7 +506,7 @@ + datawatcher_a.define(Entity.DATA_CUSTOM_NAME, Optional.empty()); + datawatcher_a.define(Entity.DATA_SILENT, false); + datawatcher_a.define(Entity.DATA_NO_GRAVITY, false); +- datawatcher_a.define(Entity.DATA_POSE, Pose.STANDING); ++ datawatcher_a.define(Entity.DATA_POSE, net.minecraft.world.entity.Pose.STANDING); + datawatcher_a.define(Entity.DATA_TICKS_FROZEN, 0); + this.defineSynchedData(datawatcher_a); + this.entityData = datawatcher_a.build(); +@@ -354,7 +568,7 @@ + } + + public boolean addTag(String tag) { +- return this.tags.size() >= 1024 ? false : this.tags.add(tag); ++ return this.tags.add(tag); // Paper - fully limit tag size - replace set impl + } + + public boolean removeTag(String tag) { +@@ -362,20 +576,68 @@ + } + + public void kill(ServerLevel world) { +- this.remove(Entity.RemovalReason.KILLED); ++ this.remove(Entity.RemovalReason.KILLED, 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(EntityRemoveEvent.Cause cause) { ++ this.remove(Entity.RemovalReason.DISCARDED, cause); ++ // CraftBukkit end ++ } ++ + protected abstract void defineSynchedData(SynchedEntityData.Builder builder); + + public SynchedEntityData getEntityData() { + 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 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 ClientboundSetEntityDataPacket(this.id, values)); ++ } ++ // Paper end ++ + public boolean equals(Object object) { + return object instanceof Entity ? ((Entity) object).id == this.id : false; + } +@@ -385,22 +647,39 @@ + } + + public void remove(Entity.RemovalReason reason) { +- this.setRemoved(reason); ++ // CraftBukkit start - add Bukkit remove cause ++ this.setRemoved(reason, null); + } + ++ public void remove(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ this.setRemoved(entity_removalreason, cause); ++ // CraftBukkit end ++ } ++ + public void onClientRemoval() {} + + public void onRemoval(Entity.RemovalReason reason) {} + +- public void setPose(Pose pose) { ++ public void setPose(net.minecraft.world.entity.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 EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()])); ++ } ++ // Paper end - Don't fire sync event during generation ++ // CraftBukkit end + this.entityData.set(Entity.DATA_POSE, pose); + } + +- public Pose getPose() { +- return (Pose) this.entityData.get(Entity.DATA_POSE); ++ public net.minecraft.world.entity.Pose getPose() { ++ return (net.minecraft.world.entity.Pose) this.entityData.get(Entity.DATA_POSE); + } + +- public boolean hasPose(Pose pose) { ++ public boolean hasPose(net.minecraft.world.entity.Pose pose) { + return this.getPose() == pose; + } + +@@ -417,6 +696,33 @@ + } + + public void setRot(float yaw, float pitch) { ++ // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0 ++ if (Float.isNaN(yaw)) { ++ yaw = 0; ++ } ++ ++ if (yaw == Float.POSITIVE_INFINITY || yaw == 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"); ++ ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite yaw (Hacking?)"); ++ } ++ yaw = 0; ++ } ++ ++ // pitch was sometimes set to NaN, so we need to set it back to 0 ++ if (Float.isNaN(pitch)) { ++ pitch = 0; ++ } ++ ++ if (pitch == Float.POSITIVE_INFINITY || pitch == 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"); ++ ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite pitch (Hacking?)"); ++ } ++ pitch = 0; ++ } ++ // CraftBukkit end ++ + this.setYRot(yaw % 360.0F); + this.setXRot(pitch % 360.0F); + } +@@ -426,8 +732,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() { +@@ -459,13 +765,29 @@ + } + + 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 gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.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(); +@@ -475,7 +797,7 @@ + --this.boardingCooldown; + } + +- this.handlePortal(); ++ if (this instanceof ServerPlayer) this.handlePortal(); // CraftBukkit - // Moved up to postTick + if (this.canSpawnSprintParticle()) { + this.spawnSprintParticle(); + } +@@ -502,7 +824,7 @@ + this.setRemainingFireTicks(this.remainingFireTicks - 1); + } + +- if (this.getTicksFrozen() > 0) { ++ if (this.getTicksFrozen() > 0 && !freezeLocked) { // Paper - Freeze Tick Lock API + this.setTicksFrozen(0); + this.level().levelEvent((Player) null, 1009, this.blockPosition, 1); + } +@@ -514,6 +836,10 @@ + if (this.isInLava()) { + this.lavaHurt(); + this.fallDistance *= 0.5F; ++ // CraftBukkit start ++ } else { ++ this.lastLavaContact = null; ++ // CraftBukkit end + } + + this.checkBelowWorld(); +@@ -525,7 +851,7 @@ + world = this.level(); + if (world instanceof ServerLevel worldserver) { + if (this instanceof Leashable) { +- Leashable.tickLeash(worldserver, (Entity) ((Leashable) this)); ++ Leashable.tickLeash(worldserver, (Entity & Leashable) this); // CraftBukkit - decompile error + } + } + +@@ -537,7 +863,12 @@ + } + + public void checkBelowWorld() { +- if (this.getY() < (double) (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() < (double) (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(); + } + +@@ -568,15 +899,32 @@ + + 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(); ++ 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 + Level world = this.level(); + + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; + +- if (this.hurtServer(worldserver, this.damageSources().lava(), 4.0F) && this.shouldPlayLavaHurtSound() && !this.isSilent()) { ++ // CraftBukkit start ++ if (this.hurtServer(worldserver, this.damageSources().lava().directBlock(this.level, this.lastLavaContact), 4.0F) && this.shouldPlayLavaHurtSound() && !this.isSilent()) { + worldserver.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.GENERIC_BURN, this.getSoundSource(), 0.4F, 2.0F + this.random.nextFloat() * 0.4F); + } ++ // CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls + } + + } +@@ -587,9 +935,25 @@ + } + + 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) { ++ EntityCombustEvent event = new 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) { + if (this.remainingFireTicks < ticks) { + this.setRemainingFireTicks(ticks); +@@ -610,7 +974,7 @@ + } + + protected void onBelowWorld() { +- this.discard(); ++ this.discard(EntityRemoveEvent.Cause.OUT_OF_WORLD); // CraftBukkit - add Bukkit remove cause + } + + public boolean isFree(double offsetX, double offsetY, double offsetZ) { +@@ -672,6 +1036,7 @@ + } + + public void move(MoverType type, Vec3 movement) { ++ final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity + if (this.noPhysics) { + this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); + } else { +@@ -747,8 +1112,30 @@ + + if (movement.y != vec3d1.y) { + block.updateEntityMovementAfterFallOn(this.level(), this); ++ } ++ } ++ ++ // CraftBukkit start ++ if (this.horizontalCollision && this.getBukkitEntity() instanceof Vehicle) { ++ Vehicle vehicle = (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 > vec3d1.x) { ++ bl = bl.getRelative(BlockFace.EAST); ++ } else if (movement.x < vec3d1.x) { ++ bl = bl.getRelative(BlockFace.WEST); ++ } else if (movement.z > vec3d1.z) { ++ bl = bl.getRelative(BlockFace.SOUTH); ++ } else if (movement.z < vec3d1.z) { ++ bl = bl.getRelative(BlockFace.NORTH); ++ } ++ ++ if (!bl.getType().isAir()) { ++ VehicleBlockCollisionEvent event = new 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 entity_movementemission = this.getMovementEmission(); +@@ -913,7 +1300,7 @@ + } + + protected BlockPos getOnPos(float offset) { +- if (this.mainSupportingBlockPos.isPresent()) { ++ if (this.mainSupportingBlockPos.isPresent() && this.level().getChunkIfLoadedImmediately(this.mainSupportingBlockPos.get()) != null) { // Paper - ensure no loads + BlockPos blockposition = (BlockPos) this.mainSupportingBlockPos.get(); + + if (offset <= 1.0E-5F) { +@@ -1133,6 +1520,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 oldPos, Vec3 newPos) { + this.movementThisTick.add(new Entity.Movement(oldPos, newPos)); + } +@@ -1599,6 +2000,7 @@ + this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F); + this.yRotO = this.getYRot(); + this.xRotO = this.getXRot(); ++ this.setYHeadRot(yaw); // Paper - Update head rotation + } + + public void absMoveTo(double x, double y, double z) { +@@ -1609,6 +2011,7 @@ + this.yo = y; + this.zo = d4; + this.setPos(d3, y, d4); ++ if (this.valid) this.level.getChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); // CraftBukkit + } + + public void moveTo(Vec3 pos) { +@@ -1628,11 +2031,19 @@ + } + + public void moveTo(double x, double y, double z, float yaw, float pitch) { ++ // 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(yaw); + this.setXRot(pitch); + this.setOldPosAndRot(); + this.reapplyPosition(); ++ this.setYHeadRot(yaw); // Paper - Update head rotation + } + + public final void setOldPosAndRot() { +@@ -1701,6 +2112,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 d0 = entity.getX() - this.getX(); + double d1 = entity.getZ() - this.getZ(); + double d2 = Mth.absMax(d0, d1); +@@ -1737,7 +2149,21 @@ + } + + public void push(double deltaX, double deltaY, double deltaZ) { +- this.setDeltaMovement(this.getDeltaMovement().add(deltaX, deltaY, deltaZ)); ++ // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent ++ this.push(deltaX, deltaY, deltaZ, null); ++ } ++ ++ public void push(double deltaX, double deltaY, double deltaZ, @Nullable Entity pushingEntity) { ++ org.bukkit.util.Vector delta = new org.bukkit.util.Vector(deltaX, deltaY, deltaZ); ++ 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; + } + +@@ -1858,9 +2284,21 @@ + } + + 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 entityKilled, DamageSource damageSource) { + if (entityKilled instanceof ServerPlayer) { + CriteriaTriggers.ENTITY_KILLED_PLAYER.trigger((ServerPlayer) entityKilled, this, damageSource); +@@ -1889,74 +2327,133 @@ + } + + public boolean saveAsPassenger(CompoundTag nbt) { ++ // CraftBukkit start - allow excluding certain data when saving ++ return this.saveAsPassenger(nbt, true); ++ } ++ ++ public boolean saveAsPassenger(CompoundTag nbttagcompound, boolean includeAll) { ++ // CraftBukkit end + if (this.removalReason != null && !this.removalReason.shouldSave()) { + return false; + } else { + String s = this.getEncodeId(); + +- if (s == null) { ++ if (!this.persist || s == null) { // CraftBukkit - persist flag + return false; + } else { +- nbt.putString("id", s); +- this.saveWithoutId(nbt); ++ nbttagcompound.putString("id", s); ++ this.saveWithoutId(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll + return true; + } + } + } + ++ // Paper start - Entity serialization api ++ public boolean serializeEntity(CompoundTag compound) { ++ List<Entity> pass = new java.util.ArrayList<>(this.getPassengers()); ++ this.passengers = ImmutableList.of(); ++ boolean result = save(compound); ++ this.passengers = ImmutableList.copyOf(pass); ++ return result; ++ } ++ // Paper end - Entity serialization api + public boolean save(CompoundTag nbt) { + return this.isPassenger() ? false : this.saveAsPassenger(nbt); + } + + public CompoundTag saveWithoutId(CompoundTag nbt) { ++ // CraftBukkit start - allow excluding certain data when saving ++ return this.saveWithoutId(nbt, true); ++ } ++ ++ public CompoundTag saveWithoutId(CompoundTag nbttagcompound, boolean includeAll) { ++ // CraftBukkit end + try { +- if (this.vehicle != null) { +- nbt.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ())); +- } else { +- nbt.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ())); ++ // CraftBukkit start - selectively save position ++ if (includeAll) { ++ if (this.vehicle != null) { ++ nbttagcompound.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ())); ++ } else { ++ nbttagcompound.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ())); ++ } + } ++ // CraftBukkit end + + Vec3 vec3d = this.getDeltaMovement(); + +- nbt.put("Motion", this.newDoubleList(vec3d.x, vec3d.y, vec3d.z)); +- nbt.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot())); +- nbt.putFloat("FallDistance", this.fallDistance); +- nbt.putShort("Fire", (short) this.remainingFireTicks); +- nbt.putShort("Air", (short) this.getAirSupply()); +- nbt.putBoolean("OnGround", this.onGround()); +- nbt.putBoolean("Invulnerable", this.invulnerable); +- nbt.putInt("PortalCooldown", this.portalCooldown); +- nbt.putUUID("UUID", this.getUUID()); ++ nbttagcompound.put("Motion", this.newDoubleList(vec3d.x, vec3d.y, vec3d.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 ++ ++ nbttagcompound.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot())); ++ nbttagcompound.putFloat("FallDistance", this.fallDistance); ++ nbttagcompound.putShort("Fire", (short) this.remainingFireTicks); ++ nbttagcompound.putShort("Air", (short) this.getAirSupply()); ++ nbttagcompound.putBoolean("OnGround", this.onGround()); ++ nbttagcompound.putBoolean("Invulnerable", this.invulnerable); ++ nbttagcompound.putInt("PortalCooldown", this.portalCooldown); ++ // CraftBukkit start - selectively save uuid and world ++ if (includeAll) { ++ nbttagcompound.putUUID("UUID", this.getUUID()); ++ // PAIL: Check above UUID reads 1.8 properly, ie: UUIDMost / UUIDLeast ++ nbttagcompound.putLong("WorldUUIDLeast", ((ServerLevel) this.level).getWorld().getUID().getLeastSignificantBits()); ++ nbttagcompound.putLong("WorldUUIDMost", ((ServerLevel) this.level).getWorld().getUID().getMostSignificantBits()); ++ } ++ nbttagcompound.putInt("Bukkit.updateLevel", Entity.CURRENT_LEVEL); ++ if (!this.persist) { ++ nbttagcompound.putBoolean("Bukkit.persist", this.persist); ++ } ++ if (!this.visibleByDefault) { ++ nbttagcompound.putBoolean("Bukkit.visibleByDefault", this.visibleByDefault); ++ } ++ if (this.persistentInvisibility) { ++ nbttagcompound.putBoolean("Bukkit.invisible", this.persistentInvisibility); ++ } ++ // SPIGOT-6907: re-implement LivingEntity#setMaximumAir() ++ if (this.maxAirTicks != this.getDefaultMaxAirSupply()) { ++ nbttagcompound.putInt("Bukkit.MaxAirSupply", this.getMaxAirSupply()); ++ } ++ nbttagcompound.putInt("Spigot.ticksLived", this.tickCount); ++ // CraftBukkit end + Component ichatbasecomponent = this.getCustomName(); + + if (ichatbasecomponent != null) { +- nbt.putString("CustomName", Component.Serializer.toJson(ichatbasecomponent, this.registryAccess())); ++ nbttagcompound.putString("CustomName", Component.Serializer.toJson(ichatbasecomponent, this.registryAccess())); + } + + if (this.isCustomNameVisible()) { +- nbt.putBoolean("CustomNameVisible", this.isCustomNameVisible()); ++ nbttagcompound.putBoolean("CustomNameVisible", this.isCustomNameVisible()); + } + + if (this.isSilent()) { +- nbt.putBoolean("Silent", this.isSilent()); ++ nbttagcompound.putBoolean("Silent", this.isSilent()); + } + + if (this.isNoGravity()) { +- nbt.putBoolean("NoGravity", this.isNoGravity()); ++ nbttagcompound.putBoolean("NoGravity", this.isNoGravity()); + } + + if (this.hasGlowingTag) { +- nbt.putBoolean("Glowing", true); ++ nbttagcompound.putBoolean("Glowing", true); + } + + int i = this.getTicksFrozen(); + + if (i > 0) { +- nbt.putInt("TicksFrozen", this.getTicksFrozen()); ++ nbttagcompound.putInt("TicksFrozen", this.getTicksFrozen()); + } + + if (this.hasVisualFire) { +- nbt.putBoolean("HasVisualFire", this.hasVisualFire); ++ nbttagcompound.putBoolean("HasVisualFire", this.hasVisualFire); + } + + ListTag nbttaglist; +@@ -1972,10 +2469,10 @@ + nbttaglist.add(StringTag.valueOf(s)); + } + +- nbt.put("Tags", nbttaglist); ++ nbttagcompound.put("Tags", nbttaglist); + } + +- this.addAdditionalSaveData(nbt); ++ this.addAdditionalSaveData(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll + if (this.isVehicle()) { + nbttaglist = new ListTag(); + iterator = this.getPassengers().iterator(); +@@ -1984,17 +2481,44 @@ + Entity entity = (Entity) iterator.next(); + CompoundTag nbttagcompound1 = new CompoundTag(); + +- if (entity.saveAsPassenger(nbttagcompound1)) { ++ if (entity.saveAsPassenger(nbttagcompound1, includeAll)) { // CraftBukkit - pass on includeAll + nbttaglist.add(nbttagcompound1); + } + } + + if (!nbttaglist.isEmpty()) { +- nbt.put("Passengers", nbttaglist); ++ nbttagcompound.put("Passengers", nbttaglist); + } + } + +- return nbt; ++ // CraftBukkit start - stores eventually existing bukkit values ++ if (this.bukkitEntity != null) { ++ this.bukkitEntity.storeBukkitValues(nbttagcompound); ++ } ++ // 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) { ++ nbttagcompound.putUUID("Paper.OriginWorld", originWorld); ++ } ++ nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ())); ++ } ++ if (spawnReason != null) { ++ nbttagcompound.putString("Paper.SpawnReason", spawnReason.name()); ++ } ++ // Save entity's from mob spawner status ++ if (spawnedViaMobSpawner) { ++ nbttagcompound.putBoolean("Paper.FromMobSpawner", true); ++ } ++ if (fromNetherPortal) { ++ nbttagcompound.putBoolean("Paper.FromNetherPortal", true); ++ } ++ if (freezeLocked) { ++ nbttagcompound.putBoolean("Paper.FreezeLock", true); ++ } ++ // Paper end ++ return nbttagcompound; + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being saved"); +@@ -2080,6 +2604,71 @@ + } else { + throw new IllegalStateException("Entity has invalid position"); + } ++ ++ // CraftBukkit start ++ // Spigot start ++ if (this instanceof net.minecraft.world.entity.LivingEntity) { ++ this.tickCount = nbt.getInt("Spigot.ticksLived"); ++ } ++ // Spigot end ++ this.persist = !nbt.contains("Bukkit.persist") || nbt.getBoolean("Bukkit.persist"); ++ this.visibleByDefault = !nbt.contains("Bukkit.visibleByDefault") || nbt.getBoolean("Bukkit.visibleByDefault"); ++ // SPIGOT-6907: re-implement LivingEntity#setMaximumAir() ++ if (nbt.contains("Bukkit.MaxAirSupply")) { ++ this.maxAirTicks = nbt.getInt("Bukkit.MaxAirSupply"); ++ } ++ // CraftBukkit end ++ ++ // CraftBukkit start ++ // Paper - move world parsing/loading to PlayerList#placeNewPlayer ++ this.getBukkitEntity().readBukkitValues(nbt); ++ if (nbt.contains("Bukkit.invisible")) { ++ boolean bukkitInvisible = nbt.getBoolean("Bukkit.invisible"); ++ this.setInvisible(bukkitInvisible); ++ this.persistentInvisibility = bukkitInvisible; ++ } ++ // CraftBukkit end ++ ++ // Paper start ++ ListTag originTag = nbt.getList("Paper.Origin", net.minecraft.nbt.Tag.TAG_DOUBLE); ++ if (!originTag.isEmpty()) { ++ UUID originWorld = null; ++ if (nbt.contains("Paper.OriginWorld")) { ++ originWorld = nbt.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 = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status ++ fromNetherPortal = nbt.getBoolean("Paper.FromNetherPortal"); ++ if (nbt.contains("Paper.SpawnReason")) { ++ String spawnReasonName = nbt.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 (!nbt.getBoolean("PersistenceRequired")) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL; ++ } ++ } ++ } ++ if (spawnReason == null) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; ++ } ++ if (nbt.contains("Paper.FreezeLock")) { ++ freezeLocked = nbt.getBoolean("Paper.FreezeLock"); ++ } ++ // Paper end ++ + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being loaded"); +@@ -2099,7 +2688,13 @@ + ResourceLocation minecraftkey = EntityType.getKey(entitytypes); + + return entitytypes.canSerialize() && minecraftkey != null ? minecraftkey.toString() : null; ++ } ++ ++ // CraftBukkit start - allow excluding certain data when saving ++ protected void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) { ++ this.addAdditionalSaveData(nbttagcompound); + } ++ // CraftBukkit end + + protected abstract void readAdditionalSaveData(CompoundTag nbt); + +@@ -2150,12 +2745,60 @@ + + @Nullable + public ItemEntity spawnAtLocation(ServerLevel world, ItemStack stack, float yOffset) { ++ // Paper start - Restore vanilla drops behavior ++ return this.spawnAtLocation(world, 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 world, 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 entityitem = new ItemEntity(world, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack); ++ // CraftBukkit start - Capture drops for death event ++ if (this instanceof net.minecraft.world.entity.LivingEntity && !((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 entityitem = new ItemEntity(world, this.getX(), this.getY() + (double) 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 + +- entityitem.setDefaultPickUpDelay(); ++ entityitem.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer) ++ // Paper start - Call EntityDropItemEvent ++ return this.spawnAtLocation(world, entityitem); ++ } ++ } ++ @Nullable ++ public ItemEntity spawnAtLocation(ServerLevel world, ItemEntity entityitem) { ++ { ++ // Paper end - Call EntityDropItemEvent ++ // CraftBukkit start ++ EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); ++ Bukkit.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return null; ++ } ++ // CraftBukkit end + world.addFreshEntity(entityitem); + return entityitem; + } +@@ -2184,7 +2827,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 = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials()); ++ if (event.isCancelled()) { ++ // Paper end - Expand EntityUnleashEvent ++ ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder())); ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end ++ if (!event.isDropLeash()) { // Paper - Expand EntityUnleashEvent + leashable.removeLeash(); + } else { + leashable.dropLeash(); +@@ -2200,6 +2852,14 @@ + + if (itemstack.is(Items.LEAD) && leashable.canHaveALeashAttachedToIt()) { + if (!this.level().isClientSide()) { ++ // CraftBukkit start - fire PlayerLeashEntityEvent ++ if (CraftEventFactory.callPlayerLeashEntityEvent(this, player, player, hand).isCancelled()) { ++ // ((ServerPlayer) player).resendItemInHands(); // SPIGOT-7615: Resend to fix client desync with used item // Paper - Fix inventory desync ++ ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, leashable.getLeashHolder())); ++ player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync ++ return InteractionResult.PASS; ++ } ++ // CraftBukkit end + leashable.setLeashedTo(player, true); + } + +@@ -2265,15 +2925,15 @@ + } + + public boolean showVehicleHealth() { +- return this instanceof LivingEntity; ++ return this instanceof net.minecraft.world.entity.LivingEntity; + } + + public boolean startRiding(Entity entity, boolean force) { +- if (entity == this.vehicle) { ++ if (entity == this.vehicle || entity.level != this.level) { // Paper - Ensure entity passenger world matches ridden entity (bad plugins) + return false; + } else if (!entity.couldAcceptPassenger()) { + return false; +- } else if (!this.level().isClientSide() && !entity.type.canSerialize()) { ++ } else if (!force && !this.level().isClientSide() && !entity.type.canSerialize()) { // SPIGOT-7947: Allow force riding all entities + return false; + } else { + for (Entity entity1 = entity; entity1.vehicle != null; entity1 = entity1.vehicle) { +@@ -2285,11 +2945,32 @@ + if (!force && (!this.canRide(entity) || !entity.canAddPassenger(this))) { + return false; + } else { ++ // CraftBukkit start ++ if (entity.getBukkitEntity() instanceof Vehicle && this.getBukkitEntity() instanceof LivingEntity) { ++ VehicleEnterEvent event = new VehicleEnterEvent((Vehicle) entity.getBukkitEntity(), this.getBukkitEntity()); ++ // Suppress during worldgen ++ if (this.valid) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ if (event.isCancelled()) { ++ return false; ++ } ++ } ++ ++ EntityMountEvent event = new EntityMountEvent(this.getBukkitEntity(), entity.getBukkitEntity()); ++ // Suppress during worldgen ++ if (this.valid) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end + if (this.isPassenger()) { + this.stopRiding(); + } + +- this.setPose(Pose.STANDING); ++ this.setPose(net.minecraft.world.entity.Pose.STANDING); + this.vehicle = entity; + this.vehicle.addPassenger(this); + entity.getIndirectPassengersStream().filter((entity2) -> { +@@ -2314,19 +2995,30 @@ + } + + 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) { + if (passenger.getVehicle() != this) { + throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)"); +@@ -2349,21 +3041,53 @@ + } + } + +- protected void removePassenger(Entity passenger) { +- if (passenger.getVehicle() == this) { ++ // Paper start - Force entity dismount during teleportation ++ protected boolean removePassenger(Entity entity) { return removePassenger(entity, false);} ++ protected boolean removePassenger(Entity entity, boolean suppressCancellation) { // CraftBukkit ++ // Paper end - Force entity dismount during teleportation ++ if (entity.getVehicle() == this) { + throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)"); + } else { +- if (this.passengers.size() == 1 && this.passengers.get(0) == passenger) { ++ // CraftBukkit start ++ CraftEntity craft = (CraftEntity) entity.getBukkitEntity().getVehicle(); ++ Entity orig = craft == null ? null : craft.getHandle(); ++ if (this.getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { ++ VehicleExitEvent event = new VehicleExitEvent( ++ (Vehicle) this.getBukkitEntity(), ++ (LivingEntity) entity.getBukkitEntity(), !suppressCancellation // Paper - Force entity dismount during teleportation ++ ); ++ // Suppress during worldgen ++ if (this.valid) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ CraftEntity craftn = (CraftEntity) entity.getBukkitEntity().getVehicle(); ++ Entity n = craftn == null ? null : craftn.getHandle(); ++ if (event.isCancelled() || n != orig) { ++ return false; ++ } ++ } ++ ++ EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation ++ // Suppress during worldgen ++ if (this.valid) { ++ Bukkit.getPluginManager().callEvent(event); ++ } ++ if (event.isCancelled()) { ++ return false; ++ } ++ // CraftBukkit end ++ if (this.passengers.size() == 1 && this.passengers.get(0) == entity) { + this.passengers = ImmutableList.of(); + } else { + this.passengers = (ImmutableList) this.passengers.stream().filter((entity1) -> { +- return entity1 != passenger; ++ return entity1 != entity; + }).collect(ImmutableList.toImmutableList()); + } + +- passenger.boardingCooldown = 60; +- this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger); ++ entity.boardingCooldown = 60; ++ this.gameEvent(GameEvent.ENTITY_DISMOUNT, entity); + } ++ return true; // CraftBukkit + } + + protected boolean canAddPassenger(Entity passenger) { +@@ -2464,7 +3188,7 @@ + if (teleporttransition != null) { + ServerLevel worldserver1 = teleporttransition.newLevel(); + +- if (worldserver.getServer().isLevelEnabled(worldserver1) && (worldserver1.dimension() == worldserver.dimension() || this.canTeleport(worldserver, worldserver1))) { ++ if (this instanceof ServerPlayer || (worldserver1 != null && (worldserver1.dimension() == worldserver.dimension() || this.canTeleport(worldserver, worldserver1)))) { // CraftBukkit - always call event for players + this.teleport(teleporttransition); + } + } +@@ -2547,7 +3271,7 @@ + } + + public boolean isCrouching() { +- return this.hasPose(Pose.CROUCHING); ++ return this.hasPose(net.minecraft.world.entity.Pose.CROUCHING); + } + + public boolean isSprinting() { +@@ -2563,7 +3287,7 @@ + } + + public boolean isVisuallySwimming() { +- return this.hasPose(Pose.SWIMMING); ++ return this.hasPose(net.minecraft.world.entity.Pose.SWIMMING); + } + + public boolean isVisuallyCrawling() { +@@ -2571,6 +3295,13 @@ + } + + public void setSwimming(boolean swimming) { ++ // CraftBukkit start ++ if (this.valid && this.isSwimming() != swimming && this instanceof net.minecraft.world.entity.LivingEntity) { ++ if (CraftEventFactory.callToggleSwimEvent((net.minecraft.world.entity.LivingEntity) this, swimming).isCancelled()) { ++ return; ++ } ++ } ++ // CraftBukkit end + this.setSharedFlag(4, swimming); + } + +@@ -2609,6 +3340,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()); + } + +@@ -2624,8 +3356,12 @@ + return this.getTeam() != null ? this.getTeam().isAlliedTo(team) : false; + } + ++ // CraftBukkit - start + public void setInvisible(boolean invisible) { +- this.setSharedFlag(5, invisible); ++ if (!this.persistentInvisibility) { // Prevent Minecraft from removing our invisibility flag ++ this.setSharedFlag(5, invisible); ++ } ++ // CraftBukkit - end + } + + public boolean getSharedFlag(int index) { +@@ -2644,7 +3380,7 @@ + } + + public int getMaxAirSupply() { +- return 300; ++ return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() + } + + public int getAirSupply() { +@@ -2652,7 +3388,18 @@ + } + + public void setAirSupply(int air) { +- this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, air); ++ // CraftBukkit start ++ EntityAirChangeEvent event = new 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() { +@@ -2679,11 +3426,44 @@ + + public void thunderHit(ServerLevel world, 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 PluginManager pluginManager = Bukkit.getPluginManager(); ++ // CraftBukkit end ++ + if (this.remainingFireTicks == 0) { +- this.igniteForSeconds(8.0F); ++ // CraftBukkit start - Call a combust event when lightning strikes ++ EntityCombustByEntityEvent entityCombustEvent = new 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 + } + +- this.hurtServer(world, this.damageSources().lightningBolt(), 5.0F); ++ // CraftBukkit start ++ if (thisBukkitEntity instanceof Hanging) { ++ HangingBreakByEntityEvent hangingEvent = new HangingBreakByEntityEvent((Hanging) thisBukkitEntity, stormBukkitEntity); ++ pluginManager.callEvent(hangingEvent); ++ ++ if (hangingEvent.isCancelled()) { ++ return; ++ } ++ } ++ ++ if (this.fireImmune()) { ++ return; ++ } ++ ++ if (!this.hurtServer(world, this.damageSources().lightningBolt().customEventDamager(lightning), 5.0F)) { // Paper - fix DamageSource API ++ return; ++ } ++ // CraftBukkit end + } + + public void onAboveBubbleCol(boolean drag) { +@@ -2713,7 +3493,7 @@ + this.resetFallDistance(); + } + +- public boolean killedEntity(ServerLevel world, LivingEntity other) { ++ public boolean killedEntity(ServerLevel world, net.minecraft.world.entity.LivingEntity other) { + return true; + } + +@@ -2818,7 +3598,7 @@ + public String toString() { + String s = this.level() == null ? "~NULL~" : this.level().toString(); + +- return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ(), this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ()); ++ return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid, this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid); // Paper - add more info + } + + public final boolean isInvulnerableToBase(DamageSource damageSource) { +@@ -2838,6 +3618,13 @@ + } + + public void restoreFrom(Entity original) { ++ // Paper start - Forward CraftEntity in teleport command ++ CraftEntity bukkitEntity = original.bukkitEntity; ++ if (bukkitEntity != null) { ++ bukkitEntity.setHandle(this); ++ this.bukkitEntity = bukkitEntity; ++ } ++ // Paper end - Forward CraftEntity in teleport command + CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag()); + + nbttagcompound.remove("Dimension"); +@@ -2850,8 +3637,57 @@ + public Entity teleport(TeleportTransition teleportTarget) { + Level world = this.level(); + ++ // Paper start - Fix item duplication and teleport issues ++ if ((!this.isAlive() || !this.valid) && (teleportTarget.newLevel() != world)) { ++ LOGGER.warn("Illegal Entity Teleport " + this + " to " + teleportTarget.newLevel() + ":" + teleportTarget.position(), new Throwable()); ++ return null; ++ } ++ // Paper end - Fix item duplication and teleport issues + if (world instanceof ServerLevel worldserver) { + if (!this.isRemoved()) { ++ // CraftBukkit start ++ PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); ++ Vec3 velocity = absolutePosition.deltaMovement(); // Paper ++ Location to = CraftLocation.toBukkit(absolutePosition.position(), teleportTarget.newLevel().getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); ++ // Paper start - gateway-specific teleport event ++ final 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 = 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(); ++ teleportTarget = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause()); ++ // Paper start - Call EntityPortalExitEvent ++ velocity = Vec3.ZERO; ++ } ++ if (this.portalProcess != null) { // if in a portal ++ 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()); ++ teleportTarget = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), velocity, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause()); ++ } ++ } ++ if (this.isRemoved()) { ++ return null; ++ } ++ // Paper end - Call EntityPortalExitEvent ++ // CraftBukkit end + ServerLevel worldserver1 = teleportTarget.newLevel(); + boolean flag = worldserver1.dimension() != worldserver.dimension(); + +@@ -2918,10 +3754,19 @@ + gameprofilerfiller.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 + entity.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 + entity.teleportSetPosition(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); +- world.addDuringTeleport(entity); ++ if (this.inWorld) world.addDuringTeleport(entity); // CraftBukkit - Don't spawn the new entity if the current entity isn't spawned + Iterator iterator1 = list1.iterator(); + + while (iterator1.hasNext()) { +@@ -2947,7 +3792,7 @@ + } + + private void sendTeleportTransitionToRidingPlayers(TeleportTransition teleportTarget) { +- LivingEntity entityliving = this.getControllingPassenger(); ++ net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger(); + Iterator iterator = this.getIndirectPassengers().iterator(); + + while (iterator.hasNext()) { +@@ -2995,9 +3840,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 EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, false); // CraftBukkit ++ event.callEvent(); ++ if (!event.isDropLeash()) { ++ leashable.removeLeash(); ++ } else { ++ leashable.dropLeash(); ++ } ++ // Paper end - Expand EntityUnleashEvent + } + + } +@@ -3005,12 +3858,35 @@ + public Vec3 getRelativePortalPosition(Direction.Axis portalAxis, BlockUtil.FoundRectangle portalRect) { + return PortalShape.getRelativePosition(portalRect, portalAxis, this.position(), this.getDimensions(this.getPose())); + } ++ ++ // CraftBukkit start ++ public CraftPortalEvent callPortalEvent(Entity entity, Location exit, PlayerTeleportEvent.TeleportCause cause, int searchRadius, int creationRadius) { ++ org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); ++ 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; ++ }; ++ EntityPortalEvent event = new 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 CraftPortalEvent(event); ++ } ++ // CraftBukkit end + + public boolean canUsePortal(boolean allowVehicles) { + return (allowVehicles || !this.isPassenger()) && this.isAlive(); + } + + public boolean canTeleport(Level from, Level to) { ++ if (!this.isAlive() || !this.valid) return false; // Paper - Fix item duplication and teleport issues + if (from.dimension() == Level.END && to.dimension() == Level.OVERWORLD) { + Iterator iterator = this.getPassengers().iterator(); + +@@ -3134,10 +4010,16 @@ + return (Boolean) this.entityData.get(Entity.DATA_CUSTOM_NAME_VISIBLE); + } + +- public boolean teleportTo(ServerLevel world, double destX, double destY, double destZ, Set<Relative> flags, float yaw, float pitch, boolean resetCamera) { +- float f2 = Mth.clamp(pitch, -90.0F, 90.0F); +- Entity entity = this.teleport(new TeleportTransition(world, new Vec3(destX, destY, destZ), Vec3.ZERO, yaw, f2, flags, TeleportTransition.DO_NOTHING)); ++ // CraftBukkit start ++ public final boolean teleportTo(ServerLevel world, double destX, double destY, double destZ, Set<Relative> flags, float yaw, float pitch, boolean resetCamera) { ++ return this.teleportTo(world, destX, destY, destZ, flags, yaw, pitch, resetCamera, PlayerTeleportEvent.TeleportCause.UNKNOWN); ++ } + ++ public boolean teleportTo(ServerLevel worldserver, double d0, double d1, double d2, Set<Relative> set, float f, float f1, boolean flag, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { ++ float f2 = Mth.clamp(f1, -90.0F, 90.0F); ++ Entity entity = this.teleport(new TeleportTransition(worldserver, new Vec3(d0, d1, d2), Vec3.ZERO, f, f2, set, TeleportTransition.DO_NOTHING, cause)); ++ // CraftBukkit end ++ + return entity != null; + } + +@@ -3187,7 +4069,7 @@ + /** @deprecated */ + @Deprecated + protected void fixupDimensions() { +- Pose entitypose = this.getPose(); ++ net.minecraft.world.entity.Pose entitypose = this.getPose(); + EntityDimensions entitysize = this.getDimensions(entitypose); + + this.dimensions = entitysize; +@@ -3196,7 +4078,7 @@ + + public void refreshDimensions() { + EntityDimensions entitysize = this.dimensions; +- Pose entitypose = this.getPose(); ++ net.minecraft.world.entity.Pose entitypose = this.getPose(); + EntityDimensions entitysize1 = this.getDimensions(entitypose); + + this.dimensions = entitysize1; +@@ -3258,10 +4140,29 @@ + } + + public final void setBoundingBox(AABB boundingBox) { +- this.bb = boundingBox; ++ // CraftBukkit start - block invalid bounding boxes ++ double minX = boundingBox.minX, ++ minY = boundingBox.minY, ++ minZ = boundingBox.minZ, ++ maxX = boundingBox.maxX, ++ maxY = boundingBox.maxY, ++ maxZ = boundingBox.maxZ; ++ double len = boundingBox.maxX - boundingBox.minX; ++ if (len < 0) maxX = minX; ++ if (len > 64) maxX = minX + 64.0; ++ ++ len = boundingBox.maxY - boundingBox.minY; ++ if (len < 0) maxY = minY; ++ if (len > 64) maxY = minY + 64.0; ++ ++ len = boundingBox.maxZ - boundingBox.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) { ++ public final float getEyeHeight(net.minecraft.world.entity.Pose pose) { + return this.getDimensions(pose).eyeHeight(); + } + +@@ -3300,7 +4201,14 @@ + + public void startSeenByPlayer(ServerPlayer player) {} + +- public void stopSeenByPlayer(ServerPlayer player) {} ++ // Paper start - entity tracking events ++ public void stopSeenByPlayer(ServerPlayer player) { ++ // 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(player.getBukkitEntity(), this.getBukkitEntity()).callEvent(); ++ } ++ } ++ // Paper end - entity tracking events + + public float rotate(Rotation rotation) { + float f = Mth.wrapDegrees(this.getYRot()); +@@ -3335,7 +4243,7 @@ + } + + @Nullable +- public LivingEntity getControllingPassenger() { ++ public net.minecraft.world.entity.LivingEntity getControllingPassenger() { + return null; + } + +@@ -3373,20 +4281,34 @@ + } + + 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() { ++ // 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(); ++ } ++ private Iterable<Entity> getIndirectPassengers_old() { ++ // Paper end - Optimize indirect passenger iteration + return () -> { + return this.getIndirectPassengersStream().iterator(); + }; +@@ -3399,6 +4321,7 @@ + } + + public boolean hasExactlyOnePlayerPassenger() { ++ if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration + return this.countPlayerPassengers() == 1; + } + +@@ -3435,7 +4358,7 @@ + } + + public boolean isControlledByLocalInstance() { +- LivingEntity entityliving = this.getControllingPassenger(); ++ net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger(); + + if (entityliving instanceof Player entityhuman) { + return entityhuman.isLocalPlayer(); +@@ -3445,7 +4368,7 @@ + } + + public boolean isControlledByClient() { +- LivingEntity entityliving = this.getControllingPassenger(); ++ net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger(); + + return entityliving != null && entityliving.isControlledByClient(); + } +@@ -3463,7 +4386,7 @@ + return new Vec3((double) f1 * d2 / (double) f3, 0.0D, (double) f2 * d2 / (double) f3); + } + +- public Vec3 getDismountLocationForPassenger(LivingEntity passenger) { ++ public Vec3 getDismountLocationForPassenger(net.minecraft.world.entity.LivingEntity passenger) { + return new Vec3(this.getX(), this.getBoundingBox().maxY, this.getZ()); + } + +@@ -3488,9 +4411,38 @@ + public int getFireImmuneTicks() { + return 1; + } ++ ++ // CraftBukkit start ++ private final CommandSource commandSource = new CommandSource() { ++ ++ @Override ++ public void sendSystemMessage(Component message) { ++ } + ++ @Override ++ public CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return Entity.this.getBukkitEntity(); ++ } ++ ++ @Override ++ public boolean acceptsSuccess() { ++ return ((ServerLevel) Entity.this.level()).getGameRules().getBoolean(GameRules.RULE_SENDCOMMANDFEEDBACK); ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return true; ++ } ++ ++ @Override ++ public boolean shouldInformAdmins() { ++ return true; ++ } ++ }; ++ // CraftBukkit end ++ + public CommandSourceStack createCommandSourceStackForNameResolution(ServerLevel world) { +- return new CommandSourceStack(CommandSource.NULL, this.position(), this.getRotationVector(), world, 0, this.getName().getString(), this.getDisplayName(), world.getServer(), this); ++ return new CommandSourceStack(this.commandSource, this.position(), this.getRotationVector(), world, 0, this.getName().getString(), this.getDisplayName(), world.getServer(), this); // CraftBukkit + } + + public void lookAt(EntityAnchorArgument.Anchor anchorPoint, Vec3 target) { +@@ -3551,6 +4503,11 @@ + vec3d = vec3d.add(vec3d1); + ++k1; + } ++ // CraftBukkit start - store last lava contact location ++ if (tag == FluidTags.LAVA) { ++ this.lastLavaContact = blockposition_mutableblockposition.immutable(); ++ } ++ // CraftBukkit end + } + } + } +@@ -3613,7 +4570,7 @@ + return new ClientboundAddEntityPacket(this, entityTrackerEntry); + } + +- public EntityDimensions getDimensions(Pose pose) { ++ public EntityDimensions getDimensions(net.minecraft.world.entity.Pose pose) { + return this.type.getDimensions(); + } + +@@ -3713,8 +4670,40 @@ + public double getRandomZ(double widthScale) { + return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); + } ++ ++ // 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) { + this.position = new Vec3(x, y, z); + int i = Mth.floor(x); +@@ -3732,6 +4721,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() {} +@@ -3818,8 +4813,17 @@ + + @Override + public final void setRemoved(Entity.RemovalReason reason) { ++ // CraftBukkit start - add Bukkit remove cause ++ this.setRemoved(reason, null); ++ } ++ ++ @Override ++ public final void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) { ++ CraftEventFactory.callEntityRemoveEvent(this, cause); ++ // CraftBukkit end ++ final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers + if (this.removalReason == null) { +- this.removalReason = reason; ++ this.removalReason = entity_removalreason; + } + + if (this.removalReason.shouldDestroy()) { +@@ -3827,14 +4831,30 @@ + } + + this.getPassengers().forEach(Entity::stopRiding); +- this.levelCallback.onRemove(reason); +- this.onRemoval(reason); ++ this.levelCallback.onRemove(entity_removalreason); ++ this.onRemoval(entity_removalreason); ++ // Paper start - Folia schedulers ++ if (!(this instanceof ServerPlayer) && entity_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 changeListener) { + this.levelCallback = changeListener; +@@ -3887,7 +4907,7 @@ + } + + public Vec3 getKnownMovement() { +- LivingEntity entityliving = this.getControllingPassenger(); ++ net.minecraft.world.entity.LivingEntity entityliving = this.getControllingPassenger(); + + if (entityliving instanceof Player entityhuman) { + if (this.isAlive()) { +@@ -3962,4 +4982,14 @@ + + void accept(Entity entity, double x, double y, double z); + } ++ ++ // Paper start - Expose entity id counter ++ public static int nextEntityId() { ++ return ENTITY_COUNTER.incrementAndGet(); ++ } ++ ++ public boolean isTicking() { ++ return ((net.minecraft.server.level.ServerLevel) this.level).isPositionEntityTicking(this.blockPosition()); ++ } ++ // Paper end - Expose entity id counter + } |