From 0609391ea0b3ea6d07c394d6266027272a7a50c7 Mon Sep 17 00:00:00 2001 From: Dueris Date: Mon, 1 Jun 2026 19:34:04 -0700 Subject: [PATCH 01/12] Initial rebase --- .../net/minecraft/server/level/ServerLevel.java.patch | 2 +- .../world/level/block/state/BlockBehaviour.java.patch | 2 +- .../org/bukkit/craftbukkit/entity/CraftHumanEntity.java | 9 ++++++++- .../java/org/bukkit/craftbukkit/entity/CraftPlayer.java | 4 ++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch index 2e4dc8485ff0..4d2e9286090a 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch @@ -642,7 +642,7 @@ - private boolean addEntity(final Entity entity) { + // CraftBukkit start + private boolean addEntity(final Entity entity, final org.bukkit.event.entity.CreatureSpawnEvent.@Nullable SpawnReason spawnReason) { -+ org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot add entity async"); // Paper - block async calls + entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process + // Paper start - extra debug info + if (entity.valid) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch index b74db39b6f37..f42b41242e1b 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch @@ -4,7 +4,7 @@ } protected void onPlace(final BlockState state, final Level level, final BlockPos pos, final BlockState oldState, final boolean movedByPiston) { -+ org.spigotmc.AsyncCatcher.catchOp("block onPlace"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, pos, "Cannot place block async"); // Paper - block async calls } protected void affectNeighborsAfterRemoval(final BlockState state, final ServerLevel level, final BlockPos pos, final boolean movedByPiston) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java index 2aea80487ff1..b29fe7f01ae1 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import io.papermc.paper.adventure.PaperAdventure; @@ -501,6 +502,12 @@ private static int getMountInventoryColumns(final AbstractMountInventoryMenu mou @Override public InventoryView openMerchant(Merchant merchant, boolean force) { Preconditions.checkNotNull(merchant, "merchant cannot be null"); + // Paper start - block async calls + if (merchant instanceof CraftEntity craftEntity) { + TickThread.ensureTickThread(craftEntity.entity, "Cannot open merchant screen async from merchant"); + } + TickThread.ensureTickThread(this.entity, "Cannot open merchant screen async"); + // Paper end - block async calls if (!force && merchant.isTrading()) { return null; @@ -562,7 +569,7 @@ public InventoryView openStonecutter(Location location, boolean force) { } private InventoryView openInventory(Location location, boolean force, Material material) { - org.spigotmc.AsyncCatcher.catchOp("open" + material); + TickThread.ensureTickThread(this.entity, "Cannot open inventory async"); // Paper - block async calls if (location == null) { location = this.getLocation(); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index ad51146ef274..412b1dac75d0 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.destroystokyo.paper.event.player.PlayerSetSpawnEvent; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -1339,6 +1340,7 @@ public void setSprinting(boolean sprinting) { @Override public void loadData() { + TickThread.ensureTickThread(this.entity, "Cannot load data async"); // Paper - block async calls this.server.getHandle().playerIo.load(this.getHandle().nameAndId()) .map(tag -> TagValueInput.create(ProblemReporter.DISCARDING, this.server.getServer().registryAccess(), tag)) .ifPresent(this.getHandle()::load); @@ -1346,6 +1348,7 @@ public void loadData() { @Override public void saveData() { + TickThread.ensureTickThread(this.entity, "Cannot save data async"); // Paper - block async calls this.server.getHandle().playerIo.save(this.getHandle()); } @@ -1662,6 +1665,7 @@ public void setGameMode(GameMode mode) { Preconditions.checkArgument(mode != null, "GameMode cannot be null"); if (this.getHandle().connection == null) return; + TickThread.ensureTickThread(this.entity, "Cannot set gamemode async"); // Paper - block async calls this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper - Expand PlayerGameModeChangeEvent } From 6cd13e737a116260d3c6fd5d29d8bffff337c52f Mon Sep 17 00:00:00 2001 From: Dueris Date: Mon, 1 Jun 2026 19:46:45 -0700 Subject: [PATCH 02/12] A few more replacement calls --- .../main/java/org/bukkit/craftbukkit/CraftWorld.java | 11 ++++++----- .../org/bukkit/craftbukkit/entity/CraftEntity.java | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index f3f068011abd..9555bb3d8dd4 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -2,6 +2,7 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList; import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.Lists; @@ -1555,7 +1556,7 @@ public void playSound(Location loc, String sound, org.bukkit.SoundCategory categ @Override public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); // Paper - block async calls if (loc == null || sound == null || category == null) return; double x = loc.getX(); @@ -1567,7 +1568,7 @@ public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory catego @Override public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); // Paper - block async calls if (loc == null || sound == null || category == null) return; double x = loc.getX(); @@ -1590,8 +1591,8 @@ public void playSound(Entity entity, String sound, org.bukkit.SoundCategory cate @Override public void playSound(Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; + TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); // Paper - block async calls ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); @@ -1611,8 +1612,8 @@ public void playSound(final net.kyori.adventure.sound.Sound sound) { @Override public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; + TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); // Paper - block async calls ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(Identifier.parse(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); @@ -1623,7 +1624,7 @@ public void playSound(Entity entity, String sound, org.bukkit.SoundCategory cate @Override public void playSound(final net.kyori.adventure.sound.Sound sound, final double x, final double y, final double z) { - org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + TickThread.ensureTickThread(this.getHandle(), ((int) x) >> 4, ((int) z) >> 4, "Cannot play sound async"); // Paper - block async calls io.papermc.paper.adventure.PaperAdventure.asSoundPacket(sound, x, y, z, sound.seed().orElseGet(this.world.getRandom()::nextLong), this.playSound0(x, y, z)); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index e45f9514e4b5..9f03f7ecbaaf 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; @@ -371,7 +372,7 @@ public static LookAnchor toApiAnchor(net.minecraft.commands.arguments.EntityAnch @Override public List getNearbyEntities(double x, double y, double z) { Preconditions.checkState(!this.entity.generation, "Cannot get nearby entities during world generation"); - org.spigotmc.AsyncCatcher.catchOp("getNearbyEntities"); // Spigot + TickThread.ensureTickThread(this.entity, "Cannot check nearby entities async"); // Paper - block async calls List entities = this.getHandle().level().getEntities(this.entity, this.entity.getBoundingBox().inflate(x, y, z), Predicates.alwaysTrue()); List result = new java.util.ArrayList<>(entities.size()); From d5e958d85a02ca5868c82f670005249950706476 Mon Sep 17 00:00:00 2001 From: Dueris Date: Mon, 1 Jun 2026 19:34:04 -0700 Subject: [PATCH 03/12] More tick thread checks --- .../net/minecraft/server/level/ChunkMap.java.patch | 8 ++++---- .../net/minecraft/server/level/ServerPlayer.java.patch | 2 ++ .../org/bukkit/craftbukkit/entity/CraftLivingEntity.java | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch index 704059c3e0bb..303588c98004 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -264,7 +264,7 @@ } protected void addEntity(final Entity entity) { -+ org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot track entity async"); // Paper - block async calls + // Paper start - ignore and warn about illegal addEntity calls instead of crashing server + if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { + LOGGER.error("Illegal ChunkMap::addEntity for world " + io.papermc.paper.util.MCUtil.getLevelName(this.level) @@ -284,7 +284,7 @@ } protected void removeEntity(final Entity entity) { -+ org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot untrack entity async"); // Paper - block async calls if (entity instanceof ServerPlayer player) { this.updatePlayerStatus(player, false); @@ -305,7 +305,7 @@ } public void removePlayer(final ServerPlayer player) { -+ org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot remove tracker player async"); // Paper - block async calls if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); if (this.seenBy.isEmpty()) { @@ -313,7 +313,7 @@ } public void updatePlayer(final ServerPlayer player) { -+ org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot conduct update player tracker update async"); // Paper - block async calls if (player != this.entity) { - Vec3 deltaToPlayer = player.position().subtract(this.entity.position()); + // Paper start - remove allocation of Vec3D here diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch index fc4746f898f7..c12d582906df 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -443,6 +443,7 @@ + // Paper end - Expand PlayerDeathEvent API + @Override + public void die(final DamageSource source) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot die async"); // Paper - block async calls + // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check + boolean showDeathMessage = this.level().getGameRules().get(GameRules.SHOW_DEATH_MESSAGES); + // CraftBukkit start - fire PlayerDeathEvent @@ -1096,6 +1097,7 @@ + } + @Override + public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot close container async"); // Paper - block async calls + org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit + // Paper end - Inventory close reason this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index 6b6b0051af41..4bc299e436d9 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -505,7 +505,7 @@ public void setKiller(Player killer) { @Override public boolean addPotionEffect(PotionEffect effect) { - org.spigotmc.AsyncCatcher.catchOp("effect add"); // Paper + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot add potion effect async"); // Paper - block async calls return this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon } From 219ec23df83426900ed9ac6e6275526881397076 Mon Sep 17 00:00:00 2001 From: Dueris Date: Tue, 30 Jun 2026 03:27:23 -0700 Subject: [PATCH 04/12] Update and fixup --- .../net/minecraft/server/level/ChunkMap.java.patch | 2 +- .../minecraft/server/level/ServerLevel.java.patch | 2 +- .../minecraft/server/level/ServerPlayer.java.patch | 14 ++++++++++++-- .../java/org/bukkit/craftbukkit/CraftWorld.java | 2 +- .../craftbukkit/entity/CraftHumanEntity.java | 1 + 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch index 303588c98004..72ab295f7245 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -313,7 +313,7 @@ } public void updatePlayer(final ServerPlayer player) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot conduct update player tracker update async"); // Paper - block async calls ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot conduct player tracker update async"); // Paper - block async calls if (player != this.entity) { - Vec3 deltaToPlayer = player.position().subtract(this.entity.position()); + // Paper start - remove allocation of Vec3D here diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch index 4d2e9286090a..265374b93498 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch @@ -1135,7 +1135,7 @@ @Override public void onTrackingStart(final Entity entity) { - ServerLevel.this.getChunkSource().addEntity(entity); -+ org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot start tracking entity async"); // Paper - block async calls + // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true if (entity instanceof ServerPlayer player) { ServerLevel.this.players.add(player); diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch index c12d582906df..30e2a6f354da 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -210,6 +210,14 @@ if (enderPearl.isRemoved()) { LOGGER.warn("Trying to save removed ender pearl, skipping"); } else { +@@ -548,6 +_,7 @@ + } + + public void initMenu(final AbstractContainerMenu container) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot init menu async"); // Paper - block async calls + container.addSlotListener(this.containerListener); + container.setSynchronizer(this.containerSynchronizer); + } @@ -580,6 +_,11 @@ @Override @@ -964,7 +972,7 @@ } @Override -@@ -1323,8 +_,9 @@ +@@ -1323,22 +_,51 @@ this.connection.send(new ClientboundShowDialogPacket(dialog)); } @@ -975,7 +983,9 @@ } @Override -@@ -1333,12 +_,39 @@ + public OptionalInt openMenu(final @Nullable MenuProvider provider) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot open menu async"); // Paper - block async calls + if (provider == null) { return OptionalInt.empty(); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 9555bb3d8dd4..b242fb0b39cd 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -1624,7 +1624,7 @@ public void playSound(Entity entity, String sound, org.bukkit.SoundCategory cate @Override public void playSound(final net.kyori.adventure.sound.Sound sound, final double x, final double y, final double z) { - TickThread.ensureTickThread(this.getHandle(), ((int) x) >> 4, ((int) z) >> 4, "Cannot play sound async"); // Paper - block async calls + TickThread.ensureTickThread(this.getHandle(), Mth.floor(x) >> 4, Mth.floor(z) >> 4, "Cannot play sound async"); // Paper - block async calls io.papermc.paper.adventure.PaperAdventure.asSoundPacket(sound, x, y, z, sound.seed().orElseGet(this.world.getRandom()::nextLong), this.playSound0(x, y, z)); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java index b29fe7f01ae1..38b993aa8172 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -446,6 +446,7 @@ public void openInventory(InventoryView inventory) { Preconditions.checkArgument(this.equals(inventory.getPlayer()), "InventoryView must belong to the opening player"); if (!(this.getHandle() instanceof ServerPlayer)) return; // TODO: NPC support? if (((ServerPlayer) this.getHandle()).connection == null) return; + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot open inventory view async"); // Paper - block async calls if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { // fire INVENTORY_CLOSE if one already open ((ServerPlayer) this.getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(this.getHandle().containerMenu.containerId), org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason From 490a95b20b23102a2ddf9770289be63ea3209a4b Mon Sep 17 00:00:00 2001 From: Dueris Date: Mon, 1 Jun 2026 19:34:04 -0700 Subject: [PATCH 05/12] More tick thread checks --- .../net/minecraft/server/level/ChunkMap.java.patch | 8 ++++---- .../org/bukkit/craftbukkit/entity/CraftLivingEntity.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch index 72ab295f7245..60ae65b56525 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -264,7 +264,7 @@ } protected void addEntity(final Entity entity) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot track entity async"); // Paper - block async calls ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot add entity to tracking async"); // Paper - block async calls + // Paper start - ignore and warn about illegal addEntity calls instead of crashing server + if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { + LOGGER.error("Illegal ChunkMap::addEntity for world " + io.papermc.paper.util.MCUtil.getLevelName(this.level) @@ -284,7 +284,7 @@ } protected void removeEntity(final Entity entity) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot untrack entity async"); // Paper - block async calls ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot remove entity async"); // Paper - block async calls if (entity instanceof ServerPlayer player) { this.updatePlayerStatus(player, false); @@ -305,7 +305,7 @@ } public void removePlayer(final ServerPlayer player) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot remove tracker player async"); // Paper - block async calls ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot remove player async"); // Paper - block async calls if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); if (this.seenBy.isEmpty()) { @@ -313,7 +313,7 @@ } public void updatePlayer(final ServerPlayer player) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot conduct player tracker update async"); // Paper - block async calls ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot update player async"); // Paper - block async calls if (player != this.entity) { - Vec3 deltaToPlayer = player.position().subtract(this.entity.position()); + // Paper start - remove allocation of Vec3D here diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index 4bc299e436d9..559d4985b041 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -505,7 +505,7 @@ public void setKiller(Player killer) { @Override public boolean addPotionEffect(PotionEffect effect) { - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot add potion effect async"); // Paper - block async calls + TickThread.ensureTickThread(this.entity, "Cannot add potion effect async"); // Paper - block async calls return this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon } From cf42c690873e6885f4254e4d8026808c7e5c5fd7 Mon Sep 17 00:00:00 2001 From: "duerismc@gmail.com" Date: Tue, 30 Jun 2026 04:13:51 -0700 Subject: [PATCH 06/12] Fixup --- .../net/minecraft/server/level/ChunkMap.java.patch | 8 ++++---- .../minecraft/server/level/ServerPlayer.java.patch | 4 ++-- .../main/java/org/bukkit/craftbukkit/CraftWorld.java | 11 +++++------ .../org/bukkit/craftbukkit/entity/CraftEntity.java | 3 +-- .../bukkit/craftbukkit/entity/CraftHumanEntity.java | 8 ++++---- .../org/bukkit/craftbukkit/entity/CraftPlayer.java | 7 +++---- 6 files changed, 19 insertions(+), 22 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch index 60ae65b56525..9e01bd16a8e7 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -264,7 +264,7 @@ } protected void addEntity(final Entity entity) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot add entity to tracking async"); // Paper - block async calls ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot track entity async"); // Paper - block async calls + // Paper start - ignore and warn about illegal addEntity calls instead of crashing server + if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { + LOGGER.error("Illegal ChunkMap::addEntity for world " + io.papermc.paper.util.MCUtil.getLevelName(this.level) @@ -284,7 +284,7 @@ } protected void removeEntity(final Entity entity) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot remove entity async"); // Paper - block async calls ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Cannot untrack entity async"); // Paper - block async calls if (entity instanceof ServerPlayer player) { this.updatePlayerStatus(player, false); @@ -305,7 +305,7 @@ } public void removePlayer(final ServerPlayer player) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot remove player async"); // Paper - block async calls ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot remove tracking player async"); // Paper - block async calls if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); if (this.seenBy.isEmpty()) { @@ -313,7 +313,7 @@ } public void updatePlayer(final ServerPlayer player) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot update player async"); // Paper - block async calls ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot update tracking player async"); // Paper - block async calls if (player != this.entity) { - Vec3 deltaToPlayer = player.position().subtract(this.entity.position()); + // Paper start - remove allocation of Vec3D here diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch index 30e2a6f354da..628c19a5be2c 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -440,7 +440,7 @@ this.connection .send( new ClientboundPlayerCombatKillPacket(this.getId(), deathMessage), -@@ -903,6 +_,65 @@ +@@ -903,6 +_,66 @@ } ) ); @@ -1098,7 +1098,7 @@ this.initMenu(this.containerMenu); } -@@ -1409,10 +_,30 @@ +@@ -1409,10 +_,31 @@ @Override public void closeContainer() { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index b242fb0b39cd..18986e9a1b07 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -2,7 +2,6 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList; import ca.spottedleaf.moonrise.common.util.CoordinateUtils; -import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.Lists; @@ -1556,7 +1555,7 @@ public void playSound(Location loc, String sound, org.bukkit.SoundCategory categ @Override public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); // Paper - block async calls if (loc == null || sound == null || category == null) return; double x = loc.getX(); @@ -1568,7 +1567,7 @@ public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory catego @Override public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); // Paper - block async calls if (loc == null || sound == null || category == null) return; double x = loc.getX(); @@ -1592,7 +1591,7 @@ public void playSound(Entity entity, String sound, org.bukkit.SoundCategory cate @Override public void playSound(Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; - TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); // Paper - block async calls ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); @@ -1613,7 +1612,7 @@ public void playSound(final net.kyori.adventure.sound.Sound sound) { @Override public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; - TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); // Paper - block async calls ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(Identifier.parse(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); @@ -1624,7 +1623,7 @@ public void playSound(Entity entity, String sound, org.bukkit.SoundCategory cate @Override public void playSound(final net.kyori.adventure.sound.Sound sound, final double x, final double y, final double z) { - TickThread.ensureTickThread(this.getHandle(), Mth.floor(x) >> 4, Mth.floor(z) >> 4, "Cannot play sound async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), Mth.floor(x) >> 4, Mth.floor(z) >> 4, "Cannot play sound async"); // Paper - block async calls io.papermc.paper.adventure.PaperAdventure.asSoundPacket(sound, x, y, z, sound.seed().orElseGet(this.world.getRandom()::nextLong), this.playSound0(x, y, z)); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index 9f03f7ecbaaf..c3a01d3abafa 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -1,6 +1,5 @@ package org.bukkit.craftbukkit.entity; -import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; @@ -372,7 +371,7 @@ public static LookAnchor toApiAnchor(net.minecraft.commands.arguments.EntityAnch @Override public List getNearbyEntities(double x, double y, double z) { Preconditions.checkState(!this.entity.generation, "Cannot get nearby entities during world generation"); - TickThread.ensureTickThread(this.entity, "Cannot check nearby entities async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot get nearby entities async"); // Paper - block async calls List entities = this.getHandle().level().getEntities(this.entity, this.entity.getBoundingBox().inflate(x, y, z), Predicates.alwaysTrue()); List result = new java.util.ArrayList<>(entities.size()); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java index 38b993aa8172..f4ef2132eaea 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -1,6 +1,5 @@ package org.bukkit.craftbukkit.entity; -import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import io.papermc.paper.adventure.PaperAdventure; @@ -327,6 +326,7 @@ public InventoryView getOpenInventory() { @Override public InventoryView openInventory(Inventory inventory) { if (!(this.getHandle() instanceof ServerPlayer)) return null; + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot open inventory async"); // Paper - block async calls ServerPlayer player = (ServerPlayer) this.getHandle(); AbstractContainerMenu formerContainer = this.getHandle().containerMenu; @@ -505,9 +505,9 @@ public InventoryView openMerchant(Merchant merchant, boolean force) { Preconditions.checkNotNull(merchant, "merchant cannot be null"); // Paper start - block async calls if (merchant instanceof CraftEntity craftEntity) { - TickThread.ensureTickThread(craftEntity.entity, "Cannot open merchant screen async from merchant"); + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(craftEntity.entity, "Cannot open merchant screen async from merchant"); } - TickThread.ensureTickThread(this.entity, "Cannot open merchant screen async"); + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot open merchant screen async"); // Paper end - block async calls if (!force && merchant.isTrading()) { @@ -570,7 +570,7 @@ public InventoryView openStonecutter(Location location, boolean force) { } private InventoryView openInventory(Location location, boolean force, Material material) { - TickThread.ensureTickThread(this.entity, "Cannot open inventory async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot open inventory async"); // Paper - block async calls if (location == null) { location = this.getLocation(); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 412b1dac75d0..c883a3e54354 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -1,6 +1,5 @@ package org.bukkit.craftbukkit.entity; -import ca.spottedleaf.moonrise.common.util.TickThread; import com.destroystokyo.paper.event.player.PlayerSetSpawnEvent; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -1340,7 +1339,7 @@ public void setSprinting(boolean sprinting) { @Override public void loadData() { - TickThread.ensureTickThread(this.entity, "Cannot load data async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot load data async"); // Paper - block async calls this.server.getHandle().playerIo.load(this.getHandle().nameAndId()) .map(tag -> TagValueInput.create(ProblemReporter.DISCARDING, this.server.getServer().registryAccess(), tag)) .ifPresent(this.getHandle()::load); @@ -1348,7 +1347,7 @@ public void loadData() { @Override public void saveData() { - TickThread.ensureTickThread(this.entity, "Cannot save data async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot save data async"); // Paper - block async calls this.server.getHandle().playerIo.save(this.getHandle()); } @@ -1665,7 +1664,7 @@ public void setGameMode(GameMode mode) { Preconditions.checkArgument(mode != null, "GameMode cannot be null"); if (this.getHandle().connection == null) return; - TickThread.ensureTickThread(this.entity, "Cannot set gamemode async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot set gamemode async"); // Paper - block async calls this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper - Expand PlayerGameModeChangeEvent } From 605c0f52f76d97ffc40debf5eecf8eaccb565a27 Mon Sep 17 00:00:00 2001 From: "duerismc@gmail.com" Date: Tue, 30 Jun 2026 04:16:32 -0700 Subject: [PATCH 07/12] Include material name to match previous catcher --- .../java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java index f4ef2132eaea..533c71ebca76 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -570,7 +570,7 @@ public InventoryView openStonecutter(Location location, boolean force) { } private InventoryView openInventory(Location location, boolean force, Material material) { - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot open inventory async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot open " + material + " inventory async"); // Paper - block async calls if (location == null) { location = this.getLocation(); } From 63aa800d4dd6801b701dac90a74de86173d7190b Mon Sep 17 00:00:00 2001 From: "duerismc@gmail.com" Date: Tue, 30 Jun 2026 04:19:52 -0700 Subject: [PATCH 08/12] Missed in rebase --- .../java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index 559d4985b041..4bc299e436d9 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -505,7 +505,7 @@ public void setKiller(Player killer) { @Override public boolean addPotionEffect(PotionEffect effect) { - TickThread.ensureTickThread(this.entity, "Cannot add potion effect async"); // Paper - block async calls + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot add potion effect async"); // Paper - block async calls return this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon } From cd064de3cf0639592387784db5dbe93352afaee9 Mon Sep 17 00:00:00 2001 From: Dueris Date: Thu, 2 Jul 2026 11:42:37 -0700 Subject: [PATCH 09/12] Use import and remove Paper comments --- .../java/org/bukkit/craftbukkit/CraftWorld.java | 11 ++++++----- .../org/bukkit/craftbukkit/entity/CraftEntity.java | 3 ++- .../bukkit/craftbukkit/entity/CraftHumanEntity.java | 13 ++++++------- .../craftbukkit/entity/CraftLivingEntity.java | 3 ++- .../org/bukkit/craftbukkit/entity/CraftPlayer.java | 7 ++++--- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 18986e9a1b07..8374855de5cb 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -2,6 +2,7 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList; import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.Lists; @@ -1555,7 +1556,7 @@ public void playSound(Location loc, String sound, org.bukkit.SoundCategory categ @Override public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); // Paper - block async calls + TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); if (loc == null || sound == null || category == null) return; double x = loc.getX(); @@ -1567,7 +1568,7 @@ public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory catego @Override public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); // Paper - block async calls + TickThread.ensureTickThread(this.getHandle(), CraftLocation.toBlockPos(loc), "Cannot play sound async"); if (loc == null || sound == null || category == null) return; double x = loc.getX(); @@ -1591,7 +1592,7 @@ public void playSound(Entity entity, String sound, org.bukkit.SoundCategory cate @Override public void playSound(Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); // Paper - block async calls + TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); @@ -1612,7 +1613,7 @@ public void playSound(final net.kyori.adventure.sound.Sound sound) { @Override public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); // Paper - block async calls + TickThread.ensureTickThread(craftEntity.getHandleRaw(), "Cannot play sound async"); ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(Identifier.parse(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); @@ -1623,7 +1624,7 @@ public void playSound(Entity entity, String sound, org.bukkit.SoundCategory cate @Override public void playSound(final net.kyori.adventure.sound.Sound sound, final double x, final double y, final double z) { - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), Mth.floor(x) >> 4, Mth.floor(z) >> 4, "Cannot play sound async"); // Paper - block async calls + TickThread.ensureTickThread(this.getHandle(), Mth.floor(x) >> 4, Mth.floor(z) >> 4, "Cannot play sound async"); io.papermc.paper.adventure.PaperAdventure.asSoundPacket(sound, x, y, z, sound.seed().orElseGet(this.world.getRandom()::nextLong), this.playSound0(x, y, z)); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index c3a01d3abafa..648dc520a0c3 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; @@ -371,7 +372,7 @@ public static LookAnchor toApiAnchor(net.minecraft.commands.arguments.EntityAnch @Override public List getNearbyEntities(double x, double y, double z) { Preconditions.checkState(!this.entity.generation, "Cannot get nearby entities during world generation"); - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot get nearby entities async"); // Paper - block async calls + TickThread.ensureTickThread(this.entity, "Cannot get nearby entities async"); List entities = this.getHandle().level().getEntities(this.entity, this.entity.getBoundingBox().inflate(x, y, z), Predicates.alwaysTrue()); List result = new java.util.ArrayList<>(entities.size()); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java index 533c71ebca76..d54deaa9f12d 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import io.papermc.paper.adventure.PaperAdventure; @@ -326,7 +327,7 @@ public InventoryView getOpenInventory() { @Override public InventoryView openInventory(Inventory inventory) { if (!(this.getHandle() instanceof ServerPlayer)) return null; - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot open inventory async"); // Paper - block async calls + TickThread.ensureTickThread(this.entity, "Cannot open inventory async"); ServerPlayer player = (ServerPlayer) this.getHandle(); AbstractContainerMenu formerContainer = this.getHandle().containerMenu; @@ -446,7 +447,7 @@ public void openInventory(InventoryView inventory) { Preconditions.checkArgument(this.equals(inventory.getPlayer()), "InventoryView must belong to the opening player"); if (!(this.getHandle() instanceof ServerPlayer)) return; // TODO: NPC support? if (((ServerPlayer) this.getHandle()).connection == null) return; - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot open inventory view async"); // Paper - block async calls + TickThread.ensureTickThread(this.entity, "Cannot open inventory view async"); if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { // fire INVENTORY_CLOSE if one already open ((ServerPlayer) this.getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(this.getHandle().containerMenu.containerId), org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason @@ -503,12 +504,10 @@ private static int getMountInventoryColumns(final AbstractMountInventoryMenu mou @Override public InventoryView openMerchant(Merchant merchant, boolean force) { Preconditions.checkNotNull(merchant, "merchant cannot be null"); - // Paper start - block async calls if (merchant instanceof CraftEntity craftEntity) { - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(craftEntity.entity, "Cannot open merchant screen async from merchant"); + TickThread.ensureTickThread(craftEntity.entity, "Cannot open merchant screen async from merchant"); } - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot open merchant screen async"); - // Paper end - block async calls + TickThread.ensureTickThread(this.entity, "Cannot open merchant screen async"); if (!force && merchant.isTrading()) { return null; @@ -570,7 +569,7 @@ public InventoryView openStonecutter(Location location, boolean force) { } private InventoryView openInventory(Location location, boolean force, Material material) { - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot open " + material + " inventory async"); // Paper - block async calls + TickThread.ensureTickThread(this.entity, "Cannot open " + material + " inventory async"); if (location == null) { location = this.getLocation(); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index 4bc299e436d9..e558a55086d9 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import io.papermc.paper.adventure.PaperAdventure; @@ -505,7 +506,7 @@ public void setKiller(Player killer) { @Override public boolean addPotionEffect(PotionEffect effect) { - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot add potion effect async"); // Paper - block async calls + TickThread.ensureTickThread(this.entity, "Cannot add potion effect async"); return this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index c883a3e54354..d5db087f31b7 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.destroystokyo.paper.event.player.PlayerSetSpawnEvent; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -1339,7 +1340,7 @@ public void setSprinting(boolean sprinting) { @Override public void loadData() { - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot load data async"); // Paper - block async calls + TickThread.ensureTickThread(this.entity, "Cannot load data async"); this.server.getHandle().playerIo.load(this.getHandle().nameAndId()) .map(tag -> TagValueInput.create(ProblemReporter.DISCARDING, this.server.getServer().registryAccess(), tag)) .ifPresent(this.getHandle()::load); @@ -1347,7 +1348,7 @@ public void loadData() { @Override public void saveData() { - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot save data async"); // Paper - block async calls + TickThread.ensureTickThread(this.entity, "Cannot save data async"); this.server.getHandle().playerIo.save(this.getHandle()); } @@ -1664,7 +1665,7 @@ public void setGameMode(GameMode mode) { Preconditions.checkArgument(mode != null, "GameMode cannot be null"); if (this.getHandle().connection == null) return; - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Cannot set gamemode async"); // Paper - block async calls + TickThread.ensureTickThread(this.entity, "Cannot set gamemode async"); this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper - Expand PlayerGameModeChangeEvent } From 41129ba7849225bc4394e88eebdb08e7c1ca9190 Mon Sep 17 00:00:00 2001 From: Dueris Date: Fri, 17 Jul 2026 12:51:59 -0700 Subject: [PATCH 10/12] Thread check advancement award and revoke --- .../server/PlayerAdvancements.java.patch | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/paper-server/patches/sources/net/minecraft/server/PlayerAdvancements.java.patch b/paper-server/patches/sources/net/minecraft/server/PlayerAdvancements.java.patch index f3ef38ec840e..13cedbd3bfee 100644 --- a/paper-server/patches/sources/net/minecraft/server/PlayerAdvancements.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/PlayerAdvancements.java.patch @@ -25,7 +25,12 @@ LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", id, this.playerSavePath); } else { this.startProgress(advancement, progress); -@@ -168,14 +_,31 @@ +@@ -164,18 +_,36 @@ + } + + public boolean award(final AdvancementHolder holder, final String criterion) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.player, "Cannot award advancement async"); // Paper - block async calls + boolean result = false; AdvancementProgress progress = this.getOrStartProgress(holder); boolean wasDone = progress.isDone(); if (progress.grantProgress(criterion)) { @@ -59,6 +64,14 @@ } }); } +@@ -189,6 +_,7 @@ + } + + public boolean revoke(final AdvancementHolder advancement, final String criterion) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.player, "Cannot revoke advancement async"); // Paper - block async calls + boolean result = false; + AdvancementProgress progress = this.getOrStartProgress(advancement); + boolean wasDone = progress.isDone(); @@ -238,7 +_,7 @@ public void flushDirty(final ServerPlayer player, final boolean showAdvancements) { if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !this.progressChanged.isEmpty()) { From 7afc7f4579f66552fd61120187b066f7b3b85b7d Mon Sep 17 00:00:00 2001 From: Dueris Date: Fri, 17 Jul 2026 14:59:28 -0700 Subject: [PATCH 11/12] Thread check recipe add & remove --- .../minecraft/stats/ServerRecipeBook.java.patch | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch b/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch index 79fcad6dbccc..c3055072189f 100644 --- a/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch +++ b/paper-server/patches/sources/net/minecraft/stats/ServerRecipeBook.java.patch @@ -1,6 +1,12 @@ --- a/net/minecraft/stats/ServerRecipeBook.java +++ b/net/minecraft/stats/ServerRecipeBook.java -@@ -64,17 +_,21 @@ +@@ -59,22 +_,27 @@ + } + + public int addRecipes(final Collection> recipes, final ServerPlayer player) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(player, "Cannot add recipes async"); // Paper - block async calls + List recipesToAdd = new ArrayList<>(); + for (RecipeHolder recipe : recipes) { ResourceKey> id = recipe.id(); if (!this.known.contains(id) && !recipe.value().isSpecial()) { @@ -24,6 +30,14 @@ player.connection.send(new ClientboundRecipeBookAddPacket(recipesToAdd, false)); } +@@ -82,6 +_,7 @@ + } + + public int removeRecipes(final Collection> recipes, final ServerPlayer player) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(player, "Cannot remove recipes async"); // Paper - block async calls + List recipesToRemove = Lists.newArrayList(); + + for (RecipeHolder recipe : recipes) { @@ -92,7 +_,7 @@ } } From 8124224ede97203ff4b0363ed8c939c4531b3244 Mon Sep 17 00:00:00 2001 From: Dueris Date: Fri, 17 Jul 2026 14:59:37 -0700 Subject: [PATCH 12/12] Thread check criterion trigger --- .../advancements/triggers/SimpleCriterionTrigger.java.patch | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/paper-server/patches/sources/net/minecraft/advancements/triggers/SimpleCriterionTrigger.java.patch b/paper-server/patches/sources/net/minecraft/advancements/triggers/SimpleCriterionTrigger.java.patch index 5e4628730ff0..5c3c8bc03b9d 100644 --- a/paper-server/patches/sources/net/minecraft/advancements/triggers/SimpleCriterionTrigger.java.patch +++ b/paper-server/patches/sources/net/minecraft/advancements/triggers/SimpleCriterionTrigger.java.patch @@ -1,6 +1,10 @@ --- a/net/minecraft/advancements/triggers/SimpleCriterionTrigger.java +++ b/net/minecraft/advancements/triggers/SimpleCriterionTrigger.java -@@ -20,14 +_,14 @@ +@@ -17,17 +_,18 @@ + + public abstract class SimpleCriterionTrigger implements CriterionTrigger { + protected void trigger(final ServerPlayer player, final Predicate matcher) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(player, "Cannot trigger criterion async"); // Paper - block async calls PlayerAdvancements advancements = player.getAdvancements(); Map listenersForType = advancements.getTriggerMapForType(this); if (listenersForType != null && !listenersForType.isEmpty()) {