diff --git a/canvas-server/minecraft-patches/base/0014-Restore-detached-fishing-hook-portal-bug.patch b/canvas-server/minecraft-patches/base/0014-Restore-detached-fishing-hook-portal-bug.patch new file mode 100644 index 0000000000..4691f28262 --- /dev/null +++ b/canvas-server/minecraft-patches/base/0014-Restore-detached-fishing-hook-portal-bug.patch @@ -0,0 +1,467 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BaconCat__ <126538523+BaconCat1@users.noreply.github.com> +Date: Thu, 19 Mar 2026 19:44:24 -0500 +Subject: [PATCH] Restore detached fishing hook portal bug + + +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index aca88b9ec53debc85387fcd5920eef1d0292b769..e1860e6bc6e4fbfc9f06b086cd84fabd221cc184 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -14,7 +14,6 @@ import com.mojang.serialization.MapCodec; + import com.mojang.serialization.codecs.RecordCodecBuilder; + import java.net.InetSocketAddress; + import java.util.Collection; +-import java.util.HashSet; + import java.util.List; + import java.util.Optional; + import java.util.OptionalInt; +@@ -266,7 +265,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + private @Nullable BlockPos raidOmenPosition; + private Vec3 lastKnownClientMovement = Vec3.ZERO; + private Input lastClientInput = Input.EMPTY; +- private final Set enderPearls = new HashSet<>(); ++ private final Set enderPearls = java.util.concurrent.ConcurrentHashMap.newKeySet(); // Canvas - restore vanilla ender pearl behavior ++ private final java.util.concurrent.ConcurrentMap canvas$enderPearlSnapshots = new java.util.concurrent.ConcurrentHashMap<>(); // Canvas - restore detached fishing hook portal behavior + private long timeEntitySatOnShoulder; + private CompoundTag shoulderEntityLeft = new CompoundTag(); + private CompoundTag shoulderEntityRight = new CompoundTag(); +@@ -3404,16 +3404,52 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + + public void registerEnderPearl(ThrownEnderpearl enderPearl) { + //this.enderPearls.add(enderPearl); // Folia - region threading - do not track ender pearls ++ if (io.canvasmc.canvas.Config.INSTANCE.restoreVanillaEnderPearlBehavior) { // Canvas - restore vanilla ender pearl behavior ++ this.enderPearls.add(enderPearl); ++ } ++ this.canvas$updateEnderPearlSnapshot(enderPearl); // Canvas - restore detached fishing hook portal behavior + } + + public void deregisterEnderPearl(ThrownEnderpearl enderPearl) { + //this.enderPearls.remove(enderPearl); // Folia - region threading - do not track ender pearls ++ if (io.canvasmc.canvas.Config.INSTANCE.restoreVanillaEnderPearlBehavior) { // Canvas - restore vanilla ender pearl behavior ++ this.enderPearls.remove(enderPearl); ++ } ++ this.canvas$enderPearlSnapshots.remove(enderPearl.getUUID()); // Canvas - restore detached fishing hook portal behavior + } + + public Set getEnderPearls() { + return this.enderPearls; + } + ++ // Canvas start - restore detached fishing hook portal behavior ++ public void canvas$updateEnderPearlSnapshot(ThrownEnderpearl enderPearl) { ++ this.canvas$enderPearlSnapshots.put( ++ enderPearl.getUUID(), ++ new CanvasEnderPearlSnapshot(enderPearl.level().dimension(), enderPearl.chunkPosition().toLong()) ++ ); ++ } ++ ++ public boolean canvas$hasDetachedFishingHookCrossChunkPearl(net.minecraft.resources.ResourceKey dimension, long hookChunkPos) { ++ for (CanvasEnderPearlSnapshot snapshot : this.canvas$enderPearlSnapshots.values()) { ++ if (snapshot.dimension.equals(dimension) && snapshot.chunkPos != hookChunkPos) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ private static final class CanvasEnderPearlSnapshot { ++ private final net.minecraft.resources.ResourceKey dimension; ++ private final long chunkPos; ++ ++ private CanvasEnderPearlSnapshot(net.minecraft.resources.ResourceKey dimension, long chunkPos) { ++ this.dimension = dimension; ++ this.chunkPos = chunkPos; ++ } ++ } ++ // Canvas end - restore detached fishing hook portal behavior ++ + public CompoundTag getShoulderEntityLeft() { + return this.shoulderEntityLeft; + } +diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java +index 874da7be8423434b4c0ea429e4a88026f90f1338..797f2a2872fbabd742ec5336a3e13272dc25fd18 100644 +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -1370,6 +1370,21 @@ public abstract class Player extends Avatar implements ContainerUser { + @Override + protected void preRemove(RemovalReason reason) { + super.preRemove(reason); ++ // Canvas start - restore detached fishing hook portal behavior ++ if (reason == RemovalReason.CHANGED_DIMENSION && io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug) { ++ if (this.fishing != null) { ++ final long hookChunkPos = this.fishing.canvas$getHookChunkPositionSnapshot(); ++ final boolean hasEnderPearlInDifferentChunk = this instanceof net.minecraft.server.level.ServerPlayer serverPlayer ++ && serverPlayer.canvas$hasDetachedFishingHookCrossChunkPearl(this.level().dimension(), hookChunkPos); ++ this.fishing.canvas$markPortalTransferState( ++ hasEnderPearlInDifferentChunk, ++ this.getUUID(), ++ this.fishing.canvas$hasExternalChunkLoaderSnapshot() ++ ); ++ } ++ return; ++ } ++ // Canvas end - restore detached fishing hook portal behavior + this.fishing = null; + } + // Folia end - region threading +diff --git a/net/minecraft/world/entity/projectile/FishingHook.java b/net/minecraft/world/entity/projectile/FishingHook.java +index 71f6acc07d222fe512bf8d9fbaad35cb36bcf2d6..905464d56d6581e42bf971b56dc263ae639dc112 100644 +--- a/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/net/minecraft/world/entity/projectile/FishingHook.java +@@ -68,6 +68,17 @@ public class FishingHook extends Projectile { + private final int luck; + private final int lureSpeed; + private final InterpolationHandler interpolationHandler = new InterpolationHandler(this); ++ // Canvas start - restore detached fishing hook portal behavior ++ private volatile boolean canvas$detachedPortalBugActive = false; ++ private volatile boolean canvas$realizeScheduled = false; ++ private volatile long canvas$hookChunkPositionSnapshot = Long.MIN_VALUE; ++ private long canvas$lastChunkStatusScanTick = Long.MIN_VALUE; ++ private volatile boolean canvas$chunkHasExternalLoader = false; ++ private volatile java.util.UUID canvas$detachedOwnerUuid = null; ++ private volatile boolean canvas$forceRealizeAfterCrossDimensionArrival = false; ++ private volatile boolean canvas$cachedShouldStopFishing = false; ++ private long canvas$cachedShouldStopFishingTick = Long.MIN_VALUE; ++ // Canvas end - restore detached fishing hook portal behavior + + // CraftBukkit start - Extra variables to enable modification of fishing wait time, values are minecraft defaults + public int minWaitTime = 100; +@@ -166,8 +177,29 @@ public class FishingHook extends Projectile { + this.syncronizedRandom.setSeed(this.getUUID().getLeastSignificantBits() ^ this.level().getGameTime()); + this.getInterpolation().interpolate(); + super.tick(); ++ // Canvas start - restore detached fishing hook portal behavior ++ if (io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug) { ++ this.canvas$hookChunkPositionSnapshot = this.chunkPosition().toLong(); ++ long gameTime = this.level().getGameTime(); ++ if (gameTime - this.canvas$lastChunkStatusScanTick >= 20L) { // cache the nearby-player chunk-loader scan; refresh once per second ++ this.canvas$chunkHasExternalLoader = this.canvas$hasExternalChunkLoader(); ++ this.canvas$lastChunkStatusScanTick = gameTime; ++ } ++ } + Player playerOwner = this.getPlayerOwner(); ++ if (this.canvas$tryHandleDetachedHook(playerOwner)) { ++ return; ++ } ++ // Canvas end - restore detached fishing hook portal behavior + if (playerOwner == null) { ++ // Canvas start - restore detached fishing hook portal behavior ++ if (io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug ++ && this.canvas$detachedPortalBugActive ++ && this.canvas$detachedOwnerUuid != null) { ++ this.canvas$tryHandleDetachedHook(this.canvas$getReloggedOwner()); ++ return; ++ } ++ // Canvas end - restore detached fishing hook portal behavior + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else if (this.level().isClientSide() || !this.shouldStopFishing(playerOwner)) { + if (this.onGround()) { +@@ -207,6 +239,10 @@ public class FishingHook extends Projectile { + } else { + if (this.currentState == FishingHook.FishHookState.HOOKED_IN_ENTITY) { + if (this.hookedIn != null) { ++ if (io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug ++ && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.hookedIn)) { ++ return; ++ } + if (!this.hookedIn.isRemoved() && this.hookedIn.canInteractWithLevel() && this.hookedIn.level().dimension() == this.level().dimension() + ) + { +@@ -270,12 +306,54 @@ public class FishingHook extends Projectile { + } + + private boolean shouldStopFishing(Player player) { ++ long gameTime = this.level().getGameTime(); + // Folia start - region threading + if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player)) { +- return true; ++ boolean cacheFresh = gameTime - this.canvas$cachedShouldStopFishingTick <= 20L; ++ // Canvas start - restore detached fishing hook portal behavior ++ if (io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug && this.canvas$detachedPortalBugActive) { ++ if (!this.canvas$realizeScheduled ++ && player.level().dimension() == this.level().dimension() ++ && !canvas$isPortalTransitionActive(player)) { ++ this.canvas$realizeScheduled = true; ++ player.scheduleToOrRun(() -> this.scheduleToOrRun(() -> { ++ this.canvas$realizeScheduled = false; ++ if (this.canvas$detachedPortalBugActive && !this.isRemoved() && !canvas$isPortalTransitionActive(player)) { ++ this.canvas$realizeDetachedHook(player); ++ } ++ })); ++ } ++ // Off-thread: only trust a fresh cached decision; otherwise deny interaction. ++ return cacheFresh && this.canvas$cachedShouldStopFishing == false ? false : true; ++ } ++ // Canvas end - restore detached fishing hook portal behavior ++ // Off-thread: rely on cached decision if fresh; otherwise return true to avoid off-thread work. ++ return cacheFresh ? this.canvas$cachedShouldStopFishing : true; + } + // Folia end - region threading ++ boolean stop = this.canvas$computeShouldStopFishingOnOwnerThread(player); ++ this.canvas$cachedShouldStopFishing = stop; ++ this.canvas$cachedShouldStopFishingTick = gameTime; ++ return stop; ++ } ++ ++ private boolean canvas$computeShouldStopFishingOnOwnerThread(Player player) { ++ // Canvas start - restore detached fishing hook portal behavior ++ if (io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug ++ && this.canvas$detachedPortalBugActive ++ && player.isRemoved() ++ && player.getRemovalReason() == Entity.RemovalReason.CHANGED_DIMENSION) { ++ return false; ++ } ++ // Canvas end - restore detached fishing hook portal behavior + if (player.canInteractWithLevel()) { ++ // Canvas start - restore detached fishing hook portal behavior ++ if (io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug ++ && this.canvas$detachedPortalBugActive ++ && player.level().dimension() != this.level().dimension()) { ++ return false; ++ } ++ // Canvas end - restore detached fishing hook portal behavior + ItemStack mainHandItem = player.getMainHandItem(); + ItemStack offhandItem = player.getOffhandItem(); + boolean isFishingRod = mainHandItem.is(Items.FISHING_ROD); +@@ -513,6 +591,21 @@ public class FishingHook extends Projectile { + public int retrieve(ItemStack stack, net.minecraft.world.InteractionHand hand) { + // Paper end - Add hand parameter to PlayerFishEvent + Player playerOwner = this.getPlayerOwner(); ++ if (!this.level().isClientSide() ++ && playerOwner instanceof ServerPlayer serverPlayer ++ && (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this) ++ || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(serverPlayer))) { ++ if (io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug) { ++ ItemStack stackCopy = stack.copy(); // Canvas - restore detached fishing hook portal behavior ++ this.scheduleToOrRun(() -> this.canvas$retrieveFromDifferentRegion(serverPlayer, stackCopy, hand)); ++ } ++ return 0; ++ } ++ return this.canvas$retrieve(playerOwner, stack, hand); ++ } ++ ++ // Canvas start - restore detached fishing hook portal behavior ++ private int canvas$retrieve(@Nullable Player playerOwner, ItemStack stack, net.minecraft.world.InteractionHand hand) { + if (!this.level().isClientSide() && playerOwner != null && !this.shouldStopFishing(playerOwner)) { + int i = 0; + if (this.hookedIn != null) { +@@ -595,6 +688,29 @@ public class FishingHook extends Projectile { + } + } + ++ public void canvas$retrieveFromDifferentRegion(ServerPlayer player, ItemStack stack, net.minecraft.world.InteractionHand hand) { ++ if (!io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug ++ || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this) ++ || this.isRemoved()) { ++ return; ++ } ++ int rodDamage = this.canvas$retrieve(player, stack, hand); ++ if (rodDamage > 0 && !player.isRemoved()) { ++ player.scheduleToOrRun(() -> player.getItemInHand(hand).hurtAndBreak(rodDamage, player, hand.asEquipmentSlot())); ++ } ++ } ++ ++ public void canvas$tryRealizeDetachedHookFromDifferentRegion(ServerPlayer player) { ++ if (!io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug ++ || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this)) { ++ return; ++ } ++ if (this.canvas$detachedPortalBugActive && !this.isRemoved()) { ++ this.canvas$scheduleOrRealizeDetachedHook(player); ++ } ++ } ++ // Canvas end - restore detached fishing hook portal behavior ++ + @Override + public void handleEntityEvent(byte id) { + if (id == EntityEvent.FISHING_ROD_REEL_IN && this.level().isClientSide() && this.hookedIn instanceof Player player && player.isLocalPlayer()) { +@@ -645,11 +761,45 @@ public class FishingHook extends Projectile { + private void updateOwnerInfo(@Nullable FishingHook fishingHook) { + Player playerOwner = this.getPlayerOwner(); + if (playerOwner != null) { +- playerOwner.fishing = fishingHook; ++ Runnable task = () -> { ++ if (fishingHook == null) { ++ if (playerOwner.fishing == this) { ++ playerOwner.fishing = null; ++ } ++ } else { ++ playerOwner.fishing = fishingHook; ++ } ++ }; ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(playerOwner)) { ++ task.run(); ++ } else { ++ playerOwner.scheduleToOrRun(task); ++ } + } + } + + public @Nullable Player getPlayerOwner() { ++ // Canvas start - restore detached fishing hook portal behavior ++ if (io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug ++ && this.canvas$detachedPortalBugActive ++ && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this)) { ++ Entity owner = this.getOwnerRaw(); ++ if (owner instanceof Player player && !player.isRemoved()) { ++ return player; ++ } ++ } ++ if (io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug ++ && this.canvas$detachedPortalBugActive ++ && this.canvas$detachedOwnerUuid != null) { ++ net.minecraft.server.MinecraftServer server = net.minecraft.server.MinecraftServer.getServer(); ++ if (server != null) { ++ ServerPlayer fallback = server.getPlayerList().getPlayer(this.canvas$detachedOwnerUuid); ++ if (fallback != null) { ++ return fallback; ++ } ++ } ++ } ++ // Canvas end - restore detached fishing hook portal behavior + return this.getOwner() instanceof Player player ? player : null; + } + +@@ -678,6 +828,117 @@ public class FishingHook extends Projectile { + } + } + ++ // Canvas start - restore detached fishing hook portal behavior ++ private boolean canvas$tryHandleDetachedHook(@Nullable Player playerOwner) { ++ if (!this.canvas$detachedPortalBugActive) { ++ return false; ++ } ++ if (this.canvas$getChunkLoadingPlayer() != null) { ++ this.canvas$realizeDetachedHook(playerOwner); ++ return true; ++ } ++ if (playerOwner == null || canvas$isPortalTransitionActive(playerOwner)) { ++ return true; ++ } ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(playerOwner) ++ && playerOwner.level().dimension() == this.level().dimension()) { ++ this.canvas$realizeDetachedHook(playerOwner); ++ return true; ++ } ++ if (this.canvas$forceRealizeAfterCrossDimensionArrival && playerOwner instanceof ServerPlayer serverPlayer) { ++ this.canvas$scheduleOrRealizeDetachedHook(serverPlayer); ++ return true; ++ } ++ return true; ++ } ++ ++ private void canvas$scheduleOrRealizeDetachedHook(ServerPlayer player) { ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player)) { ++ this.canvas$realizeDetachedHook(player); ++ } else if (!this.canvas$realizeScheduled) { ++ this.canvas$realizeScheduled = true; ++ player.scheduleToOrRun(() -> this.scheduleToOrRun(() -> { ++ this.canvas$realizeScheduled = false; ++ if (this.canvas$detachedPortalBugActive && !this.isRemoved() && !canvas$isPortalTransitionActive(player)) { ++ this.canvas$realizeDetachedHook(player); ++ } ++ })); ++ } ++ } ++ ++ private @Nullable ServerPlayer canvas$getReloggedOwner() { ++ if (this.canvas$detachedOwnerUuid == null) { ++ return null; ++ } ++ net.minecraft.server.MinecraftServer server = net.minecraft.server.MinecraftServer.getServer(); ++ return server == null ? null : server.getPlayerList().getPlayer(this.canvas$detachedOwnerUuid); ++ } ++ ++ private void canvas$realizeDetachedHook(@Nullable Player playerOwner) { ++ if (playerOwner != null ++ && playerOwner.level().dimension() == this.level().dimension() ++ && this.hookedIn != null ++ && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.hookedIn) ++ && !this.hookedIn.isRemoved() ++ && this.hookedIn.canInteractWithLevel()) { ++ this.pullEntity(this.hookedIn); ++ } ++ this.level().broadcastEntityEvent(this, EntityEvent.FISHING_ROD_REEL_IN); ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause ++ this.canvas$detachedPortalBugActive = false; ++ this.canvas$realizeScheduled = false; ++ this.canvas$detachedOwnerUuid = null; ++ this.canvas$forceRealizeAfterCrossDimensionArrival = false; ++ this.updateOwnerInfo(null); ++ } ++ ++ private static boolean canvas$isPortalTransitionActive(Player player) { ++ if (player.isRemoved() && player.getRemovalReason() == Entity.RemovalReason.CHANGED_DIMENSION) { ++ return true; ++ } ++ return player instanceof ServerPlayer serverPlayer && serverPlayer.isChangingDimension(); ++ } ++ ++ private ServerPlayer canvas$getChunkLoadingPlayer() { ++ if (!(this.level() instanceof ServerLevel serverLevel)) { ++ return null; ++ } ++ ca.spottedleaf.moonrise.common.list.ReferenceList players = ++ serverLevel.moonrise$getNearbyPlayers().getPlayers(this.chunkPosition(), ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.VIEW_DISTANCE); ++ if (players == null || players.size() == 0) { ++ return null; ++ } ++ ServerPlayer[] raw = players.getRawDataUnchecked(); ++ for (int i = 0, len = players.size(); i < len; ++i) { ++ ServerPlayer player = raw[i]; ++ if (player != null && !player.isRemoved()) { ++ return player; ++ } ++ } ++ return null; ++ } ++ ++ public long canvas$getHookChunkPositionSnapshot() { ++ return this.canvas$hookChunkPositionSnapshot; ++ } ++ ++ public boolean canvas$hasExternalChunkLoaderSnapshot() { ++ return this.canvas$chunkHasExternalLoader; ++ } ++ ++ public void canvas$markPortalTransferState(boolean active, java.util.UUID ownerUuid, boolean forceRealizeAfterCrossDimensionArrival) { ++ this.canvas$detachedPortalBugActive = active; ++ this.canvas$realizeScheduled = false; ++ this.canvas$detachedOwnerUuid = active ? ownerUuid : null; ++ this.canvas$forceRealizeAfterCrossDimensionArrival = active && forceRealizeAfterCrossDimensionArrival; ++ } ++ ++ private boolean canvas$hasExternalChunkLoader() { ++ ServerPlayer loadingPlayer = this.canvas$getChunkLoadingPlayer(); ++ return loadingPlayer != null && (this.canvas$detachedOwnerUuid == null || !this.canvas$detachedOwnerUuid.equals(loadingPlayer.getUUID())); ++ } ++ // Canvas end - restore detached fishing hook portal behavior ++ + public static enum FishHookState { + FLYING, + HOOKED_IN_ENTITY, +diff --git a/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java b/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java +index a9c8f1b1c8bb32fbbc2048ac0f8a435922fa483e..172ccab92f15f04aa744445c07df5825f8d9ccc2 100644 +--- a/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java ++++ b/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java +@@ -266,14 +266,17 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { + } + + if (this.isAlive()) { ++ if (entity instanceof ServerPlayer serverPlayer1) { ++ serverPlayer1.canvas$updateEnderPearlSnapshot(this); // Canvas - restore detached fishing hook portal behavior ++ } + BlockPos blockPos = BlockPos.containing(this.position()); + if (( + --this.ticketTimer <= 0L + || var7 != SectionPos.blockToSectionCoord(blockPos.getX()) + || sectionPosZ != SectionPos.blockToSectionCoord(blockPos.getZ()) + ) +- && entity instanceof ServerPlayer serverPlayer1) { +- this.ticketTimer = serverPlayer1.registerAndUpdateEnderPearlTicket(this); ++ && entity instanceof ServerPlayer serverPlayer2) { ++ this.ticketTimer = serverPlayer2.registerAndUpdateEnderPearlTicket(this); + } + } + } else { diff --git a/canvas-server/minecraft-patches/base/0014-Rewrite-Waypoints.patch b/canvas-server/minecraft-patches/base/0015-Rewrite-Waypoints.patch similarity index 98% rename from canvas-server/minecraft-patches/base/0014-Rewrite-Waypoints.patch rename to canvas-server/minecraft-patches/base/0015-Rewrite-Waypoints.patch index 89c3368bc9..f0f880dfb6 100644 --- a/canvas-server/minecraft-patches/base/0014-Rewrite-Waypoints.patch +++ b/canvas-server/minecraft-patches/base/0015-Rewrite-Waypoints.patch @@ -31,7 +31,7 @@ index 702ed101f2345bcdc99612bddce70d4e5566f6bb..9d86118655dd3b0ef349770280ad3e91 //this.updateSkyBrightness(); // Folia - region threading - delay until first tick // Paper start - rewrite chunk system diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index aca88b9ec53debc85387fcd5920eef1d0292b769..3777c81fb5123cae478af94048e6b843c207b5c0 100644 +index e1860e6bc6e4fbfc9f06b086cd84fabd221cc184..3ba030012809f789356f58bb2919eb0aa6b76949 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -435,6 +435,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @@ -136,7 +136,7 @@ index 1cbc46e480cc7d109c6128f109a1a9e38bf1a193..ed9cba657d336b4577c4c1f3b808ae9d } } diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 1caba4a4ec839ac20d00f8efd7af501c5af279db..b19db8806c21ef526cfa30cb64885f47d598dbb8 100644 +index bcf011f010e2483a6af320eeda9fc32ddebd9546..5af2a7238df00840ab41fccd245b50704dd9b2f7 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -4378,7 +4378,9 @@ public abstract class Entity implements SyncedDataHolder, DebugValueSource, Name diff --git a/canvas-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/canvas-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch index bdc5e34c75..f2f546cd26 100644 --- a/canvas-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ b/canvas-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -1,23 +1,32 @@ --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java -@@ -266,7 +_,7 @@ +@@ -265,7 +_,7 @@ private @Nullable BlockPos raidOmenPosition; private Vec3 lastKnownClientMovement = Vec3.ZERO; private Input lastClientInput = Input.EMPTY; -- private final Set enderPearls = new HashSet<>(); +- private final Set enderPearls = java.util.concurrent.ConcurrentHashMap.newKeySet(); // Canvas - restore vanilla ender pearl behavior + private final Set enderPearls = java.util.concurrent.ConcurrentHashMap.newKeySet(); // Canvas - region threading + private final java.util.concurrent.ConcurrentMap canvas$enderPearlSnapshots = new java.util.concurrent.ConcurrentHashMap<>(); // Canvas - restore detached fishing hook portal behavior private long timeEntitySatOnShoulder; private CompoundTag shoulderEntityLeft = new CompoundTag(); - private CompoundTag shoulderEntityRight = new CompoundTag(); -@@ -428,6 +_,8 @@ +@@ -428,6 +_,9 @@ public boolean sentListPacket = false; public boolean suppressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready // CraftBukkit end + public final io.canvasmc.canvas.RegionizedTpsBar.DisplayManager canvas$tpsBarDisplay = io.canvasmc.canvas.RegionizedTpsBar.DisplayManager.createNew(this); // Canvas - tpsbar + public final int canvas$infoBucketIndex; // Canvas - optimize playerlist tick ++ public final it.unimi.dsi.fastutil.objects.Object2ObjectMap canvas$activeWaypoints = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); // Canvas - region threading public boolean isRealPlayer; // Paper public com.destroystokyo.paper.event.entity.@Nullable PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent public org.bukkit.event.player.PlayerQuitEvent.@Nullable QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event +@@ -435,7 +_,6 @@ + // Paper start - rewrite chunk system + private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; + private final ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.ViewDistanceHolder viewDistanceHolder = new ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.ViewDistanceHolder(); +- public final it.unimi.dsi.fastutil.objects.Object2ObjectMap canvas$activeWaypoints = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); // Canvas - region threading + + @Override + public final boolean moonrise$isRealPlayer() { @@ -480,6 +_,7 @@ this.bukkitPickUpLoot = true; this.maxHealthCache = this.getMaxHealth(); @@ -351,23 +360,29 @@ this.ejectPassengers(); // Paper start - Workaround vehicle not tracking the passenger disconnection dismount -@@ -3404,11 +_,13 @@ - } +@@ -3405,17 +_,15 @@ public void registerEnderPearl(ThrownEnderpearl enderPearl) { -- //this.enderPearls.add(enderPearl); // Folia - region threading - do not track ender pearls + //this.enderPearls.add(enderPearl); // Folia - region threading - do not track ender pearls +- if (io.canvasmc.canvas.Config.INSTANCE.restoreVanillaEnderPearlBehavior) { // Canvas - restore vanilla ender pearl behavior +- this.enderPearls.add(enderPearl); +- } + if (!io.canvasmc.canvas.Config.INSTANCE.restoreVanillaEnderPearlBehavior) return; // Canvas - restore vanilla ender pearl behavior + this.enderPearls.add(enderPearl); + this.canvas$updateEnderPearlSnapshot(enderPearl); // Canvas - restore detached fishing hook portal behavior } public void deregisterEnderPearl(ThrownEnderpearl enderPearl) { -- //this.enderPearls.remove(enderPearl); // Folia - region threading - do not track ender pearls + //this.enderPearls.remove(enderPearl); // Folia - region threading - do not track ender pearls +- if (io.canvasmc.canvas.Config.INSTANCE.restoreVanillaEnderPearlBehavior) { // Canvas - restore vanilla ender pearl behavior +- this.enderPearls.remove(enderPearl); +- } + if (!io.canvasmc.canvas.Config.INSTANCE.restoreVanillaEnderPearlBehavior) return; // Canvas - restore vanilla ender pearl behavior + this.enderPearls.remove(enderPearl); + this.canvas$enderPearlSnapshots.remove(enderPearl.getUUID()); // Canvas - restore detached fishing hook portal behavior } - public Set getEnderPearls() { -@@ -3454,7 +_,7 @@ +@@ -3490,7 +_,7 @@ } public Set> debugSubscriptions() { @@ -376,7 +391,7 @@ } public record RespawnConfig(LevelData.RespawnData respawnData, boolean forced) { -@@ -3467,7 +_,7 @@ +@@ -3503,7 +_,7 @@ ); static ResourceKey getDimensionOrDefault(ServerPlayer.@Nullable RespawnConfig respawnConfig) { diff --git a/canvas-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch b/canvas-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch index dd5c8dbf3b..9ee099505a 100644 --- a/canvas-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch +++ b/canvas-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch @@ -74,7 +74,7 @@ int i = (int)(f * 0.5); ((ServerLevel)this.level()) .sendParticles(ParticleTypes.DAMAGE_INDICATOR, target.getX(), target.getY(0.5), target.getZ(), i, 0.1, 0.0, 0.1, 0.2); -@@ -1960,6 +_,7 @@ +@@ -1975,6 +_,7 @@ } public float getCurrentItemAttackStrengthDelay() { @@ -82,7 +82,7 @@ return (float)(1.0 / this.getAttributeValue(Attributes.ATTACK_SPEED) * 20.0); } -@@ -1970,6 +_,7 @@ +@@ -1985,6 +_,7 @@ } public float getAttackStrengthScale(float adjustTicks) { diff --git a/canvas-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch b/canvas-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch index 10761bd55d..894235ce14 100644 --- a/canvas-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch +++ b/canvas-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch @@ -1,15 +1,26 @@ --- a/net/minecraft/world/entity/projectile/FishingHook.java +++ b/net/minecraft/world/entity/projectile/FishingHook.java -@@ -207,7 +_,7 @@ +@@ -239,14 +_,15 @@ } else { if (this.currentState == FishingHook.FishHookState.HOOKED_IN_ENTITY) { if (this.hookedIn != null) { -- if (!this.hookedIn.isRemoved() && this.hookedIn.canInteractWithLevel() && this.hookedIn.level().dimension() == this.level().dimension() -+ if (!this.hookedIn.isRemoved() && this.hookedIn.canInteractWithLevel() && this.hookedIn.level().dimension() == this.level().dimension() && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.hookedIn) // Canvas - region threading +- if (io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug +- && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.hookedIn)) { +- return; +- } + if (!this.hookedIn.isRemoved() && this.hookedIn.canInteractWithLevel() && this.hookedIn.level().dimension() == this.level().dimension() ++ && (!io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug ++ || ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.hookedIn)) ) { this.setPos(this.hookedIn.getX(), this.hookedIn.getY(0.8), this.hookedIn.getZ()); -@@ -516,6 +_,11 @@ ++ } else if (io.canvasmc.canvas.Config.INSTANCE.combat.restoreDetachedFishingHookPortalBug ++ && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.hookedIn)) { ++ return; + } else { + this.setHookedEntity(null); + new io.papermc.paper.event.entity.FishHookStateChangeEvent((org.bukkit.entity.FishHook) getBukkitEntity(), org.bukkit.entity.FishHook.HookState.UNHOOKED).callEvent(); // Paper - Add FishHookStateChangeEvent. #UNHOOKED +@@ -609,6 +_,11 @@ if (!this.level().isClientSide() && playerOwner != null && !this.shouldStopFishing(playerOwner)) { int i = 0; if (this.hookedIn != null) { @@ -21,7 +32,7 @@ // CraftBukkit start org.bukkit.event.player.PlayerFishEvent playerFishEvent = new org.bukkit.event.player.PlayerFishEvent((org.bukkit.entity.Player) playerOwner.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (org.bukkit.entity.FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), org.bukkit.event.player.PlayerFishEvent.State.CAUGHT_ENTITY); // Paper - Add hand parameter to PlayerFishEvent if (!playerFishEvent.callEvent()) { -@@ -609,6 +_,14 @@ +@@ -725,6 +_,14 @@ if (owner != null) { Vec3 vec3 = new Vec3(owner.getX() - this.getX(), owner.getY() - this.getY(), owner.getZ() - this.getZ()).scale(0.1); entity.setDeltaMovement(entity.getDeltaMovement().add(vec3)); diff --git a/canvas-server/src/main/java/io/canvasmc/canvas/Config.java b/canvas-server/src/main/java/io/canvasmc/canvas/Config.java index 892da66146..420d0a7c4f 100644 --- a/canvas-server/src/main/java/io/canvasmc/canvas/Config.java +++ b/canvas-server/src/main/java/io/canvasmc/canvas/Config.java @@ -627,6 +627,12 @@ public static class Mace { @Comment("Allows toggling if a fishing rod can pull entities") public boolean fishingRodPulls = true; + @Comment({ + "Restores vanilla detached fishing-hook behavior used by wireless redstone setups.", + "When enabled, hooks keep their attached state while owner or target are on another region thread." + }) + public boolean restoreDetachedFishingHookPortalBug = false; + @Comment("Configures the damage modifier per critical hit") public float criticalHitMultiplier = 1.5F;