diff --git a/leaf-server/minecraft-patches/features/0302-PWT-Entity-Teleport-Fix.patch b/leaf-server/minecraft-patches/features/0302-PWT-Entity-Teleport-Fix.patch new file mode 100644 index 000000000..b79adc32e --- /dev/null +++ b/leaf-server/minecraft-patches/features/0302-PWT-Entity-Teleport-Fix.patch @@ -0,0 +1,257 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: splatage +Date: Tue, 10 Mar 2026 11:53:48 +1300 +Subject: [PATCH] PWT Entity Teleport Fix + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 912ef41ae1e97ff856386394d8816710d31d2e13..3ca4c34011d4c473c30247490b48c7793aeeb910 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -138,10 +138,13 @@ import net.minecraft.util.thread.ReentrantBlockableEventLoop; + import net.minecraft.world.Difficulty; + import net.minecraft.world.RandomSequences; + import net.minecraft.world.Stopwatches; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.PortalProcessor; + import net.minecraft.world.entity.ai.village.VillageSiege; + import net.minecraft.world.entity.npc.CatSpawner; + import net.minecraft.world.entity.npc.wanderingtrader.WanderingTraderSpawner; + import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.level.portal.TeleportTransition; + import net.minecraft.world.flag.FeatureFlagSet; + import net.minecraft.world.flag.FeatureFlags; + import net.minecraft.world.item.alchemy.PotionBrewing; +@@ -312,7 +315,76 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pendingDimensionTransfers = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ ++ private static final class PendingDimensionTransfer { ++ final Entity entity; ++ final ServerLevel sourceLevel; ++ final PortalProcessor portalProcess; ++ ++ private PendingDimensionTransfer(final Entity entity, final ServerLevel sourceLevel, final PortalProcessor portalProcess) { ++ this.entity = entity; ++ this.sourceLevel = sourceLevel; ++ this.portalProcess = portalProcess; ++ } ++ } ++ ++ public final void pwt$queuePendingDimensionTransfer(final Entity entity, final ServerLevel sourceLevel, final PortalProcessor portalProcess) { ++ this.pendingDimensionTransfers.add(new PendingDimensionTransfer(entity, sourceLevel, portalProcess)); ++ } ++ ++ private volatile boolean pwt$processingPendingDimensionTransfers = false; ++ ++ public final boolean pwt$isProcessingPendingDimensionTransfers() { ++ return this.pwt$processingPendingDimensionTransfers; ++ } ++ ++ private void pwt$drainPendingDimensionTransfers() { ++ PendingDimensionTransfer pending; ++ ++ this.pwt$processingPendingDimensionTransfers = true; ++ try { ++ while ((pending = this.pendingDimensionTransfers.poll()) != null) { ++ final Entity entity = pending.entity; ++ if (entity.isRemoved()) { ++ continue; ++ } ++ ++ if (!(entity.level() instanceof ServerLevel currentLevel) || currentLevel != pending.sourceLevel) { ++ continue; ++ } ++ ++ final PortalProcessor currentPortalProcess = entity.portalProcess; ++ if (currentPortalProcess == null || currentPortalProcess != pending.portalProcess) { ++ continue; ++ } ++ ++ if (currentPortalProcess.isParallelCancelledByPlugin()) { ++ entity.portalProcess = null; ++ continue; ++ } ++ ++ final TeleportTransition portalDestination = currentPortalProcess.getPortalDestination(pending.sourceLevel, entity); ++ if (portalDestination != null) { ++ final ServerLevel destinationLevel = portalDestination.newLevel(); ++ if ( ++ pending.sourceLevel.isAllowedToEnterPortal(destinationLevel) ++ && (destinationLevel.dimension() == pending.sourceLevel.dimension() || entity.canTeleport(pending.sourceLevel, destinationLevel)) ++ ) { ++ entity.teleport(portalDestination); ++ } ++ } ++ ++ if (entity.portalProcess == currentPortalProcess) { ++ currentPortalProcess.confirmParallelAsHandled(); ++ } ++ } ++ } finally { ++ this.pwt$processingPendingDimensionTransfers = false; ++ } ++ } ++ ++ // Paper start - improve tick loop + public final ca.spottedleaf.moonrise.common.time.TickData tickTimes1s = new ca.spottedleaf.moonrise.common.time.TickData(java.util.concurrent.TimeUnit.SECONDS.toNanos(1L)); + public final ca.spottedleaf.moonrise.common.time.TickData tickTimes5s = new ca.spottedleaf.moonrise.common.time.TickData(java.util.concurrent.TimeUnit.SECONDS.toNanos(5L)); + public final ca.spottedleaf.moonrise.common.time.TickData tickTimes10s = new ca.spottedleaf.moonrise.common.time.TickData(java.util.concurrent.TimeUnit.SECONDS.toNanos(10L)); +@@ -536,6 +608,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop portalEntityTask = entity -> { +- assert entity.portalProcess != null; +- // Fix NPE when portalProcess becomes null before task execution +- // Portal process was likely nulled out (e.g., expired) between scheduling and execution. +- if (entity.portalProcess == null) { +- return; +- } +- +- if (entity.portalProcess.isParallelCancelledByPlugin()) { +- entity.portalProcess = null; +- return; +- } ++private void pwt$queueDimensionTeleport(ServerLevel serverLevel) { ++ if (this.portalProcess == null) { ++ return; ++ } + +- TeleportTransition portalDestination = entity.portalProcess.getPortalDestination(serverLevel, entity); +- if (portalDestination != null) { +- ServerLevel level = portalDestination.newLevel(); +- if (serverLevel.isAllowedToEnterPortal(level) && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level)) +- ) { +- entity.teleport(portalDestination); +- } +- } +- // Add another null check here just in case teleport() somehow nulled it (defensive) +- if (entity.portalProcess != null) { +- entity.portalProcess.confirmParallelAsHandled(); +- } +- }; ++ if (this.portalProcess.isParallelCancelledByPlugin()) { ++ this.portalProcess = null; ++ return; ++ } + +- this.portalProcess.setParallelAsScheduled(); +- this.getBukkitEntity().taskScheduler.schedule(portalEntityTask, entity -> { +- }, 0); ++ final Entity vehicle = this.getVehicle(); ++ if (vehicle != null) { ++ final PortalProcessor vehiclePortalProcess = vehicle.portalProcess; ++ if (vehiclePortalProcess != null && !vehicle.isRemoved()) { ++ // Mounted passengers must be carried by the vehicle's cross-dimension transfer path. ++ // Drop the passenger-local portal process so it does not queue or retry independently. ++ this.portalProcess = null; ++ return; ++ } + } ++ ++ this.portalProcess.setParallelAsScheduled(); ++ serverLevel.getServer().pwt$queuePendingDimensionTransfer(this, serverLevel, this.portalProcess); ++} + // Leaf start - SparklyPaper - parallel world ticking - mark pending teleport to prevent clearing portal process + + public int getDimensionChangingDelay() {