From eecf17b71af1294e5a0bf3cdc31f99617ff16f0d Mon Sep 17 00:00:00 2001 From: Frieren Date: Mon, 27 Jul 2026 05:21:01 +0400 Subject: [PATCH] Handle Canvas region state during player NPC spawn --- .../nms/v1_21_R7/entity/HumanController.java | 1 + .../nms/v26_1_R1/entity/HumanController.java | 1 + .../nms/v26_1_R1/util/NMSImpl.java | 59 ++++++++++++++++++- .../nms/v26_2_R1/entity/HumanController.java | 1 + 4 files changed, 59 insertions(+), 3 deletions(-) diff --git a/v1_21_R7/src/main/java/net/citizensnpcs/nms/v1_21_R7/entity/HumanController.java b/v1_21_R7/src/main/java/net/citizensnpcs/nms/v1_21_R7/entity/HumanController.java index 386672be8..a11c6ec0a 100644 --- a/v1_21_R7/src/main/java/net/citizensnpcs/nms/v1_21_R7/entity/HumanController.java +++ b/v1_21_R7/src/main/java/net/citizensnpcs/nms/v1_21_R7/entity/HumanController.java @@ -42,6 +42,7 @@ protected Entity createEntity(final Location at, final NPC npc) { final GameProfile profile = new GameProfile(uuid, name); final EntityHumanNPC handle = new EntityHumanNPC(MinecraftServer.getServer(), nmsWorld, profile, ClientInformation.createDefault(), npc); + handle.absSnapTo(at.getX(), at.getY(), at.getZ(), at.getYaw(), at.getPitch()); Skin skin = handle.getSkinTracker().getSkin(); if (skin != null) { skin.apply(handle); diff --git a/v26_1_R1/src/main/java/net/citizensnpcs/nms/v26_1_R1/entity/HumanController.java b/v26_1_R1/src/main/java/net/citizensnpcs/nms/v26_1_R1/entity/HumanController.java index fcee6c9aa..1866b8a58 100644 --- a/v26_1_R1/src/main/java/net/citizensnpcs/nms/v26_1_R1/entity/HumanController.java +++ b/v26_1_R1/src/main/java/net/citizensnpcs/nms/v26_1_R1/entity/HumanController.java @@ -42,6 +42,7 @@ protected Entity createEntity(final Location at, final NPC npc) { final GameProfile profile = new GameProfile(uuid, name); final EntityHumanNPC handle = new EntityHumanNPC(MinecraftServer.getServer(), nmsWorld, profile, ClientInformation.createDefault(), npc); + handle.absSnapTo(at.getX(), at.getY(), at.getZ(), at.getYaw(), at.getPitch()); Skin skin = handle.getSkinTracker().getSkin(); if (skin != null) { skin.apply(handle); diff --git a/v26_1_R1/src/main/java/net/citizensnpcs/nms/v26_1_R1/util/NMSImpl.java b/v26_1_R1/src/main/java/net/citizensnpcs/nms/v26_1_R1/util/NMSImpl.java index b5fbf85d3..b77330880 100644 --- a/v26_1_R1/src/main/java/net/citizensnpcs/nms/v26_1_R1/util/NMSImpl.java +++ b/v26_1_R1/src/main/java/net/citizensnpcs/nms/v26_1_R1/util/NMSImpl.java @@ -428,9 +428,15 @@ public void activate(org.bukkit.entity.Entity entity) { @Override public void addEntityToWorld(org.bukkit.entity.Entity entity, SpawnReason custom, Consumer isAdded) { + boolean canvasPlayer = "Canvas".equals(Bukkit.getName()) && entity instanceof Player; Runnable task = () -> { Entity handle = getHandle(entity); ServerLevel level = (ServerLevel) handle.level(); + ServerPlayer player = canvasPlayer ? (ServerPlayer) handle : null; + Collection regionConnections = player == null ? null : getCanvasRegionConnections(level); + Connection connection = player == null ? null : getCanvasConnection(player); + boolean addedConnection = regionConnections != null && connection != null + && !regionConnections.contains(connection) && regionConnections.add(connection); int viewDistance = -1; ChunkMap chunkMap = null; if (entity instanceof Player) { @@ -438,9 +444,16 @@ public void addEntityToWorld(org.bukkit.entity.Entity entity, SpawnReason custom viewDistance = chunkMap.serverViewDistance; chunkMap.serverViewDistance = -1; } - boolean success = level.addFreshEntity(handle, custom); - if (chunkMap != null) { - chunkMap.serverViewDistance = viewDistance; + boolean success; + try { + success = level.addFreshEntity(handle, custom); + } finally { + if (addedConnection) { + regionConnections.remove(connection); + } + if (chunkMap != null) { + chunkMap.serverViewDistance = viewDistance; + } } isAdded.accept(success); }; @@ -453,6 +466,28 @@ public void addEntityToWorld(org.bukkit.entity.Entity entity, SpawnReason custom } } + private static Connection getCanvasConnection(ServerPlayer player) { + if (CANVAS_CONNECTION == null) + return null; + try { + return (Connection) CANVAS_CONNECTION.invoke(player.connection); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } + + private static Collection getCanvasRegionConnections(ServerLevel level) { + if (CANVAS_CURRENT_WORLD_DATA == null || CANVAS_REGION_CONNECTIONS == null) + return null; + try { + return (Collection) CANVAS_REGION_CONNECTIONS.invoke(CANVAS_CURRENT_WORLD_DATA.invoke(level)); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } + @Override public void addOrRemoveFromPlayerList(org.bukkit.entity.Entity entity, boolean remove) { if (entity == null) @@ -2941,6 +2976,13 @@ public void acceptEmpty(MemoryModuleType type) { private static final MethodHandle BRAIN_SENSORS_GETTER = NMS.getGetter(Brain.class, "sensors"); private static final MethodHandle BRAIN_SETTER = NMS.getFirstSetter(LivingEntity.class, Brain.class); private static final MethodHandle BUKKITENTITY_FIELD_SETTER = NMS.getSetter(Entity.class, "bukkitEntity"); + private static final MethodHandle CANVAS_CONNECTION = "Canvas".equals(Bukkit.getName()) + ? NMS.getGetter(ServerGamePacketListenerImpl.class.getSuperclass(), "connection", false) + : null; + private static final MethodHandle CANVAS_CURRENT_WORLD_DATA = "Canvas".equals(Bukkit.getName()) + ? NMS.getMethodHandle(Level.class, "getCurrentWorldData", false) + : null; + private static final MethodHandle CANVAS_REGION_CONNECTIONS = loadCanvasRegionConnectionsGetter(); private static final MethodHandle CHUNKMAP_UPDATE_PLAYER_STATUS = NMS.getMethodHandle(ChunkMap.class, "updatePlayerStatus", true, ServerPlayer.class, boolean.class); private static final MethodHandle CLIENT_LOADED_TIMEOUT_TIMER = NMS.getSetter(ServerGamePacketListenerImpl.class, @@ -3032,6 +3074,17 @@ public void acceptEmpty(MemoryModuleType type) { public static final MethodHandle WAITING_FOR_RESPAWN = NMS.getSetter(ServerGamePacketListenerImpl.class, "waitingForRespawn"); + private static MethodHandle loadCanvasRegionConnectionsGetter() { + if (!"Canvas".equals(Bukkit.getName())) + return null; + try { + return NMS.getGetter(Class.forName("io.papermc.paper.threadedregions.RegionizedWorldData"), "connections", + false); + } catch (ClassNotFoundException e) { + return null; + } + } + static { if (NMS.getMethodHandle(CraftBlock.class, "getNMS", false) != null) { GET_BLOCK_STATE = NMS.getMethodHandle(CraftBlock.class, "getNMS", false); diff --git a/v26_2_R1/src/main/java/net/citizensnpcs/nms/v26_2_R1/entity/HumanController.java b/v26_2_R1/src/main/java/net/citizensnpcs/nms/v26_2_R1/entity/HumanController.java index 002882d24..651768458 100644 --- a/v26_2_R1/src/main/java/net/citizensnpcs/nms/v26_2_R1/entity/HumanController.java +++ b/v26_2_R1/src/main/java/net/citizensnpcs/nms/v26_2_R1/entity/HumanController.java @@ -42,6 +42,7 @@ protected Entity createEntity(final Location at, final NPC npc) { final GameProfile profile = new GameProfile(uuid, name); final EntityHumanNPC handle = new EntityHumanNPC(MinecraftServer.getServer(), nmsWorld, profile, ClientInformation.createDefault(), npc); + handle.absSnapTo(at.getX(), at.getY(), at.getZ(), at.getYaw(), at.getPitch()); Skin skin = handle.getSkinTracker().getSkin(); if (skin != null) { skin.apply(handle);