From 367b689cc5c46fe89686d1a25630e60a70d47377 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 27 Nov 2025 16:08:01 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=BF=98=E5=8E=9F?= =?UTF-8?q?=E5=8E=9F=E7=89=88=E6=96=B9=E5=9D=97=E7=9B=AE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 9 +++++-- .../block/BukkitCustomBlockStateWrapper.java | 2 +- .../block/behavior/BukkitBlockBehavior.java | 2 +- .../block/behavior/CropBlockBehavior.java | 4 +-- .../behavior/FenceGateBlockBehavior.java | 2 +- .../block/behavior/GrassBlockBehavior.java | 4 +-- .../block/behavior/SaplingBlockBehavior.java | 4 +-- .../block/behavior/TrapDoorBlockBehavior.java | 2 +- .../bukkit/item/behavior/AxeItemBehavior.java | 2 +- .../item/behavior/BlockItemBehavior.java | 2 +- .../behavior/FlintAndSteelItemBehavior.java | 4 +-- .../item/listener/ItemEventListener.java | 6 ++--- .../feature/DebugCleanCacheCommand.java | 2 +- .../plugin/injector/WorldStorageInjector.java | 6 ++--- .../plugin/network/BukkitNetworkManager.java | 4 +-- .../protocol/VisualBlockStatePacket.java | 2 +- .../plugin/user/BukkitServerPlayer.java | 4 +-- .../bukkit/world/BukkitWorldManager.java | 10 +++++--- .../core/block/AbstractBlockManager.java | 6 ++--- .../craftengine/core/block/BlockKeys.java | 1 + .../core/block/ImmutableBlockState.java | 25 ++++++++++++++++--- 21 files changed, 65 insertions(+), 38 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index 10c77c589..ce09c5bff 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -223,7 +223,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { protected void applyPlatformSettings(ImmutableBlockState state) { DelegatingBlockState nmsState = (DelegatingBlockState) state.customBlockState().literalObject(); nmsState.setBlockState(state); - Object nmsVisualState = state.vanillaBlockState().literalObject(); + Object nmsVisualState = state.visualBlockState().literalObject(); BlockSettings settings = state.settings(); try { @@ -291,9 +291,14 @@ public final class BukkitBlockManager extends AbstractBlockManager { this.burnableBlocks.add(nmsBlock); } - Key vanillaBlockId = state.vanillaBlockState().ownerId(); + Key vanillaBlockId = state.visualBlockState().ownerId(); BlockGenerator.field$CraftEngineBlock$isNoteBlock().set(nmsBlock, vanillaBlockId.equals(BlockKeys.NOTE_BLOCK)); BlockGenerator.field$CraftEngineBlock$isTripwire().set(nmsBlock, vanillaBlockId.equals(BlockKeys.TRIPWIRE)); + if (vanillaBlockId.equals(BlockKeys.BARRIER)) { + state.setRestoreBlockState(createBlockState("minecraft:glass")); + } else { + state.setRestoreBlockState(state.visualBlockState()); + } } catch (ReflectiveOperationException e) { this.plugin.logger().warn("Failed to apply platform block settings for block state " + state, e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java index 53d9cbfb0..907fa26c6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlockStateWrapper.java @@ -20,7 +20,7 @@ public class BukkitCustomBlockStateWrapper extends AbstractBlockStateWrapper imp @Override public BlockStateWrapper visualBlockState() { - return getImmutableBlockState().map(ImmutableBlockState::vanillaBlockState).orElse(null); + return getImmutableBlockState().map(ImmutableBlockState::visualBlockState).orElse(null); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java index ea36861b8..9bc29f2da 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehavior.java @@ -180,7 +180,7 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior { public boolean isPathFindable(Object thisBlock, Object[] args, Callable superMethod) throws Exception { Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(args[0]); if (optionalCustomState.isEmpty()) return false; - BlockStateWrapper vanillaState = optionalCustomState.get().vanillaBlockState(); + BlockStateWrapper vanillaState = optionalCustomState.get().visualBlockState(); if (vanillaState == null) return false; return FastNMS.INSTANCE.method$BlockStateBase$isPathFindable(vanillaState.literalObject(), VersionHelper.isOrAbove1_20_5() ? null : args[1], VersionHelper.isOrAbove1_20_5() ? null : args[2], args[isPathFindable$type]); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java index 7ae23af7f..066a13178 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java @@ -135,7 +135,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior { if (isMaxAge(state)) return InteractionResult.PASS; boolean sendSwing = false; - Object visualState = state.vanillaBlockState().literalObject(); + Object visualState = state.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); @@ -158,7 +158,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior { } ImmutableBlockState customState = optionalCustomState.get(); boolean sendParticles = false; - Object visualState = customState.vanillaBlockState().literalObject(); + Object visualState = customState.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, pos, visualState); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java index 27ffdea31..697b84624 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java @@ -147,7 +147,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior implements IsPat Player player = context.getPlayer(); if (player == null) return; this.toggle(state, context.getLevel(), context.getClickedPos(), player); - if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item) context.getItem())) { + if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.visualBlockState().literalObject()), context.getHitResult(), (Item) context.getItem())) { player.swingHand(context.getHand()); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java index a53af5459..359ff4a9c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java @@ -60,7 +60,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior { } boolean sendParticles = false; ImmutableBlockState customState = optionalCustomState.get(); - Object visualState = customState.vanillaBlockState().literalObject(); + Object visualState = customState.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState); @@ -93,7 +93,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior { if (!block.isEmpty()) return InteractionResult.PASS; boolean sendSwing = false; - Object visualState = state.vanillaBlockState().literalObject(); + Object visualState = state.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java index 9f13df44c..3d53ce579 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java @@ -115,7 +115,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior { } ImmutableBlockState customState = optionalCustomState.get(); boolean sendParticles = false; - Object visualState = customState.vanillaBlockState().literalObject(); + Object visualState = customState.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState); @@ -153,7 +153,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior { if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || player == null || player.isAdventureMode()) return InteractionResult.PASS; boolean sendSwing = false; - Object visualState = state.vanillaBlockState().literalObject(); + Object visualState = state.visualBlockState().literalObject(); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java index c297d3289..4339bf882 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/TrapDoorBlockBehavior.java @@ -120,7 +120,7 @@ public class TrapDoorBlockBehavior extends BukkitBlockBehavior implements IsPath Player player = context.getPlayer(); if (player == null) return; this.toggle(state, context.getLevel(), context.getClickedPos(), player); - if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item) context.getItem())) { + if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.visualBlockState().literalObject()), context.getHitResult(), (Item) context.getItem())) { player.swingHand(context.getHand()); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java index 699fb7975..619567244 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java @@ -101,7 +101,7 @@ public class AxeItemBehavior extends ItemBehavior { // resend swing if it's not interactable on client side if (!InteractUtils.isInteractable( - bukkitPlayer, BlockStateUtils.fromBlockData(customState.vanillaBlockState().literalObject()), + bukkitPlayer, BlockStateUtils.fromBlockData(customState.visualBlockState().literalObject()), context.getHitResult(), item ) || player.isSecondaryUseActive()) { player.swingHand(context.getHand()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java index add1679d3..6d0e7871d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java @@ -112,7 +112,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior { } else { ImmutableBlockState customState = optionalCustomState.get(); // custom block - if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.vanillaBlockState().literalObject() : againstBlockState)) { + if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.visualBlockState().literalObject() : againstBlockState)) { return InteractionResult.FAIL; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java index b734543f3..70ac737e6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FlintAndSteelItemBehavior.java @@ -77,10 +77,10 @@ public class FlintAndSteelItemBehavior extends ItemBehavior { // 点击对象为自定义方块 ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); // 原版外观也可燃 - if (BlockStateUtils.isBurnable(immutableBlockState.vanillaBlockState().literalObject())) { + if (BlockStateUtils.isBurnable(immutableBlockState.visualBlockState().literalObject())) { return InteractionResult.PASS; } - BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()); + BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject()); // 点击的是方块上面,则只需要判断shift和可交互 if (direction == Direction.UP) { // 客户端层面必须可交互 diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java index 3c61cdf90..88f5173de 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java @@ -167,7 +167,7 @@ public class ItemEventListener implements Listener { // fix client side issues if (action.isRightClick() && hitResult != null && - InteractUtils.canPlaceVisualBlock(player, BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()), hitResult, itemInHand)) { + InteractUtils.canPlaceVisualBlock(player, BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject()), hitResult, itemInHand)) { player.updateInventory(); } @@ -272,13 +272,13 @@ public class ItemEventListener implements Listener { if (immutableBlockState != null) { // client won't have sounds if the clientside block is interactable // so we should check and resend sounds on BlockPlaceEvent - BlockData craftBlockData = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()); + BlockData craftBlockData = BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject()); if (InteractUtils.isInteractable(player, craftBlockData, hitResult, itemInHand)) { if (!serverPlayer.isSecondaryUseActive()) { serverPlayer.setResendSound(); } } else { - if (BlockStateUtils.isReplaceable(immutableBlockState.customBlockState().literalObject()) && !BlockStateUtils.isReplaceable(immutableBlockState.vanillaBlockState().literalObject())) { + if (BlockStateUtils.isReplaceable(immutableBlockState.customBlockState().literalObject()) && !BlockStateUtils.isReplaceable(immutableBlockState.visualBlockState().literalObject())) { serverPlayer.setResendSwing(); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java index 126e06e3e..910ecf5c2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java @@ -78,7 +78,7 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature Set ids = new HashSet<>(); for (CustomBlock customBlock : instance.loadedBlocks().values()) { for (ImmutableBlockState state : customBlock.variantProvider().states()) { - ids.add(state.vanillaBlockState()); + ids.add(state.visualBlockState()); } } VisualBlockStateAllocator visualBlockStateAllocator = instance.blockParser().visualBlockStateAllocator(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java index 0d90bceef..ed69ec3a4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/WorldStorageInjector.java @@ -283,10 +283,10 @@ public final class WorldStorageInjector { if (Config.enableLightSystem()) { if (previousImmutableBlockState.isEmpty()) { // 原版块到自定义块,只需要判断新块是否和客户端视觉一致 - updateLight(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, x, y, z); + updateLight(holder, newImmutableBlockState.visualBlockState().literalObject(), newState, x, y, z); } else { // 自定义块到自定义块 - updateLight$complex(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, previousState, x, y, z); + updateLight$complex(holder, newImmutableBlockState.visualBlockState().literalObject(), newState, previousState, x, y, z); } } } else { @@ -311,7 +311,7 @@ public final class WorldStorageInjector { } if (Config.enableLightSystem()) { // 自定义块到原版块,只需要判断旧块是否和客户端一直 - BlockStateWrapper wrapper = previous.vanillaBlockState(); + BlockStateWrapper wrapper = previous.visualBlockState(); if (wrapper != null) { updateLight(holder, wrapper.literalObject(), previousState, x, y, z); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index ba62c7b36..563a5a6e4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -1096,7 +1096,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes if (player.isAdventureMode()) { if (Config.simplifyAdventureBreakCheck()) { ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); - if (!player.canBreak(pos, state.vanillaBlockState().literalObject())) { + if (!player.canBreak(pos, state.visualBlockState().literalObject())) { player.preventMiningBlock(); return; } @@ -1358,7 +1358,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes Key itemId = state.settings().itemId(); // no item available if (itemId == null) return; - Object vanillaBlock = FastNMS.INSTANCE.method$BlockState$getBlock(state.vanillaBlockState().literalObject()); + Object vanillaBlock = FastNMS.INSTANCE.method$BlockState$getBlock(state.visualBlockState().literalObject()); Object vanillaBlockItem = FastNMS.INSTANCE.method$Block$asItem(vanillaBlock); if (vanillaBlockItem == null) return; Key addItemId = KeyUtils.namespacedKey2Key(item.getType().getKey()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/VisualBlockStatePacket.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/VisualBlockStatePacket.java index bed116f3e..19d48a0bf 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/VisualBlockStatePacket.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/protocol/VisualBlockStatePacket.java @@ -103,7 +103,7 @@ public record VisualBlockStatePacket(int[] data) implements ModPacket { for (int i = 0; i < serverSideBlockCount; i++) { ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(i + vanillaBlockStateCount); if (state.isEmpty()) continue; - mappings[state.customBlockState().registryId() - vanillaBlockStateCount] = state.vanillaBlockState().registryId(); + mappings[state.customBlockState().registryId() - vanillaBlockStateCount] = state.visualBlockState().registryId(); } return new VisualBlockStatePacket(mappings); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 60ed0d9e8..d6bb5a6f8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -646,7 +646,7 @@ public class BukkitServerPlayer extends Player { // instant break boolean custom = immutableBlockState != null; if (custom && getDestroyProgress(state, pos) >= 1f) { - BlockStateWrapper vanillaBlockState = immutableBlockState.vanillaBlockState(); + BlockStateWrapper vanillaBlockState = immutableBlockState.visualBlockState(); // if it's not an instant break on client side, we should resend level event if (vanillaBlockState != null && getDestroyProgress(vanillaBlockState.literalObject(), pos) < 1f) { Object levelEventPacket = FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket( @@ -811,7 +811,7 @@ public class BukkitServerPlayer extends Player { // for simplified adventure break, switch mayBuild temporarily if (isAdventureMode() && Config.simplifyAdventureBreakCheck()) { // check the appearance state - if (canBreak(hitPos, customState.vanillaBlockState().literalObject())) { + if (canBreak(hitPos, customState.visualBlockState().literalObject())) { // Error might occur so we use try here try { FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer, true); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java index 082ae0d6c..c54cb0d55 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java @@ -6,6 +6,7 @@ import net.momirealms.craftengine.bukkit.plugin.injector.WorldStorageInjector; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; @@ -296,9 +297,12 @@ public class BukkitWorldManager implements WorldManager, Listener { for (int z = 0; z < 16; z++) { for (int y = 0; y < 16; y++) { ImmutableBlockState customState = ceSection.getBlockState(x, y, z); - if (!customState.isEmpty() && customState.vanillaBlockState() != null) { - FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.vanillaBlockState().literalObject(), false); - unsaved = true; + if (!customState.isEmpty()) { + BlockStateWrapper wrapper = customState.restoreBlockState(); + if (wrapper != null) { + FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, wrapper.literalObject(), false); + unsaved = true; + } } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 66abb09a2..748d27ea8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -631,7 +631,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem continue; } for (ImmutableBlockState possibleState : possibleStates) { - possibleState.setVanillaBlockState(appearance.blockState()); + possibleState.setVisualBlockState(appearance.blockState()); appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); } } @@ -650,11 +650,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } state.setBehavior(blockBehavior); int internalId = state.customBlockState().registryId(); - BlockStateWrapper visualState = state.vanillaBlockState(); + BlockStateWrapper visualState = state.visualBlockState(); // 校验,为未绑定外观的强行添加外观 if (visualState == null) { visualState = anyAppearance.blockState(); - state.setVanillaBlockState(visualState); + state.setVisualBlockState(visualState); anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); } int appearanceId = visualState.registryId(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java index 7f308db32..59ea7c2a0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java @@ -49,6 +49,7 @@ public final class BlockKeys { public static final Key TWISTING_VINES = Key.of("minecraft:twisting_vines"); public static final Key KELP = Key.of("minecraft:kelp"); public static final Key CHORUS_PLANT = Key.of("minecraft:chorus_plant"); + public static final Key BARRIER = Key.of("minecraft:barrier"); public static final Key CHEST = Key.of("minecraft:chest"); public static final Key BARREL = Key.of("minecraft:barrel"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 2c837d796..8e4430e2e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -20,6 +20,7 @@ import net.momirealms.craftengine.core.world.World; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.NBT; import net.momirealms.sparrow.nbt.Tag; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,7 +34,9 @@ public final class ImmutableBlockState { private CompoundTag tag; private BlockStateWrapper customBlockState; - private BlockStateWrapper vanillaBlockState; + private BlockStateWrapper visualBlockState; + // 安全的,在卸载ce后还原的方块 + private BlockStateWrapper restoreBlockState; private BlockBehavior behavior; private BlockSettings settings; private BlockEntityType blockEntityType; @@ -96,16 +99,30 @@ public final class ImmutableBlockState { return this.customBlockState; } + @Deprecated public BlockStateWrapper vanillaBlockState() { - return this.vanillaBlockState; + return this.visualBlockState; + } + + public BlockStateWrapper visualBlockState() { + return this.visualBlockState; + } + + @ApiStatus.Internal + public BlockStateWrapper restoreBlockState() { + return this.restoreBlockState; } public void setCustomBlockState(@NotNull BlockStateWrapper customBlockState) { this.customBlockState = customBlockState; } - public void setVanillaBlockState(@NotNull BlockStateWrapper vanillaBlockState) { - this.vanillaBlockState = vanillaBlockState; + public void setVisualBlockState(@NotNull BlockStateWrapper visualBlockState) { + this.visualBlockState = visualBlockState; + } + + public void setRestoreBlockState(BlockStateWrapper restoreBlockState) { + this.restoreBlockState = restoreBlockState; } public CompoundTag getNbtToSave() { From c691f6a06466ed1072f9bb3a9706d52e6944b3f7 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 27 Nov 2025 16:13:39 +0800 Subject: [PATCH 2/9] =?UTF-8?q?=E5=BA=94=E9=81=BF=E5=85=8D=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E8=8E=B7=E5=8F=96property?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/behavior/SeatBlockBehavior.java | 2 +- .../craftengine/bukkit/block/entity/SeatBlockEntity.java | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java index c54f6eb66..e1c68747b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java @@ -33,7 +33,7 @@ public class SeatBlockBehavior extends BukkitBlockBehavior implements EntityBloc @Override public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) { - return new SeatBlockEntity(pos, state, this.seats); + return new SeatBlockEntity(pos, state, this.seats, this.directionProperty); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java index 421e8f94e..5ff7a8812 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java @@ -15,10 +15,12 @@ import net.momirealms.sparrow.nbt.CompoundTag; public class SeatBlockEntity extends BlockEntity implements SeatOwner { private final Seat[] seats; + private final Property facing; @SuppressWarnings("unchecked") - public SeatBlockEntity(BlockPos pos, ImmutableBlockState blockState, SeatConfig[] seats) { + public SeatBlockEntity(BlockPos pos, ImmutableBlockState blockState, SeatConfig[] seats, Property directionProperty) { super(BukkitBlockEntityTypes.SEAT, pos, blockState); + this.facing = directionProperty; this.seats = new Seat[seats.length]; for (int i = 0; i < seats.length; i++) { this.seats[i] = new BukkitSeat<>(this, seats[i]); @@ -38,10 +40,9 @@ public class SeatBlockEntity extends BlockEntity implements SeatOwner { } public boolean spawnSeat(Player player) { - Property facing = super.blockState.owner().value().getProperty("facing"); int yRot = 0; - if (facing != null && facing.valueClass() == HorizontalDirection.class) { - HorizontalDirection direction = (HorizontalDirection) super.blockState.get(facing); + if (this.facing != null) { + HorizontalDirection direction = super.blockState.get(facing); yRot = switch (direction) { case NORTH -> 0; case SOUTH -> 180; From af989b4099aafb92165fc5857a697783a0b06082 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 27 Nov 2025 17:05:14 +0800 Subject: [PATCH 3/9] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E7=AA=92=E6=81=AF?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E9=BB=98=E8=AE=A4=E5=80=BC=EF=BC=8C=E4=BB=8E?= =?UTF-8?q?=E8=80=8C=E9=97=B4=E6=8E=A5=E4=BF=AE=E5=A4=8Dview=20blocking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 30 ++++++++++++++++++- .../reflection/minecraft/CoreReflections.java | 22 ++++++++++++++ .../craftengine/bukkit/world/BukkitWorld.java | 5 +--- .../core/block/AbstractBlockManager.java | 7 +++++ .../core/block/BlockRegistryMirror.java | 4 +++ 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index ce09c5bff..c250782e5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -42,6 +42,7 @@ import java.util.*; public final class BukkitBlockManager extends AbstractBlockManager { public static final Set CLIENT_SIDE_NOTE_BLOCKS = new HashSet<>(2048, 0.6f); + private static final Object BLOCK_POS$ZERO = LocationUtils.toBlockPos(0,0,0); private static final Object ALWAYS_FALSE = FastNMS.INSTANCE.method$StatePredicate$always(false); private static final Object ALWAYS_TRUE = FastNMS.INSTANCE.method$StatePredicate$always(true); private static BukkitBlockManager instance; @@ -80,10 +81,12 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public void init() { + super.init(); this.initMirrorRegistry(); this.initFireBlock(); this.deceiveBukkitRegistry(); this.markVanillaNoteBlocks(); + this.findViewBlockingVanillaBlocks(); Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState()); this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 一定要预先初始化一次,预防id超出上限 } @@ -240,8 +243,15 @@ public final class BukkitBlockManager extends AbstractBlockManager { boolean useShapeForLightOcclusion = settings.useShapeForLightOcclusion() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.getBoolean(nmsVisualState) : settings.useShapeForLightOcclusion().asBoolean(); CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.set(nmsState, useShapeForLightOcclusion); CoreReflections.field$BlockStateBase$isRedstoneConductor.set(nmsState, settings.isRedstoneConductor().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE); + + boolean suffocating = settings.isSuffocating() == Tristate.UNDEFINED ? (canBlockView(state.visualBlockState())) : (settings.isSuffocating().asBoolean()); + CoreReflections.field$BlockStateBase$isSuffocating.set(nmsState, suffocating ? ALWAYS_TRUE : ALWAYS_FALSE); CoreReflections.field$BlockStateBase$isSuffocating.set(nmsState, settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE); - CoreReflections.field$BlockStateBase$isViewBlocking.set(nmsState, settings.isViewBlocking() == Tristate.UNDEFINED ? settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE : (settings.isViewBlocking().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE)); + CoreReflections.field$BlockStateBase$isViewBlocking.set(nmsState, + settings.isViewBlocking() == Tristate.UNDEFINED ? + (suffocating ? ALWAYS_TRUE : ALWAYS_FALSE) : + (settings.isViewBlocking().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE) + ); DelegatingBlock nmsBlock = (DelegatingBlock) BlockStateUtils.getBlockOwner(nmsState); ObjectHolder shapeHolder = nmsBlock.shapeDelegate(); @@ -299,6 +309,8 @@ public final class BukkitBlockManager extends AbstractBlockManager { } else { state.setRestoreBlockState(state.visualBlockState()); } + // 根据客户端的状态决定其是否阻挡视线 + super.viewBlockingBlocks[state.customBlockState().registryId()] = canBlockView(state.visualBlockState()); } catch (ReflectiveOperationException e) { this.plugin.logger().warn("Failed to apply platform block settings for block state " + state, e); } @@ -384,6 +396,22 @@ public final class BukkitBlockManager extends AbstractBlockManager { } } + public boolean canBlockView(BlockStateWrapper blockState) { + if (!BlockStateUtils.isOcclude(blockState)) { + return false; + } + return FastNMS.INSTANCE.method$BlockStateBase$isCollisionShapeFullBlock(blockState, CoreReflections.instance$EmptyBlockGetter$INSTANCE, BLOCK_POS$ZERO); + } + + private void findViewBlockingVanillaBlocks() { + for (int i = 0; i < this.vanillaBlockStateCount; i++) { + BlockStateWrapper blockState = BlockRegistryMirror.byId(i); + if (canBlockView(blockState)) { + this.viewBlockingBlocks[i] = true; + } + } + } + @Override protected void setVanillaBlockTags(Key id, List tags) { Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id)); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 4bef1277b..f92d2c2cd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -4572,4 +4572,26 @@ public final class CoreReflections { "world.level.levelgen.feature.Feature" ) ); + + public static final Class clazz$EmptyBlockGetter = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "world.level.BlockAccessAir", + "world.level.EmptyBlockGetter" + ) + ); + + public static final Method method$EmptyBlockGetter$values = requireNonNull( + ReflectionUtils.getStaticMethod(clazz$EmptyBlockGetter, clazz$EmptyBlockGetter.arrayType()) + ); + + public static final Object instance$EmptyBlockGetter$INSTANCE; + + static { + try { + Object[] values = (Object[]) method$EmptyBlockGetter$values.invoke(null); + instance$EmptyBlockGetter$INSTANCE = values[0]; + } catch (ReflectiveOperationException e) { + throw new ReflectionInitException("Failed to init EmptyBlockGetter$INSTANCE", e); + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java index 66df48ee3..91d168666 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java @@ -24,10 +24,7 @@ import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; +import java.util.*; public class BukkitWorld implements World { private final WeakReference world; diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 748d27ea8..c0f427de8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -85,6 +85,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 临时存储哪些视觉方块被使用了 protected final Set tempVisualBlockStatesInUse = new HashSet<>(); protected final Set tempVisualBlocksInUse = new HashSet<>(); + // 能遮挡视线的方块 + protected final boolean[] viewBlockingBlocks; // 声音映射表,和使用了哪些视觉方块有关 protected Map soundReplacements = Map.of(); // 是否使用了透明方块模型 @@ -102,6 +104,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.tempVanillaBlockStateModels = new JsonElement[vanillaBlockStateCount]; this.blockParser = new BlockParser(this.autoVisualBlockStateCandidates); this.blockStateMappingParser = new BlockStateMappingParser(); + this.viewBlockingBlocks = new boolean[vanillaBlockStateCount + customBlockCount]; Arrays.fill(this.blockStateMappings, -1); } @@ -232,6 +235,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem public abstract BlockBehavior createBlockBehavior(CustomBlock customBlock, List> behaviorConfig); + public boolean isViewBlockingBlock(int stateId) { + return this.viewBlockingBlocks[stateId]; + } + protected abstract void updateTags(); protected abstract boolean isVanillaBlock(Key id); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java index ccc19d42e..0011324f1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java @@ -22,4 +22,8 @@ public final class BlockRegistryMirror { public static BlockStateWrapper stoneState() { return stoneState; } + + public static BlockStateWrapper[] blockStates() { + return blockStates; + } } From 9bce13475f6361da20c595459c12cd170f177c23 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 27 Nov 2025 17:07:46 +0800 Subject: [PATCH 4/9] Update BukkitBlockManager.java --- .../craftengine/bukkit/block/BukkitBlockManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index c250782e5..30237a4c2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -396,7 +396,8 @@ public final class BukkitBlockManager extends AbstractBlockManager { } } - public boolean canBlockView(BlockStateWrapper blockState) { + public boolean canBlockView(BlockStateWrapper wrapper) { + Object blockState = wrapper.literalObject(); if (!BlockStateUtils.isOcclude(blockState)) { return false; } From bced7bfda6bcbcb32fb68a3ca7560f49403ef96e Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 28 Nov 2025 02:57:59 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E5=AE=9E=E4=BD=93=E5=89=94=E9=99=A4?= =?UTF-8?q?=E5=89=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 4 +- .../plugin/network/BukkitNetworkManager.java | 97 ++++++++++++++++--- .../plugin/user/BukkitServerPlayer.java | 53 ++++++---- .../craftengine/bukkit/world/BukkitWorld.java | 5 +- common-files/src/main/resources/config.yml | 9 +- .../src/main/resources/translations/en.yml | 1 + .../core/block/AbstractBlockManager.java | 8 +- .../core/block/BlockStateAppearance.java | 5 +- .../core/block/ImmutableBlockState.java | 10 ++ .../render/ConstantBlockEntityRenderer.java | 13 ++- .../core/entity/player/Player.java | 16 ++- .../craftengine/core/plugin/CraftEngine.java | 1 - .../core/plugin/config/Config.java | 9 ++ .../config/template/TemplateManager.java | 7 -- .../argument/ExpressionTemplateArgument.java | 1 - .../core/plugin/network/NetWorkUser.java | 11 +-- .../core/util/ResourceConfigUtils.java | 49 ++++++++++ .../craftengine/core/world/Cullable.java | 13 +++ .../craftengine/core/world/Vec3i.java | 21 +--- .../core/world/chunk/ArrayPalette.java | 13 ++- .../core/world/chunk/BiMapPalette.java | 12 ++- .../craftengine/core/world/chunk/CEChunk.java | 21 ++-- .../core/world/chunk/ChunkStatus.java | 7 -- .../core/world/chunk/IdListPalette.java | 5 + .../craftengine/core/world/chunk/Palette.java | 2 + .../core/world/chunk/SingularPalette.java | 9 ++ .../core/world/chunk/client/ClientChunk.java | 33 +++++++ .../world/chunk/client/ClientSection.java | 34 +++++++ .../client/ClientSectionOcclusionStorage.java | 6 ++ .../chunk/client/PackedOcclusionStorage.java | 37 +++++++ .../client/SingularOcclusionStorage.java | 14 +++ .../chunk/client/VirtualCullableObject.java | 31 ++++++ .../serialization/DefaultChunkSerializer.java | 2 +- .../core/world/collision/AABB.java | 11 +++ 34 files changed, 470 insertions(+), 100 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSectionOcclusionStorage.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/PackedOcclusionStorage.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/SingularOcclusionStorage.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index 30237a4c2..bb379b71f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -88,7 +88,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { this.markVanillaNoteBlocks(); this.findViewBlockingVanillaBlocks(); Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState()); - this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 一定要预先初始化一次,预防id超出上限 + this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 一定要预先初始化一次,预防id超出上限 } public static BukkitBlockManager instance() { @@ -128,7 +128,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public void delayedLoad() { - this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 重置方块映射表 + this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 重置方块映射表 super.delayedLoad(); this.cachedVisualBlockStatePacket = VisualBlockStatePacket.create(); for (BukkitServerPlayer player : BukkitNetworkManager.instance().onlineUsers()) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 563a5a6e4..0d962ccb9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -22,6 +22,7 @@ import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.DataComponentValue; import net.kyori.adventure.text.event.HoverEvent; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptBreakEvent; @@ -90,9 +91,12 @@ import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.chunk.CEChunk; -import net.momirealms.craftengine.core.world.chunk.ChunkStatus; import net.momirealms.craftengine.core.world.chunk.Palette; import net.momirealms.craftengine.core.world.chunk.PalettedContainer; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; +import net.momirealms.craftengine.core.world.chunk.client.ClientSection; +import net.momirealms.craftengine.core.world.chunk.client.PackedOcclusionStorage; +import net.momirealms.craftengine.core.world.chunk.client.SingularOcclusionStorage; import net.momirealms.craftengine.core.world.chunk.packet.BlockEntityData; import net.momirealms.craftengine.core.world.chunk.packet.MCSection; import net.momirealms.sparrow.nbt.CompoundTag; @@ -125,6 +129,7 @@ import java.time.Instant; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; +import java.util.function.Predicate; public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener { private static BukkitNetworkManager instance; @@ -288,7 +293,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } } - public void registerBlockStatePacketListeners(int[] blockStateMappings) { + public void registerBlockStatePacketListeners(int[] blockStateMappings, Predicate occlusionPredicate) { int stoneId = BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState); int vanillaBlocks = BlockStateUtils.vanillaBlockStateCount(); int[] newMappings = new int[blockStateMappings.length]; @@ -318,7 +323,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes newMappings, newMappingsMOD, newMappings.length, - RegistryUtils.currentBiomeRegistrySize() + RegistryUtils.currentBiomeRegistrySize(), + occlusionPredicate ), this.packetIds.clientboundLevelChunkWithLightPacket(), "ClientboundLevelChunkWithLightPacket"); registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket"); registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket"); @@ -1433,9 +1439,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); if (world != null) { - int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; - player.setClientSideSectionCount(sectionCount); - player.setClientSideDimension(Key.of(location.toString())); + player.setClientSideWorld(BukkitAdaptors.adapt(world)); } else { CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket: World " + location + " does not exist"); } @@ -1463,10 +1467,9 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); if (world != null) { - int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; - player.setClientSideSectionCount(sectionCount); - player.setClientSideDimension(Key.of(location.toString())); + player.setClientSideWorld(BukkitAdaptors.adapt(world)); player.clearTrackedChunks(); + player.clearTrackedBlockEntities(); } else { CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist"); } @@ -1979,13 +1982,15 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes private final IntIdentityList biomeList; private final IntIdentityList blockList; private final boolean needsDowngrade; + private final Predicate occlusionPredicate; - public LevelChunkWithLightListener(int[] blockStateMapper, int[] modBlockStateMapper, int blockRegistrySize, int biomeRegistrySize) { + public LevelChunkWithLightListener(int[] blockStateMapper, int[] modBlockStateMapper, int blockRegistrySize, int biomeRegistrySize, Predicate occlusionPredicate) { this.blockStateMapper = blockStateMapper; this.modBlockStateMapper = modBlockStateMapper; this.biomeList = new IntIdentityList(biomeRegistrySize); this.blockList = new IntIdentityList(blockRegistrySize); this.needsDowngrade = MiscUtils.ceilLog2(BlockStateUtils.vanillaBlockStateCount()) != MiscUtils.ceilLog2(blockRegistrySize); + this.occlusionPredicate = occlusionPredicate; } public int remapBlockState(int stateId, boolean enableMod) { @@ -2022,36 +2027,92 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes buf.readBytes(chunkDataBytes); // 客户端侧section数量很重要,不能读取此时玩家所在的真实世界,包具有滞后性 - int count = player.clientSideSectionCount(); + net.momirealms.craftengine.core.world.World clientSideWorld = player.clientSideWorld(); + WorldHeight worldHeight = clientSideWorld.worldHeight(); + int count = worldHeight.getSectionsCount(); MCSection[] sections = new MCSection[count]; FriendlyByteBuf chunkDataByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(chunkDataBytes)); boolean hasChangedAnyBlock = false; boolean hasGlobalPalette = false; + // 创建客户端侧世界(只在开启实体情况下创建) + ClientSection[] clientSections = Config.enableEntityCulling() ? new ClientSection[count] : null; + for (int i = 0; i < count; i++) { MCSection mcSection = new MCSection(user.clientBlockList(), this.blockList, this.biomeList); mcSection.readPacket(chunkDataByteBuf); + PalettedContainer container = mcSection.blockStateContainer(); + // 重定向生物群系 if (remapBiomes(user, mcSection.biomeContainer())) { hasChangedAnyBlock = true; } + Palette palette = container.data().palette(); if (palette.canRemap()) { + + // 重定向方块 if (palette.remapAndCheck(s -> remapBlockState(s, user.clientModEnabled()))) { hasChangedAnyBlock = true; } + + // 处理客户端侧哪些方块有阻挡 + if (clientSections != null) { + int size = palette.getSize(); + // 单个元素的情况下,使用优化的存储方案 + if (size == 1) { + clientSections[i] = new ClientSection(new SingularOcclusionStorage(this.occlusionPredicate.test(palette.get(0)))); + } else { + boolean hasOcclusions = false; + boolean hasNoOcclusions = false; + for (int h = 0; h < size; h++) { + int entry = palette.get(h); + if (this.occlusionPredicate.test(entry)) { + hasOcclusions = true; + } else { + hasNoOcclusions = true; + } + } + // 两种情况都有,那么需要一个个遍历处理视线遮挡数据 + if (hasOcclusions && hasNoOcclusions) { + PackedOcclusionStorage storage = new PackedOcclusionStorage(false); + for (int j = 0; j < 4096; j++) { + int state = container.get(j); + storage.set(j, this.occlusionPredicate.test(state)); + } + } + // 全遮蔽或全透视则使用优化存储方案 + else { + clientSections[i] = new ClientSection(new SingularOcclusionStorage(hasOcclusions)); + } + } + } } else { hasGlobalPalette = true; + + PackedOcclusionStorage storage = null; + if (clientSections != null) { + storage = new PackedOcclusionStorage(false); + } + for (int j = 0; j < 4096; j++) { int state = container.get(j); + + // 重定向方块 int newState = remapBlockState(state, user.clientModEnabled()); if (newState != state) { container.set(j, newState); hasChangedAnyBlock = true; } + + // 写入视线遮挡数据 + if (storage != null) { + storage.set(j, this.occlusionPredicate.test(state)); + } } } + sections[i] = mcSection; } @@ -2116,13 +2177,17 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } // 记录加载的区块 - player.addTrackedChunk(chunkPos.longKey, new ChunkStatus()); + player.addTrackedChunk(chunkPos.longKey, new ClientChunk(clientSections, worldHeight)); // 生成方块实体 - CEWorld ceWorld = BukkitWorldManager.instance().getWorld(player.world().uuid()); - CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey); - if (ceChunk != null) { - ceChunk.spawnBlockEntities(player); + CEWorld ceWorld = clientSideWorld.storageWorld(); + // 世界可能被卸载,因为包滞后 + if (ceWorld != null) { + CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey); + if (ceChunk != null) { + // 生成方块实体 + ceChunk.spawnBlockEntities(player); + } } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index d6bb5a6f8..842be8e1d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -23,6 +23,7 @@ import net.momirealms.craftengine.core.advancement.AdvancementType; import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.entity.data.EntityData; import net.momirealms.craftengine.core.entity.player.GameMode; import net.momirealms.craftengine.core.entity.player.InteractionHand; @@ -39,7 +40,8 @@ import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.World; -import net.momirealms.craftengine.core.world.chunk.ChunkStatus; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; +import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject; import net.momirealms.craftengine.core.world.collision.AABB; import org.bukkit.*; import org.bukkit.attribute.Attribute; @@ -85,8 +87,7 @@ public class BukkitServerPlayer extends Player { private Reference playerRef; private Reference serverPlayerRef; // client side dimension info - private int sectionCount; - private Key clientSideDimension; + private World clientSideWorld; // check main hand/offhand interaction private int lastSuccessfulInteraction; // to prevent duplicated events @@ -124,7 +125,7 @@ public class BukkitServerPlayer extends Player { // cooldown data private CooldownData cooldownData; // tracked chunks - private ConcurrentLong2ReferenceChainedHashTable trackedChunks; + private ConcurrentLong2ReferenceChainedHashTable trackedChunks; // entity view private Map entityTypeView; // 通过指令或api设定的语言 @@ -138,6 +139,8 @@ public class BukkitServerPlayer extends Player { private boolean isHackedBreak; // 上一次停止挖掘包发出的时间 private int lastStopMiningTick; + // 跟踪到的方块实体渲染器 + private final Map trackedBlockEntityRenderers = new ConcurrentHashMap<>(); public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { this.channel = channel; @@ -477,21 +480,13 @@ public class BukkitServerPlayer extends Player { } @Override - public int clientSideSectionCount() { - return sectionCount; - } - - public void setClientSideSectionCount(int sectionCount) { - this.sectionCount = sectionCount; + public World clientSideWorld() { + return this.clientSideWorld; } @Override - public Key clientSideDimension() { - return clientSideDimension; - } - - public void setClientSideDimension(Key clientSideDimension) { - this.clientSideDimension = clientSideDimension; + public void setClientSideWorld(World world) { + this.clientSideWorld = world; } public void setConnectionState(ConnectionState connectionState) { @@ -1180,12 +1175,12 @@ public class BukkitServerPlayer extends Player { } @Override - public ChunkStatus getTrackedChunk(long chunkPos) { + public ClientChunk getTrackedChunk(long chunkPos) { return this.trackedChunks.get(chunkPos); } @Override - public void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus) { + public void addTrackedChunk(long chunkPos, ClientChunk chunkStatus) { this.trackedChunks.put(chunkPos, chunkStatus); } @@ -1300,4 +1295,26 @@ public class BukkitServerPlayer extends Player { public void sendTotemAnimation(Item totem, @Nullable SoundData sound, boolean silent) { PlayerUtils.sendTotemAnimation(this, totem, sound, silent); } + + @Override + public void addTrackedBlockEntities(Map renders) { + for (Map.Entry entry : renders.entrySet()) { + this.trackedBlockEntityRenderers.put(entry.getKey(), new VirtualCullableObject(entry.getValue())); + } + } + + @Override + public void removeTrackedBlockEntities(Collection renders) { + for (BlockPos render : renders) { + VirtualCullableObject remove = this.trackedBlockEntityRenderers.remove(render); + if (remove != null && remove.isShown()) { + remove.cullable().hide(this); + } + } + } + + @Override + public void clearTrackedBlockEntities() { + this.trackedBlockEntityRenderers.clear(); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java index 91d168666..66df48ee3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java @@ -24,7 +24,10 @@ import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; public class BukkitWorld implements World { private final WeakReference world; diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index c5c00829e..0d6efafb4 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -552,11 +552,10 @@ chunk-system: remove: [] convert: {} -#client-optimization: -# # Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure. -# entity-culling: -# enable: false -# whitelist-entities: [] +client-optimization: + # Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure. + entity-culling: + enable: false # Enables or disables debug mode debug: diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index b3ce311aa..cd0751c7b 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -87,6 +87,7 @@ warning.config.type.quaternionf: "Issue found in file - Failed t warning.config.type.vector3f: "Issue found in file - Failed to load '': Cannot cast '' to Vector3f type for option ''." warning.config.type.vec3d: "Issue found in file - Failed to load '': Cannot cast '' to Vec3d type for option ''." warning.config.type.map: "Issue found in file - Failed to load '': Cannot cast '' to Map type for option ''." +warning.config.type.aabb: "Issue found in file - Failed to load '': Cannot cast '' to AABB type for option ''." warning.config.type.snbt.invalid_syntax: "Issue found in file - Failed to load '': Invalid snbt syntax ''." warning.config.number.missing_type: "Issue found in file - The config '' is missing the required 'type' argument for number argument." warning.config.number.invalid_type: "Issue found in file - The config '' is using an invalid number argument type ''." diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index c0f427de8..1b0b09185 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -600,7 +600,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.arrangeModelForStateAndVerify(visualBlockState, parseBlockModel(modelConfig)); } } - BlockStateAppearance blockStateAppearance = new BlockStateAppearance(visualBlockState, parseBlockEntityRender(appearanceSection.get("entity-renderer"))); + BlockStateAppearance blockStateAppearance = new BlockStateAppearance( + visualBlockState, + parseBlockEntityRender(appearanceSection.get("entity-renderer")), + ResourceConfigUtils.getAsAABB(appearanceSection.getOrDefault("aabb", 1), "aabb") + ); appearances.put(appearanceName, blockStateAppearance); if (anyAppearance == null) { anyAppearance = blockStateAppearance; @@ -639,6 +643,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } for (ImmutableBlockState possibleState : possibleStates) { possibleState.setVisualBlockState(appearance.blockState()); + possibleState.setEstimatedBoundingBox(appearance.estimateAABB()); appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); } } @@ -662,6 +667,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem if (visualState == null) { visualState = anyAppearance.blockState(); state.setVisualBlockState(visualState); + state.setEstimatedBoundingBox(anyAppearance.estimateAABB()); anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); } int appearanceId = visualState.registryId(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java index 75e39df37..d2b8d0189 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java @@ -2,8 +2,11 @@ package net.momirealms.craftengine.core.block; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; +import net.momirealms.craftengine.core.world.collision.AABB; import java.util.Optional; -public record BlockStateAppearance(BlockStateWrapper blockState, Optional[]> blockEntityRenderer) { +public record BlockStateAppearance(BlockStateWrapper blockState, + Optional[]> blockEntityRenderer, + AABB estimateAABB) { } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 8e4430e2e..9a4010ef8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -17,6 +17,7 @@ import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.collision.AABB; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.NBT; import net.momirealms.sparrow.nbt.Tag; @@ -42,6 +43,7 @@ public final class ImmutableBlockState { private BlockEntityType blockEntityType; @Nullable private BlockEntityElementConfig[] renderers; + private AABB estimatedBoundingBox; ImmutableBlockState( Holder.Reference owner, @@ -87,6 +89,14 @@ public final class ImmutableBlockState { this.renderers = renderers; } + public void setEstimatedBoundingBox(AABB aabb) { + this.estimatedBoundingBox = aabb; + } + + public AABB estimatedBoundingBox() { + return estimatedBoundingBox; + } + public boolean hasBlockEntity() { return this.blockEntityType != null; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java index 4b45900a0..48245a05d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java @@ -2,14 +2,18 @@ package net.momirealms.craftengine.core.block.entity.render; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.Cullable; +import net.momirealms.craftengine.core.world.collision.AABB; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Experimental -public class ConstantBlockEntityRenderer { +public class ConstantBlockEntityRenderer implements Cullable { private final BlockEntityElement[] elements; + public final AABB aabb; - public ConstantBlockEntityRenderer(BlockEntityElement[] elements) { + public ConstantBlockEntityRenderer(BlockEntityElement[] elements, AABB aabb) { this.elements = elements; + this.aabb = aabb; } public void show(Player player) { @@ -47,4 +51,9 @@ public class ConstantBlockEntityRenderer { public BlockEntityElement[] elements() { return this.elements; } + + @Override + public AABB aabb() { + return this.aabb; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index 034502a96..d4dc6cddc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.entity.player; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.advancement.AdvancementType; +import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.entity.AbstractEntity; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.CooldownData; @@ -9,14 +10,13 @@ import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.Position; -import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.craftengine.core.world.WorldPosition; +import net.momirealms.craftengine.core.world.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.Locale; +import java.util.Map; public abstract class Player extends AbstractEntity implements NetWorkUser { private static final Key TYPE = Key.of("minecraft:player"); @@ -35,6 +35,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { @Override public abstract Object serverPlayer(); + public abstract void setClientSideWorld(World world); + public abstract float getDestroyProgress(Object blockState, BlockPos pos); public abstract void setClientSideCanBreakBlock(boolean canBreak); @@ -198,6 +200,12 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void sendTotemAnimation(Item totem, @Nullable SoundData sound, boolean silent); + public abstract void addTrackedBlockEntities(Map renders); + + public abstract void removeTrackedBlockEntities(Collection renders); + + public abstract void clearTrackedBlockEntities(); + @Override public void remove() { } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 58c1ac8cd..2c32adc8b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -21,7 +21,6 @@ import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager import net.momirealms.craftengine.core.plugin.compatibility.PluginTaskRegistry; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.template.TemplateManager; -import net.momirealms.craftengine.core.plugin.config.template.TemplateManagerImpl; import net.momirealms.craftengine.core.plugin.context.GlobalVariableManager; import net.momirealms.craftengine.core.plugin.dependency.Dependencies; import net.momirealms.craftengine.core.plugin.dependency.Dependency; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index c75eaa370..3720a4268 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -203,6 +203,8 @@ public class Config { protected boolean emoji$contexts$sign; protected int emoji$max_emojis_per_parse; + protected boolean client_optimization$entity_culling$enable; + public Config(CraftEngine plugin) { this.plugin = plugin; this.configVersion = PluginProperties.getValue("config"); @@ -562,6 +564,9 @@ public class Config { emoji$contexts$sign = config.getBoolean("emoji.contexts.sign", true); emoji$max_emojis_per_parse = config.getInt("emoji.max-emojis-per-parse", 32); + // client optimization + client_optimization$entity_culling$enable = config.getBoolean("client-optimization.entity-culling.enable", false); + firstTime = false; } @@ -1152,6 +1157,10 @@ public class Config { return instance.resource_pack$optimization$texture$zopfli_iterations; } + public static boolean enableEntityCulling() { + return instance.client_optimization$entity_culling$enable; + } + public YamlDocument loadOrCreateYamlData(String fileName) { Path path = this.plugin.dataFolderPath().resolve(fileName); if (!Files.exists(path)) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java index 79b942023..bed0110ef 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java @@ -2,14 +2,7 @@ package net.momirealms.craftengine.core.plugin.config.template; import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigParser; -import net.momirealms.craftengine.core.plugin.config.template.argument.TemplateArgument; -import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.SNBTReader; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; public interface TemplateManager extends Manageable { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ExpressionTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ExpressionTemplateArgument.java index 667b28db2..cf02eeb46 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ExpressionTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ExpressionTemplateArgument.java @@ -3,7 +3,6 @@ package net.momirealms.craftengine.core.plugin.config.template.argument; import com.ezylang.evalex.Expression; import com.ezylang.evalex.data.EvaluationValue; import net.momirealms.craftengine.core.plugin.config.template.ArgumentString; -import net.momirealms.craftengine.core.plugin.config.template.TemplateManager; import net.momirealms.craftengine.core.util.Key; import java.util.Locale; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index 501200a8a..ab1d0d520 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -6,7 +6,8 @@ import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.util.IntIdentityList; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.chunk.ChunkStatus; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -65,9 +66,7 @@ public interface NetWorkUser { @ApiStatus.Internal ConnectionState encoderState(); - int clientSideSectionCount(); - - Key clientSideDimension(); + World clientSideWorld(); Object serverPlayer(); @@ -89,9 +88,9 @@ public interface NetWorkUser { boolean isChunkTracked(long chunkPos); - ChunkStatus getTrackedChunk(long chunkPos); + ClientChunk getTrackedChunk(long chunkPos); - void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus); + void addTrackedChunk(long chunkPos, ClientChunk chunkStatus); void clearTrackedChunks(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java index 415893a3a..1a98323d1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java @@ -6,6 +6,7 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.collision.AABB; import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -341,4 +342,52 @@ public final class ResourceConfigUtils { } TranslationManager.instance().log(e.node(), e.arguments()); } + + public static AABB getAsAABB(Object o, String option) { + if (o == null) { + throw new LocalizedResourceConfigException("warning.config.type.aabb", "null", option); + } + if (o instanceof Number number) { + double min = -(number.doubleValue() / 2); + double max = number.doubleValue() / 2; + return new AABB(min, min, min, max, max, max); + } else { + double[] args; + if (o instanceof List list) { + args = new double[list.size()]; + for (int i = 0; i < args.length; i++) { + if (list.get(i) instanceof Number number) { + args[i] = number.doubleValue(); + } else { + try { + args[i] = Double.parseDouble(list.get(i).toString()); + } catch (NumberFormatException e) { + throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option); + } + } + } + } else { + String[] split = o.toString().split(","); + args = new double[split.length]; + for (int i = 0; i < args.length; i++) { + try { + args[i] = Double.parseDouble(split[i]); + } catch (NumberFormatException e) { + throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option); + } + } + } + if (args.length == 1) { + return new AABB(-args[0]/2, -args[0]/2, -args[0]/2, args[0]/2, args[0]/2, args[0]/2); + } else if (args.length == 2) { + return new AABB(-args[0]/2, -args[1]/2, -args[0]/2, args[0]/2, args[1]/2, args[0]/2); + } else if (args.length == 3) { + return new AABB(-args[0]/2, -args[1]/2, -args[2]/2, args[0]/2, args[1]/2, args[2]/2); + } else if (args.length == 6) { + return new AABB(args[0], args[1], args[2], args[3], args[4], args[5]); + } else { + throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option); + } + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java b/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java new file mode 100644 index 000000000..700442fb3 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java @@ -0,0 +1,13 @@ +package net.momirealms.craftengine.core.world; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.collision.AABB; + +public interface Cullable { + + AABB aabb(); + + void show(Player player); + + void hide(Player player); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java index 82b2e4bbb..bd4a707e6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java @@ -4,9 +4,9 @@ import net.momirealms.craftengine.core.util.Direction; public class Vec3i implements Comparable { public static final Vec3i ZERO = new Vec3i(0, 0, 0); - protected int x; - protected int y; - protected int z; + public final int x; + public final int y; + public final int z; public Vec3i(int x, int y, int z) { this.x = x; @@ -30,21 +30,6 @@ public class Vec3i implements Comparable { return x == 0 && y == 0 && z == 0 ? this : new Vec3i(this.x() + x, this.y() + y, this.z() + z); } - protected Vec3i setX(int x) { - this.x = x; - return this; - } - - protected Vec3i setY(int y) { - this.y = y; - return this; - } - - protected Vec3i setZ(int z) { - this.z = z; - return this; - } - @Override public boolean equals(Object object) { return this == object || object instanceof Vec3i vec3i && this.x == vec3i.x && this.y == vec3i.y && this.z == vec3i.z; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ArrayPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ArrayPalette.java index 0ace21c98..f7926684a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ArrayPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ArrayPalette.java @@ -77,15 +77,24 @@ public class ArrayPalette implements Palette { @Override public boolean hasAny(Predicate predicate) { - for(int i = 0; i < this.size; ++i) { + for (int i = 0; i < this.size; ++i) { if (predicate.test(this.array[i])) { return true; } } - return false; } + @Override + public boolean allMatch(Predicate predicate) { + for (int i = 0; i < this.size; ++i) { + if (!predicate.test(this.array[i])) { + return false; + } + } + return true; + } + @Override public T get(int id) { if (id >= 0 && id < this.size) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java index 02d0b190d..ae9be1758 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java @@ -67,7 +67,7 @@ public class BiMapPalette implements Palette { @Override public boolean hasAny(Predicate predicate) { - for(int i = 0; i < this.getSize(); ++i) { + for (int i = 0; i < this.getSize(); ++i) { if (predicate.test(this.map.get(i))) { return true; } @@ -75,6 +75,16 @@ public class BiMapPalette implements Palette { return false; } + @Override + public boolean allMatch(Predicate predicate) { + for (int i = 0; i < this.getSize(); ++i) { + if (!predicate.test(this.map.get(i))) { + return false; + } + } + return true; + } + @Override public T get(int id) { T object = this.map.get(id); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index a0babe284..f12123ae2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -11,6 +11,7 @@ import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityEl import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; import net.momirealms.craftengine.core.block.entity.tick.*; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntityRendererSerializer; @@ -91,8 +92,12 @@ public class CEChunk { public void spawnBlockEntities(Player player) { try { this.renderLock.readLock().lock(); - for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { - renderer.show(player); + if (Config.enableEntityCulling()) { + player.addTrackedBlockEntities(this.constantBlockEntityRenderers); + } else { + for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { + renderer.show(player); + } } for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) { renderer.show(player); @@ -105,8 +110,12 @@ public class CEChunk { public void despawnBlockEntities(Player player) { try { this.renderLock.readLock().lock(); - for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { - renderer.hide(player); + if (Config.enableEntityCulling()) { + player.removeTrackedBlockEntities(this.constantBlockEntityRenderers.keySet()); + } else { + for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { + renderer.hide(player); + } } for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) { renderer.hide(player); @@ -129,7 +138,7 @@ public class CEChunk { BlockEntityElementConfig[] renderers = state.constantRenderers(); if (renderers != null && renderers.length > 0) { BlockEntityElement[] elements = new BlockEntityElement[renderers.length]; - ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer(elements); + ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer(elements, state.estimatedBoundingBox().move(pos)); World wrappedWorld = this.world.world(); List trackedBy = getTrackedBy(); boolean hasTrackedBy = trackedBy != null && !trackedBy.isEmpty(); @@ -439,7 +448,7 @@ public class CEChunk { return Collections.unmodifiableCollection(this.blockEntities.values()); } - public List constantBlockEntityRenderers() { + public List constantBlockEntityRendererPositions() { try { this.renderLock.readLock().lock(); return new ArrayList<>(this.constantBlockEntityRenderers.keySet()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java deleted file mode 100644 index 744068aa6..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.momirealms.craftengine.core.world.chunk; - -public class ChunkStatus { - - public ChunkStatus() { - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java index 51ee07710..1fd1f1155 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java @@ -29,6 +29,11 @@ public class IdListPalette implements Palette { return true; } + @Override + public boolean allMatch(Predicate predicate) { + return true; + } + @Override public T get(int id) { T object = this.idList.get(id); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java index f190afaee..73d6523ab 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java @@ -13,6 +13,8 @@ public interface Palette { boolean hasAny(Predicate predicate); + boolean allMatch(Predicate predicate); + T get(int id); int getSize(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java index afcfe09be..b52a6308c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java @@ -45,6 +45,15 @@ public class SingularPalette implements Palette { } } + @Override + public boolean allMatch(Predicate predicate) { + if (this.entry == null) { + throw new IllegalStateException("Use of an uninitialized palette"); + } else { + return predicate.test(this.entry); + } + } + @Override public T get(int id) { if (this.entry != null && id == 0) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java new file mode 100644 index 000000000..e37e1ac31 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java @@ -0,0 +1,33 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +import net.momirealms.craftengine.core.world.SectionPos; +import net.momirealms.craftengine.core.world.WorldHeight; +import org.jetbrains.annotations.Nullable; + +public class ClientChunk { + @Nullable + public final ClientSection[] sections; + private final WorldHeight worldHeight; + + public ClientChunk(ClientSection[] sections, WorldHeight worldHeight) { + this.sections = sections; + this.worldHeight = worldHeight; + } + + @Nullable + public ClientSection[] sections() { + return sections; + } + + public boolean isOccluding(int x, int y, int z) { + if (this.sections == null) return false; + int index = sectionIndex(SectionPos.blockToSectionCoord(y)); + ClientSection section = this.sections[index]; + if (section == null) return false; + return section.isOccluding((y & 15) << 8 | (z & 15) << 4 | x & 15); + } + + public int sectionIndex(int sectionId) { + return sectionId - this.worldHeight.getMinSection(); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java new file mode 100644 index 000000000..af3f71749 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java @@ -0,0 +1,34 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +public class ClientSection { + private ClientSectionOcclusionStorage storage; + + public ClientSection(ClientSectionOcclusionStorage storage) { + this.storage = storage; + } + + boolean isOccluding(int x, int y, int z) { + return isOccluding((y << 4 | z) << 4 | x); + } + + boolean isOccluding(int index) { + return this.storage.isOccluding(index); + } + + void setOccluding(int x, int y, int z, boolean value) { + this.setOccluding((y << 4 | z) << 4 | x, value); + } + + void setOccluding(int index, boolean value) { + boolean wasOccluding = this.storage.isOccluding(index); + if (wasOccluding != value) { + if (this.storage instanceof PackedOcclusionStorage arrayStorage) { + arrayStorage.set(index, value); + } else { + PackedOcclusionStorage newStorage = new PackedOcclusionStorage(wasOccluding); + newStorage.set(index, value); + this.storage = newStorage; + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSectionOcclusionStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSectionOcclusionStorage.java new file mode 100644 index 000000000..0a53ede3d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSectionOcclusionStorage.java @@ -0,0 +1,6 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +public interface ClientSectionOcclusionStorage { + + boolean isOccluding(int index); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/PackedOcclusionStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/PackedOcclusionStorage.java new file mode 100644 index 000000000..10e3b08dd --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/PackedOcclusionStorage.java @@ -0,0 +1,37 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +import java.util.Arrays; + +public class PackedOcclusionStorage implements ClientSectionOcclusionStorage { + private static final int SIZE = 4096; + private static final int LONGS = SIZE / 64; + private final long[] data; + + public PackedOcclusionStorage() { + this.data = new long[LONGS]; + } + + public PackedOcclusionStorage(boolean defaultValue) { + this.data = new long[LONGS]; + if (defaultValue) { + Arrays.fill(this.data, -1L); // 所有位设为1 + } + } + + @Override + public boolean isOccluding(int index) { + int arrayIndex = index >>> 6; // index / 64 + int bitIndex = index & 0x3F; // index % 64 + return (this.data[arrayIndex] & (1L << bitIndex)) != 0; + } + + public void set(int index, boolean occlusion) { + int arrayIndex = index >>> 6; // index / 64 + int bitIndex = index & 0x3F; // index % 64 + if (occlusion) { + this.data[arrayIndex] |= (1L << bitIndex); + } else { + this.data[arrayIndex] &= ~(1L << bitIndex); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/SingularOcclusionStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/SingularOcclusionStorage.java new file mode 100644 index 000000000..7da13ba23 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/SingularOcclusionStorage.java @@ -0,0 +1,14 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +public class SingularOcclusionStorage implements ClientSectionOcclusionStorage { + private final boolean isOccluding; + + public SingularOcclusionStorage(boolean isOccluding) { + this.isOccluding = isOccluding; + } + + @Override + public boolean isOccluding(int index) { + return this.isOccluding; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java new file mode 100644 index 000000000..127b8b09e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java @@ -0,0 +1,31 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.Cullable; + +public class VirtualCullableObject { + private final Cullable cullable; + private boolean isShown; + + public VirtualCullableObject(Cullable cullable) { + this.cullable = cullable; + this.isShown = false; + } + + public Cullable cullable() { + return cullable; + } + + public boolean isShown() { + return isShown; + } + + public void setShown(Player player, boolean shown) { + this.isShown = shown; + if (shown) { + this.cullable.show(player); + } else { + this.cullable.hide(player); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java index ef1357235..2f851fe49 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java @@ -32,7 +32,7 @@ public final class DefaultChunkSerializer { if (!blockEntities.isEmpty()) { chunkNbt.put("block_entities", blockEntities); } - ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRenderers()); + ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRendererPositions()); if (!blockEntityRenders.isEmpty()) { chunkNbt.put("block_entity_renderers", blockEntityRenders); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java index 9a837a003..c5a725f59 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java @@ -35,6 +35,17 @@ public class AABB { this.maxZ = Math.max(pos1.z, pos2.z); } + public AABB move(BlockPos pos) { + return new AABB( + this.minX + pos.x + 0.5, + this.minY + pos.y + 0.5, + this.minZ + pos.z + 0.5, + this.maxX + pos.x + 0.5, + this.maxY + pos.y + 0.5, + this.maxZ + pos.z + 0.5 + ); + } + public AABB(BlockPos pos) { this(pos.x(), pos.y(), pos.z(), pos.x() + 1, pos.y() + 1, pos.z() + 1); } From 425e52c479c945b63907489dd392d37953da8037 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 28 Nov 2025 03:49:39 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E4=BE=A7=E9=81=AE=E8=94=BD=E4=B8=96=E7=95=8C=E6=9E=84?= =?UTF-8?q?=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/BukkitNetworkManager.java | 101 +++++++++--------- .../core/plugin/context/ContextHolder.java | 7 +- .../craftengine/core/world/SectionPos.java | 35 ++++++ .../core/world/chunk/client/ClientChunk.java | 20 ++++ .../world/chunk/client/ClientSection.java | 8 +- 5 files changed, 115 insertions(+), 56 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 0d962ccb9..b0c928641 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -326,8 +326,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes RegistryUtils.currentBiomeRegistrySize(), occlusionPredicate ), this.packetIds.clientboundLevelChunkWithLightPacket(), "ClientboundLevelChunkWithLightPacket"); - registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket"); - registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket"); + registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD, occlusionPredicate), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket"); + registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD, occlusionPredicate), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket"); registerS2CGamePacketListener( VersionHelper.isOrAbove1_21_4() ? new LevelParticleListener1_21_4(newMappings, newMappingsMOD) : @@ -1993,10 +1993,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes this.occlusionPredicate = occlusionPredicate; } - public int remapBlockState(int stateId, boolean enableMod) { - return enableMod ? this.modBlockStateMapper[stateId] : this.blockStateMapper[stateId]; - } - @Override public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { BukkitServerPlayer player = (BukkitServerPlayer) user; @@ -2006,6 +2002,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); boolean named = !VersionHelper.isOrAbove1_20_2(); + int[] remapper = user.clientModEnabled() ? this.modBlockStateMapper : this.blockStateMapper; + // 读取区块数据 int heightmapsCount = 0; Map heightmapsMap = null; @@ -2053,7 +2051,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes if (palette.canRemap()) { // 重定向方块 - if (palette.remapAndCheck(s -> remapBlockState(s, user.clientModEnabled()))) { + if (palette.remapAndCheck(s -> remapper[s])) { hasChangedAnyBlock = true; } @@ -2100,7 +2098,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes int state = container.get(j); // 重定向方块 - int newState = remapBlockState(state, user.clientModEnabled()); + int newState = remapper[state]; if (newState != state) { container.set(j, newState); hasChangedAnyBlock = true; @@ -2195,63 +2193,64 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes public static class SectionBlockUpdateListener implements ByteBufferPacketListener { private final int[] blockStateMapper; private final int[] modBlockStateMapper; + private final Predicate occlusionPredicate; - public SectionBlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper) { + public SectionBlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper, Predicate occlusionPredicate) { this.blockStateMapper = blockStateMapper; this.modBlockStateMapper = modBlockStateMapper; + this.occlusionPredicate = occlusionPredicate; } @Override public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { - if (user.clientModEnabled()) { - FriendlyByteBuf buf = event.getBuffer(); - long pos = buf.readLong(); - int blocks = buf.readVarInt(); - short[] positions = new short[blocks]; - int[] states = new int[blocks]; - for (int i = 0; i < blocks; i++) { - long k = buf.readVarLong(); - positions[i] = (short) ((int) (k & 4095L)); - states[i] = modBlockStateMapper[((int) (k >>> 12))]; - } - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeLong(pos); - buf.writeVarInt(blocks); - for (int i = 0; i < blocks; i++) { - buf.writeVarLong((long) states[i] << 12 | positions[i]); - } - event.setChanged(true); - } else { - FriendlyByteBuf buf = event.getBuffer(); - long pos = buf.readLong(); - int blocks = buf.readVarInt(); - short[] positions = new short[blocks]; - int[] states = new int[blocks]; - for (int i = 0; i < blocks; i++) { - long k = buf.readVarLong(); - positions[i] = (short) ((int) (k & 4095L)); - states[i] = blockStateMapper[((int) (k >>> 12))]; - } - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeLong(pos); - buf.writeVarInt(blocks); - for (int i = 0; i < blocks; i++) { - buf.writeVarLong((long) states[i] << 12 | positions[i]); - } - event.setChanged(true); + int[] remapper = user.clientModEnabled() ? this.modBlockStateMapper : this.blockStateMapper; + FriendlyByteBuf buf = event.getBuffer(); + long sPos = buf.readLong(); + int blocks = buf.readVarInt(); + short[] positions = new short[blocks]; + int[] states = new int[blocks]; + + // 获取客户端侧区域 + ClientSection clientSection = null; + if (Config.enableEntityCulling()) { + SectionPos sectionPos = SectionPos.of(sPos); + ClientChunk trackedChunk = user.getTrackedChunk(sectionPos.asChunkPos().longKey); + clientSection = trackedChunk.sectionById(sectionPos.y); } + + for (int i = 0; i < blocks; i++) { + long k = buf.readVarLong(); + short posIndex = (short) ((int) (k & 4095L)); + positions[i] = posIndex; + int beforeState = ((int) (k >>> 12)); + states[i] = remapper[beforeState]; + if (clientSection != null) { + // 设置遮蔽状态 + BlockPos pos = SectionPos.unpackSectionRelativePos(posIndex); + clientSection.setOccluding(pos.x, pos.y, pos.z, this.occlusionPredicate.test(beforeState)); + } + } + + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeLong(sPos); + buf.writeVarInt(blocks); + for (int i = 0; i < blocks; i++) { + buf.writeVarLong((long) states[i] << 12 | positions[i]); + } + event.setChanged(true); } } public static class BlockUpdateListener implements ByteBufferPacketListener { private final int[] blockStateMapper; private final int[] modBlockStateMapper; + private final Predicate occlusionPredicate; - public BlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper) { + public BlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper, Predicate occlusionPredicate) { this.blockStateMapper = blockStateMapper; this.modBlockStateMapper = modBlockStateMapper; + this.occlusionPredicate = occlusionPredicate; } @Override @@ -2271,6 +2270,12 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes buf.writeVarInt(event.packetID()); buf.writeBlockPos(pos); buf.writeVarInt(state); + if (Config.enableEntityCulling()) { + ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(pos.x >> 4, pos.z >> 4)); + if (trackedChunk != null) { + trackedChunk.setOccluding(pos.x, pos.y, pos.z, this.occlusionPredicate.test(state)); + } + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java index 59d678797..758f77042 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java @@ -1,13 +1,12 @@ package net.momirealms.craftengine.core.plugin.context; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Optional; +import java.util.*; import java.util.function.Supplier; public class ContextHolder { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java index fac06da92..48c234da8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/SectionPos.java @@ -17,4 +17,39 @@ public class SectionPos extends Vec3i { public static int sectionRelative(int rel) { return rel & 15; } + + public static SectionPos of(long packed) { + return new SectionPos((int) (packed >> 42), (int) (packed << 44 >> 44), (int) (packed << 22 >> 42)); + } + + public long asLong() { + return ((long) this.x & 4194303L) << 42 | (long) this.y & 1048575L | ((long) this.z & 4194303L) << 20; + } + + public ChunkPos asChunkPos() { + return new ChunkPos(this.x, this.z); + } + + public static short packSectionRelativePos(BlockPos pos) { + return (short) ((pos.x & 15) << 8 | (pos.z & 15) << 4 | pos.y & 15); + } + + public static BlockPos unpackSectionRelativePos(short encoded) { + int x = (encoded >> 8) & 15; + int z = (encoded >> 4) & 15; + int y = encoded & 15; + return new BlockPos(x, y, z); + } + + public final int minBlockX() { + return this.x << 4; + } + + public final int minBlockY() { + return this.y << 4; + } + + public final int minBlockZ() { + return this.z << 4; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java index e37e1ac31..29379fd21 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java @@ -27,7 +27,27 @@ public class ClientChunk { return section.isOccluding((y & 15) << 8 | (z & 15) << 4 | x & 15); } + public void setOccluding(int x, int y, int z, boolean occluding) { + if (this.sections == null) return; + int index = sectionIndex(SectionPos.blockToSectionCoord(y)); + ClientSection section = this.sections[index]; + if (section == null) return; + section.setOccluding((y & 15) << 8 | (z & 15) << 4, occluding); + } + public int sectionIndex(int sectionId) { return sectionId - this.worldHeight.getMinSection(); } + + @Nullable + public ClientSection sectionByIndex(int sectionIndex) { + if (this.sections == null) return null; + return this.sections[sectionIndex]; + } + + @Nullable + public ClientSection sectionById(int sectionId) { + if (this.sections == null) return null; + return this.sections[sectionIndex(sectionId)]; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java index af3f71749..32e668d15 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java @@ -7,19 +7,19 @@ public class ClientSection { this.storage = storage; } - boolean isOccluding(int x, int y, int z) { + public boolean isOccluding(int x, int y, int z) { return isOccluding((y << 4 | z) << 4 | x); } - boolean isOccluding(int index) { + public boolean isOccluding(int index) { return this.storage.isOccluding(index); } - void setOccluding(int x, int y, int z, boolean value) { + public void setOccluding(int x, int y, int z, boolean value) { this.setOccluding((y << 4 | z) << 4 | x, value); } - void setOccluding(int index, boolean value) { + public void setOccluding(int index, boolean value) { boolean wasOccluding = this.storage.isOccluding(index); if (wasOccluding != value) { if (this.storage instanceof PackedOcclusionStorage arrayStorage) { From f4607844608270253e9660b2df3ab3ecdda593e1 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 28 Nov 2025 15:35:50 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BA=A7=E6=A4=85?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=9B=91=E5=90=AC=E5=99=A8=E6=9C=AA=E6=B3=A8?= =?UTF-8?q?=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/entity/seat/BukkitSeatManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/seat/BukkitSeatManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/seat/BukkitSeatManager.java index 2e52d5564..e704276ff 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/seat/BukkitSeatManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/seat/BukkitSeatManager.java @@ -61,6 +61,7 @@ public class BukkitSeatManager implements SeatManager, Listener { @Override public void delayedInit() { Bukkit.getPluginManager().registerEvents(this.dismountListener, this.plugin.javaPlugin()); + Bukkit.getPluginManager().registerEvents(this, this.plugin.javaPlugin()); } @Override From 7eb4dc1ff8b2110dfa787f40937d5cc6702f2ce7 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 28 Nov 2025 22:06:06 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=89=94=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mythicmobs/MythicItemDrop.java | 2 +- .../plugin/network/BukkitNetworkManager.java | 21 +- .../plugin/user/BukkitServerPlayer.java | 23 + .../craftengine/bukkit/util/EntityUtils.java | 2 +- .../configuration/templates/loot_tables.yml | 2 +- .../render/ConstantBlockEntityRenderer.java | 2 + .../core/entity/player/Player.java | 5 + .../craftengine/core/loot/LootPool.java | 2 +- .../core/plugin/context/ContextHolder.java | 7 +- .../condition/MatchBlockCondition.java | 2 +- .../context/function/BreakBlockFunction.java | 2 +- .../function/CycleBlockPropertyFunction.java | 6 +- .../context/function/PlaceBlockFunction.java | 2 +- .../plugin/context/function/RunFunction.java | 2 +- .../function/TransformBlockFunction.java | 6 +- .../function/UpdateBlockPropertyFunction.java | 6 +- .../parameter/EntityParameterProvider.java | 6 +- .../parameter/PlayerParameterProvider.java | 6 +- .../parameter/PositionParameterProvider.java | 6 +- .../plugin/entityculling/EntityCulling.java | 601 ++++++------------ .../plugin/entityculling/VoxelIterator.java | 216 +++++++ .../craftengine/core/util/Color.java | 2 +- .../craftengine/core/util/MiscUtils.java | 8 +- .../craftengine/core/world/BlockPos.java | 2 +- .../core/world/EntityHitResult.java | 6 +- .../craftengine/core/world/MutableVec3d.java | 6 +- .../craftengine/core/world/Vec3d.java | 2 +- .../craftengine/core/world/chunk/CEChunk.java | 78 ++- .../core/world/chunk/client/ClientChunk.java | 2 +- .../chunk/client/VirtualCullableObject.java | 3 +- 30 files changed, 584 insertions(+), 452 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java index 9258e4214..cb3ba791a 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java @@ -42,7 +42,7 @@ public class MythicItemDrop extends ItemDrop implements IItemDrop { context = ItemBuildContext.of(player); } } - int amountInt = MiscUtils.fastFloor(amount + 0.5F); + int amountInt = MiscUtils.floor(amount + 0.5F); ItemStack itemStack = this.customItem.buildItemStack(context, amountInt); return adapt(itemStack).amount(amountInt); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index b0c928641..c49b423e6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -2075,6 +2075,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes // 两种情况都有,那么需要一个个遍历处理视线遮挡数据 if (hasOcclusions && hasNoOcclusions) { PackedOcclusionStorage storage = new PackedOcclusionStorage(false); + clientSections[i] = new ClientSection(storage); for (int j = 0; j < 4096; j++) { int state = container.get(j); storage.set(j, this.occlusionPredicate.test(state)); @@ -2092,6 +2093,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes PackedOcclusionStorage storage = null; if (clientSections != null) { storage = new PackedOcclusionStorage(false); + clientSections[i] = new ClientSection(storage); } for (int j = 0; j < 4096; j++) { @@ -2258,6 +2260,12 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes FriendlyByteBuf buf = event.getBuffer(); BlockPos pos = buf.readBlockPos(); int before = buf.readVarInt(); + if (Config.enableEntityCulling()) { + ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(pos.x >> 4, pos.z >> 4)); + if (trackedChunk != null) { + trackedChunk.setOccluding(pos.x, pos.y, pos.z, this.occlusionPredicate.test(before)); + } + } if (user.clientModEnabled() && !BlockStateUtils.isVanillaBlock(before)) { return; } @@ -2270,12 +2278,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes buf.writeVarInt(event.packetID()); buf.writeBlockPos(pos); buf.writeVarInt(state); - if (Config.enableEntityCulling()) { - ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(pos.x >> 4, pos.z >> 4)); - if (trackedChunk != null) { - trackedChunk.setOccluding(pos.x, pos.y, pos.z, this.occlusionPredicate.test(state)); - } - } } } @@ -2437,6 +2439,13 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes if (eventId != WorldEvents.BLOCK_BREAK_EFFECT) return; BlockPos blockPos = buf.readBlockPos(); int state = buf.readInt(); + // 移除不透明设置 + if (Config.enableEntityCulling()) { + ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(blockPos.x >> 4, blockPos.z >> 4)); + if (trackedChunk != null) { + trackedChunk.setOccluding(blockPos.x, blockPos.y, blockPos.z, false); + } + } boolean global = buf.readBoolean(); int newState = user.clientModEnabled() ? modBlockStateMapper[state] : blockStateMapper[state]; Object blockState = BlockStateUtils.idToBlockState(state); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 842be8e1d..a08fea4dc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -32,6 +32,7 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.CooldownData; +import net.momirealms.craftengine.core.plugin.entityculling.EntityCulling; import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.plugin.network.ConnectionState; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; @@ -142,6 +143,8 @@ public class BukkitServerPlayer extends Player { // 跟踪到的方块实体渲染器 private final Map trackedBlockEntityRenderers = new ConcurrentHashMap<>(); + private final EntityCulling culling; + public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { this.channel = channel; this.plugin = plugin; @@ -154,6 +157,7 @@ public class BukkitServerPlayer extends Player { } } } + this.culling = new EntityCulling(this, 64, 0.5); } public void setPlayer(org.bukkit.entity.Player player) { @@ -559,6 +563,15 @@ public class BukkitServerPlayer extends Player { this.predictNextBlockToMine(); } } + if (Config.enableEntityCulling()) { + long nano1 = System.nanoTime(); + for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) { + boolean visible = this.culling.isVisible(cullableObject.cullable.aabb(), LocationUtils.toVec3d(platformPlayer().getEyeLocation())); + cullableObject.setShown(this, visible); + } + long nano2 = System.nanoTime(); + //CraftEngine.instance().logger().info("EntityCulling took " + (nano2 - nano1) / 1_000_000d + "ms"); + } } private void updateGUI() { @@ -1303,6 +1316,16 @@ public class BukkitServerPlayer extends Player { } } + @Override + public void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer) { + this.trackedBlockEntityRenderers.put(blockPos, new VirtualCullableObject(renderer)); + } + + @Override + public VirtualCullableObject getTrackedBlockEntity(BlockPos blockPos) { + return this.trackedBlockEntityRenderers.get(blockPos); + } + @Override public void removeTrackedBlockEntities(Collection renders) { for (BlockPos render : renders) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java index 05a9600e1..f9cc52975 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java @@ -60,7 +60,7 @@ public final class EntityUtils { Object serverLevel = BukkitAdaptors.adapt(player.getWorld()).serverWorld(); Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player); for (Object pose : List.of(CoreReflections.instance$Pose$STANDING, CoreReflections.instance$Pose$CROUCHING, CoreReflections.instance$Pose$SWIMMING)) { - BlockPos pos = new BlockPos(MiscUtils.fastFloor(x), MiscUtils.fastFloor(y), MiscUtils.fastFloor(z)); + BlockPos pos = new BlockPos(MiscUtils.floor(x), MiscUtils.floor(y), MiscUtils.floor(z)); try { double floorHeight = (double) CoreReflections.method$BlockGetter$getBlockFloorHeight.invoke(serverLevel, LocationUtils.toBlockPos(pos)); if (pos.y() + floorHeight > y + 0.75 || !isBlockFloorValid(floorHeight)) { diff --git a/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml b/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml index 99a366d26..24d3c1878 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml @@ -178,7 +178,7 @@ templates: # template: default:loot_table/ore # arguments: # ore_block: the ore block - # ore_drop: the drops of the ore + # ore_drop: the drops of the ore material # ore_drop_count: the amount of the ore materials # exp: the exp to drop default:loot_table/ore: diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java index 48245a05d..4551ed667 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java @@ -16,6 +16,7 @@ public class ConstantBlockEntityRenderer implements Cullable { this.aabb = aabb; } + @Override public void show(Player player) { for (BlockEntityElement element : this.elements) { if (element != null) { @@ -24,6 +25,7 @@ public class ConstantBlockEntityRenderer implements Cullable { } } + @Override public void hide(Player player) { for (BlockEntityElement element : this.elements) { if (element != null) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index d4dc6cddc..36e3fcb76 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -11,6 +11,7 @@ import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -202,6 +203,10 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void addTrackedBlockEntities(Map renders); + public abstract void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer); + + public abstract VirtualCullableObject getTrackedBlockEntity(BlockPos blockPos); + public abstract void removeTrackedBlockEntities(Collection renders); public abstract void clearTrackedBlockEntities(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java b/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java index f07bd5d3c..f82d2a7e9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java @@ -44,7 +44,7 @@ public class LootPool { } if (this.compositeCondition.test(context)) { Consumer> consumer = LootFunction.decorate(this.compositeFunction, lootConsumer, context); - int i = this.rolls.getInt(context) + MiscUtils.fastFloor(this.bonusRolls.getFloat(context) * context.luck()); + int i = this.rolls.getInt(context) + MiscUtils.floor(this.bonusRolls.getFloat(context) * context.luck()); for (int j = 0; j < i; ++j) { this.addRandomItem(createFunctionApplier(consumer, context), context); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java index 758f77042..59d678797 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java @@ -1,12 +1,13 @@ package net.momirealms.craftengine.core.plugin.context; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSortedMap; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; import java.util.function.Supplier; public class ContextHolder { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java index 9eee31c0c..7f5e977c4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java @@ -40,7 +40,7 @@ public class MatchBlockCondition implements Condition Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - ExistingBlock blockAt = world.getBlock(MiscUtils.fastFloor(this.x.getDouble(ctx)), MiscUtils.fastFloor(this.y.getDouble(ctx)), MiscUtils.fastFloor(this.z.getDouble(ctx))); + ExistingBlock blockAt = world.getBlock(MiscUtils.floor(this.x.getDouble(ctx)), MiscUtils.floor(this.y.getDouble(ctx)), MiscUtils.floor(this.z.getDouble(ctx))); return MiscUtils.matchRegex(blockAt.id().asString(), this.ids, this.regexMatch); } return false; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java index 268f23984..d0b63f852 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java @@ -28,7 +28,7 @@ public class BreakBlockFunction extends AbstractConditional @Override public void runInternal(CTX ctx) { Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); - optionalPlayer.ifPresent(player -> player.breakBlock(MiscUtils.fastFloor(x.getDouble(ctx)), MiscUtils.fastFloor(y.getDouble(ctx)), MiscUtils.fastFloor(z.getDouble(ctx)))); + optionalPlayer.ifPresent(player -> player.breakBlock(MiscUtils.floor(x.getDouble(ctx)), MiscUtils.floor(y.getDouble(ctx)), MiscUtils.floor(z.getDouble(ctx)))); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java index 6e1e58bf4..b99ef0e72 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java @@ -55,9 +55,9 @@ public class CycleBlockPropertyFunction extends AbstractCon Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isEmpty()) return; World world = optionalWorldPosition.get().world(); - int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); - int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); - int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); + int x = MiscUtils.floor(this.x.getDouble(ctx)); + int y = MiscUtils.floor(this.y.getDouble(ctx)); + int z = MiscUtils.floor(this.z.getDouble(ctx)); BlockStateWrapper wrapper = updateBlockState(world.getBlock(x, y, z).blockState(), ctx); world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx)); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java index 1b6e8bf72..fedd7639a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java @@ -40,7 +40,7 @@ public class PlaceBlockFunction extends AbstractConditional Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - world.setBlockState(MiscUtils.fastFloor(this.x.getDouble(ctx)), MiscUtils.fastFloor(this.y.getDouble(ctx)), MiscUtils.fastFloor(this.z.getDouble(ctx)), this.lazyBlockState.get(), this.updateFlags.getInt(ctx)); + world.setBlockState(MiscUtils.floor(this.x.getDouble(ctx)), MiscUtils.floor(this.y.getDouble(ctx)), MiscUtils.floor(this.z.getDouble(ctx)), this.lazyBlockState.get(), this.updateFlags.getInt(ctx)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java index 020c44fe7..15f5b0586 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java @@ -48,7 +48,7 @@ public class RunFunction extends AbstractConditionalFunctio for (Function function : functions) { function.run(ctx); } - }, delay, pos.world().platformWorld(), MiscUtils.fastFloor(pos.x()) >> 4, MiscUtils.fastFloor(pos.z()) >> 4); + }, delay, pos.world().platformWorld(), MiscUtils.floor(pos.x()) >> 4, MiscUtils.floor(pos.z()) >> 4); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java index 09da0dc26..b27f438c4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java @@ -45,9 +45,9 @@ public class TransformBlockFunction extends AbstractConditi Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); - int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); - int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); + int x = MiscUtils.floor(this.x.getDouble(ctx)); + int y = MiscUtils.floor(this.y.getDouble(ctx)); + int z = MiscUtils.floor(this.z.getDouble(ctx)); BlockStateWrapper existingBlockState = world.getBlock(x, y, z).blockState().withProperties(this.properties); CompoundTag newProperties = new CompoundTag(); for (String propertyName : existingBlockState.getPropertyNames()) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java index 693647cf5..b59202282 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java @@ -40,9 +40,9 @@ public class UpdateBlockPropertyFunction extends AbstractCo Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); - int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); - int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); + int x = MiscUtils.floor(this.x.getDouble(ctx)); + int y = MiscUtils.floor(this.y.getDouble(ctx)); + int z = MiscUtils.floor(this.z.getDouble(ctx)); ExistingBlock blockAt = world.getBlock(x, y, z); BlockStateWrapper wrapper = blockAt.blockState().withProperties(this.properties); world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java index 628b8d54c..0fd065ee2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java @@ -20,9 +20,9 @@ public class EntityParameterProvider implements ChainParameterProvider { CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::xRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z())); CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Entity::name); CONTEXT_FUNCTIONS.put(DirectContextParameters.UUID, Entity::uuid); CONTEXT_FUNCTIONS.put(DirectContextParameters.WORLD, Entity::world); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java index ee6033ccd..668a0c6d5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java @@ -21,9 +21,9 @@ public class PlayerParameterProvider implements ChainParameterProvider { CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::xRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z())); CONTEXT_FUNCTIONS.put(DirectContextParameters.FOOD, Player::foodLevel); CONTEXT_FUNCTIONS.put(DirectContextParameters.SATURATION, Player::saturation); CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Player::name); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java index c646658fb..3808e3f00 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java @@ -20,9 +20,9 @@ public class PositionParameterProvider implements ChainParameterProvider MiscUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z())); } @SuppressWarnings("unchecked") diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java index 72c6ecdc0..fe190845b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java @@ -1,453 +1,276 @@ package net.momirealms.craftengine.core.plugin.entityculling; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.world.ChunkPos; import net.momirealms.craftengine.core.world.MutableVec3d; import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; +import net.momirealms.craftengine.core.world.collision.AABB; import java.util.Arrays; -import java.util.BitSet; -public class EntityCulling { - - // 面掩码常量 - private static final int ON_MIN_X = 0x01; - private static final int ON_MAX_X = 0x02; - private static final int ON_MIN_Y = 0x04; - private static final int ON_MAX_Y = 0x08; - private static final int ON_MIN_Z = 0x10; - private static final int ON_MAX_Z = 0x20; - - private final int reach; +public final class EntityCulling { + private final Player player; + private final int maxDistance; private final double aabbExpansion; - private final DataProvider provider; - private final OcclusionCache cache; + private final boolean[] dotSelectors = new boolean[14]; + private final MutableVec3d[] targetPoints = new MutableVec3d[14]; - // 重用数据结构,减少GC压力 - private final BitSet skipList = new BitSet(); - private final MutableVec3d[] targetPoints = new MutableVec3d[15]; - private final MutableVec3d targetPos = new MutableVec3d(0, 0, 0); - private final int[] cameraPos = new int[3]; - private final boolean[] dotselectors = new boolean[14]; - private final int[] lastHitBlock = new int[3]; - - // 状态标志 - private boolean allowRayChecks = false; - private boolean allowWallClipping = false; - - public EntityCulling(int maxDistance, DataProvider provider) { - this(maxDistance, provider, new ArrayOcclusionCache(maxDistance), 0.5); - } - - public EntityCulling(int maxDistance, DataProvider provider, OcclusionCache cache, double aabbExpansion) { - this.reach = maxDistance; - this.provider = provider; - this.cache = cache; + public EntityCulling(Player player, int maxDistance, double aabbExpansion) { + this.player = player; + this.maxDistance = maxDistance; this.aabbExpansion = aabbExpansion; - // 预先初始化点对象 - for(int i = 0; i < targetPoints.length; i++) { - targetPoints[i] = new MutableVec3d(0, 0, 0); + for (int i = 0; i < this.targetPoints.length; i++) { + this.targetPoints[i] = new MutableVec3d(0,0,0); } } - public boolean isAABBVisible(Vec3d aabbMin, MutableVec3d aabbMax, MutableVec3d viewerPosition) { - try { - // 计算包围盒范围 - int maxX = MiscUtils.fastFloor(aabbMax.x + aabbExpansion); - int maxY = MiscUtils.fastFloor(aabbMax.y + aabbExpansion); - int maxZ = MiscUtils.fastFloor(aabbMax.z + aabbExpansion); - int minX = MiscUtils.fastFloor(aabbMin.x - aabbExpansion); - int minY = MiscUtils.fastFloor(aabbMin.y - aabbExpansion); - int minZ = MiscUtils.fastFloor(aabbMin.z - aabbExpansion); + public boolean isVisible(AABB aabb, Vec3d cameraPos) { + // 根据AABB获取能包裹此AABB的最小长方体 + int minX = MiscUtils.floor(aabb.minX - this.aabbExpansion); + int minY = MiscUtils.floor(aabb.minY - this.aabbExpansion); + int minZ = MiscUtils.floor(aabb.minZ - this.aabbExpansion); + int maxX = MiscUtils.ceil(aabb.maxX + this.aabbExpansion); + int maxY = MiscUtils.ceil(aabb.maxY + this.aabbExpansion); + int maxZ = MiscUtils.ceil(aabb.maxZ + this.aabbExpansion); - cameraPos[0] = MiscUtils.fastFloor(viewerPosition.x); - cameraPos[1] = MiscUtils.fastFloor(viewerPosition.y); - cameraPos[2] = MiscUtils.fastFloor(viewerPosition.z); - - // 判断是否在包围盒内部 - Relative relX = Relative.from(minX, maxX, cameraPos[0]); - Relative relY = Relative.from(minY, maxY, cameraPos[1]); - Relative relZ = Relative.from(minZ, maxZ, cameraPos[2]); - - if(relX == Relative.INSIDE && relY == Relative.INSIDE && relZ == Relative.INSIDE) { - return true; + double cameraX = cameraPos.x; + double cameraY = cameraPos.y; + double cameraZ = cameraPos.z; + + Relative relX = Relative.from(minX, maxX, cameraX); + Relative relY = Relative.from(minY, maxY, cameraY); + Relative relZ = Relative.from(minZ, maxZ, cameraZ); + + // 相机位于实体内部 + if (relX == Relative.INSIDE && relY == Relative.INSIDE && relZ == Relative.INSIDE) { + return true; + } + + // 如果设置了最大距离 + if (this.maxDistance > 0) { + // 计算AABB到相机的最小距离 + double distanceSq = 0.0; + // 计算XYZ轴方向的距离 + distanceSq += distanceSq(minX, maxX, cameraX, relX); + distanceSq += distanceSq(minY, maxY, cameraY, relY); + distanceSq += distanceSq(minZ, maxZ, cameraZ, relZ); + // 检查距离是否超过最大值 + double maxDistanceSq = this.maxDistance * this.maxDistance; + // 超过最大距离,剔除 + if (distanceSq > maxDistanceSq) { + return false; } - - skipList.clear(); - - // 1. 快速检查缓存 - int id = 0; - for (int x = minX; x <= maxX; x++) { - for (int y = minY; y <= maxY; y++) { - for (int z = minZ; z <= maxZ; z++) { - int cachedValue = getCacheValue(x, y, z); - if (cachedValue == 1) return true; // 缓存显示可见 - if (cachedValue != 0) skipList.set(id); // 缓存显示不可见或遮挡 - id++; - } - } - } - - allowRayChecks = false; - id = 0; - - // 2. 遍历体素进行光线投射检查 - for (int x = minX; x <= maxX; x++) { - // 预计算X轴面的可见性和边缘数据 - byte visibleOnFaceX = 0; - byte faceEdgeDataX = 0; - if (x == minX) { faceEdgeDataX |= ON_MIN_X; if (relX == Relative.POSITIVE) visibleOnFaceX |= ON_MIN_X; } - if (x == maxX) { faceEdgeDataX |= ON_MAX_X; if (relX == Relative.NEGATIVE) visibleOnFaceX |= ON_MAX_X; } - - for (int y = minY; y <= maxY; y++) { - byte visibleOnFaceY = visibleOnFaceX; - byte faceEdgeDataY = faceEdgeDataX; - if (y == minY) { faceEdgeDataY |= ON_MIN_Y; if (relY == Relative.POSITIVE) visibleOnFaceY |= ON_MIN_Y; } - if (y == maxY) { faceEdgeDataY |= ON_MAX_Y; if (relY == Relative.NEGATIVE) visibleOnFaceY |= ON_MAX_Y; } - - for (int z = minZ; z <= maxZ; z++) { - // 如果缓存已标记为不可见,跳过 - if(skipList.get(id++)) continue; - - byte visibleOnFace = visibleOnFaceY; - byte faceEdgeData = faceEdgeDataY; - if (z == minZ) { faceEdgeData |= ON_MIN_Z; if (relZ == Relative.POSITIVE) visibleOnFace |= ON_MIN_Z; } - if (z == maxZ) { faceEdgeData |= ON_MAX_Z; if (relZ == Relative.NEGATIVE) visibleOnFace |= ON_MAX_Z; } - - if (visibleOnFace != 0) { - targetPos.set(x, y, z); - // 检查单个体素是否可见 - if (isVoxelVisible(viewerPosition, targetPos, faceEdgeData, visibleOnFace)) { - return true; - } - } - } - } - } - return false; - } catch (Throwable t) { - t.printStackTrace(); - return true; // 发生异常默认可见,防止渲染错误 - } - } - - // 接口定义 - public interface DataProvider { - boolean prepareChunk(int chunkX, int chunkZ); - boolean isOpaqueFullCube(int x, int y, int z); - default void cleanup() {} - default void checkingPosition(MutableVec3d[] targetPoints, int size, MutableVec3d viewerPosition) {} - } - - /** - * 检查单个体素是否对观察者可见 - */ - private boolean isVoxelVisible(MutableVec3d viewerPosition, MutableVec3d position, byte faceData, byte visibleOnFace) { - int targetSize = 0; - Arrays.fill(dotselectors, false); - - // 根据相对位置选择需要检测的关键点(角点和面中心点) - if((visibleOnFace & ON_MIN_X) != 0){ - dotselectors[0] = true; - if((faceData & ~ON_MIN_X) != 0) { dotselectors[1] = dotselectors[4] = dotselectors[5] = true; } - dotselectors[8] = true; - } - if((visibleOnFace & ON_MIN_Y) != 0){ - dotselectors[0] = true; - if((faceData & ~ON_MIN_Y) != 0) { dotselectors[3] = dotselectors[4] = dotselectors[7] = true; } - dotselectors[9] = true; - } - if((visibleOnFace & ON_MIN_Z) != 0){ - dotselectors[0] = true; - if((faceData & ~ON_MIN_Z) != 0) { dotselectors[1] = dotselectors[4] = dotselectors[5] = true; } - dotselectors[10] = true; - } - if((visibleOnFace & ON_MAX_X) != 0){ - dotselectors[4] = true; - if((faceData & ~ON_MAX_X) != 0) { dotselectors[5] = dotselectors[6] = dotselectors[7] = true; } - dotselectors[11] = true; - } - if((visibleOnFace & ON_MAX_Y) != 0){ - dotselectors[1] = true; - if((faceData & ~ON_MAX_Y) != 0) { dotselectors[2] = dotselectors[5] = dotselectors[6] = true; } - dotselectors[12] = true; - } - if((visibleOnFace & ON_MAX_Z) != 0){ - dotselectors[2] = true; - if((faceData & ~ON_MAX_Z) != 0) { dotselectors[3] = dotselectors[6] = dotselectors[7] = true; } - dotselectors[13] = true; } - // 填充目标点,使用偏移量防止Z-Fighting或精度问题 - if (dotselectors[0]) targetPoints[targetSize++].add(position, 0.05, 0.05, 0.05); - if (dotselectors[1]) targetPoints[targetSize++].add(position, 0.05, 0.95, 0.05); - if (dotselectors[2]) targetPoints[targetSize++].add(position, 0.05, 0.95, 0.95); - if (dotselectors[3]) targetPoints[targetSize++].add(position, 0.05, 0.05, 0.95); - if (dotselectors[4]) targetPoints[targetSize++].add(position, 0.95, 0.05, 0.05); - if (dotselectors[5]) targetPoints[targetSize++].add(position, 0.95, 0.95, 0.05); - if (dotselectors[6]) targetPoints[targetSize++].add(position, 0.95, 0.95, 0.95); - if (dotselectors[7]) targetPoints[targetSize++].add(position, 0.95, 0.05, 0.95); + // 清空之前的缓存 + Arrays.fill(this.dotSelectors, false); + if (relX == Relative.POSITIVE) { + this.dotSelectors[0] = this.dotSelectors[2] = this.dotSelectors[4] = this.dotSelectors[6] = this.dotSelectors[10] = true; + } else if (relX == Relative.NEGATIVE) { + this.dotSelectors[1] = this.dotSelectors[3] = this.dotSelectors[5] = this.dotSelectors[7] = this.dotSelectors[11] = true; + } + if (relY == Relative.POSITIVE) { + this.dotSelectors[0] = this.dotSelectors[1] = this.dotSelectors[2] = this.dotSelectors[3] = this.dotSelectors[12] = true; + } else if (relY == Relative.NEGATIVE) { + this.dotSelectors[4] = this.dotSelectors[5] = this.dotSelectors[6] = this.dotSelectors[7] = this.dotSelectors[13] = true; + } + if (relZ == Relative.POSITIVE) { + this.dotSelectors[0] = this.dotSelectors[1] = this.dotSelectors[4] = this.dotSelectors[5] = this.dotSelectors[8] = true; + } else if (relZ == Relative.NEGATIVE) { + this.dotSelectors[2] = this.dotSelectors[3] = this.dotSelectors[6] = this.dotSelectors[7] = this.dotSelectors[9] = true; + } + + int size = 0; + if (this.dotSelectors[0]) targetPoints[size++].set(minX, minY, minZ); + if (this.dotSelectors[1]) targetPoints[size++].set(maxX, minY, minZ); + if (this.dotSelectors[2]) targetPoints[size++].set(minX, minY, maxZ); + if (this.dotSelectors[3]) targetPoints[size++].set(maxX, minY, maxZ); + if (this.dotSelectors[4]) targetPoints[size++].set(minX, maxY, minZ); + if (this.dotSelectors[5]) targetPoints[size++].set(maxX, maxY, minZ); + if (this.dotSelectors[6]) targetPoints[size++].set(minX, maxY, maxZ); + if (this.dotSelectors[7]) targetPoints[size++].set(maxX, maxY, maxZ); // 面中心点 - if (dotselectors[8]) targetPoints[targetSize++].add(position, 0.05, 0.5, 0.5); - if (dotselectors[9]) targetPoints[targetSize++].add(position, 0.5, 0.05, 0.5); - if (dotselectors[10]) targetPoints[targetSize++].add(position, 0.5, 0.5, 0.05); - if (dotselectors[11]) targetPoints[targetSize++].add(position, 0.95, 0.5, 0.5); - if (dotselectors[12]) targetPoints[targetSize++].add(position, 0.5, 0.95, 0.5); - if (dotselectors[13]) targetPoints[targetSize++].add(position, 0.5, 0.5, 0.95); + double averageX = (minX + maxX) / 2.0; + double averageY = (minY + maxY) / 2.0; + double averageZ = (minZ + maxZ) / 2.0; + if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ); + if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ); + if (this.dotSelectors[10]) targetPoints[size++].set(minX, averageY, averageZ); + if (this.dotSelectors[11]) targetPoints[size++].set(maxX, averageY, averageZ); + if (this.dotSelectors[12]) targetPoints[size++].set(averageX, minY, averageZ); + if (this.dotSelectors[13]) targetPoints[size].set(averageX, maxY, averageZ); - return isVisible(viewerPosition, targetPoints, targetSize); - } - - // 优化:使用基本数据类型代替对象分配 - private boolean rayIntersection(int[] b, MutableVec3d rayOrigin, double dirX, double dirY, double dirZ) { - double invX = 1.0 / dirX; - double invY = 1.0 / dirY; - double invZ = 1.0 / dirZ; - - double t1 = (b[0] - rayOrigin.x) * invX; - double t2 = (b[0] + 1 - rayOrigin.x) * invX; - double t3 = (b[1] - rayOrigin.y) * invY; - double t4 = (b[1] + 1 - rayOrigin.y) * invY; - double t5 = (b[2] - rayOrigin.z) * invZ; - double t6 = (b[2] + 1 - rayOrigin.z) * invZ; - - double tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6)); - double tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6)); - - // tmax > 0: 射线与AABB相交,但AABB在身后 - // tmin > tmax: 射线不相交 - return tmax > 0 && tmin <= tmax; + return isVisible(cameraPos, this.targetPoints, size); } /** - * 基于网格的光线追踪 (DDA算法) + * 使用3D DDA算法检测从起点到多个目标点的视线是否通畅 + * 算法基于数字微分分析,遍历射线路径上的所有方块 */ - private boolean isVisible(MutableVec3d start, MutableVec3d[] targets, int size) { - int startX = cameraPos[0]; - int startY = cameraPos[1]; - int startZ = cameraPos[2]; + private boolean isVisible(Vec3d start, MutableVec3d[] targets, int targetCount) { + // 起点所在方块的整数坐标(世界坐标转换为方块坐标) + int startBlockX = MiscUtils.floor(start.x); + int startBlockY = MiscUtils.floor(start.y); + int startBlockZ = MiscUtils.floor(start.z); - for (int v = 0; v < size; v++) { - MutableVec3d target = targets[v]; + // 遍历所有目标点进行视线检测 + for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) { + MutableVec3d currentTarget = targets[targetIndex]; - double relX = start.x - target.x; - double relY = start.y - target.y; - double relZ = start.z - target.z; + // 计算起点到目标的相对向量(世界坐标差) + double deltaX = start.x - currentTarget.x; + double deltaY = start.y - currentTarget.y; + double deltaZ = start.z - currentTarget.z; - // 优化:避免在此处创建新的Vec3d对象进行归一化 - if(allowRayChecks) { - double len = Math.sqrt(relX * relX + relY * relY + relZ * relZ); - // 传入归一化后的方向分量 - if (rayIntersection(lastHitBlock, start, relX / len, relY / len, relZ / len)) { - continue; - } - } - - double dimAbsX = Math.abs(relX); - double dimAbsY = Math.abs(relY); - double dimAbsZ = Math.abs(relZ); + // 计算相对向量的绝对值,用于确定各方向上的距离 + double absDeltaX = Math.abs(deltaX); + double absDeltaY = Math.abs(deltaY); + double absDeltaZ = Math.abs(deltaZ); - double dimFracX = 1f / dimAbsX; - double dimFracY = 1f / dimAbsY; - double dimFracZ = 1f / dimAbsZ; + // 预计算每单位距离在各方块边界上的步进增量 + // 这些值表示射线穿过一个方块所需的时间分数 + double stepIncrementX = 1.0 / (absDeltaX + 1e-10); // 避免除0 + double stepIncrementY = 1.0 / (absDeltaY + 1e-10); + double stepIncrementZ = 1.0 / (absDeltaZ + 1e-10); - int intersectCount = 1; - int x_inc, y_inc, z_inc; - double t_next_y, t_next_x, t_next_z; + // 射线将穿过的总方块数量(包括起点和终点) + int totalBlocksToCheck = 1; - // 初始化DDA步进参数 - if (dimAbsX == 0f) { - x_inc = 0; t_next_x = dimFracX; - } else if (target.x > start.x) { - x_inc = 1; - intersectCount += MiscUtils.fastFloor(target.x) - startX; - t_next_x = (startX + 1 - start.x) * dimFracX; + // 各方块坐标的步进方向(1: 正向, -1: 反向, 0: 静止) + int stepDirectionX, stepDirectionY, stepDirectionZ; + + // 到下一个方块边界的时间参数(射线参数化表示) + double nextStepTimeX, nextStepTimeY, nextStepTimeZ; + + // X方向步进参数计算 + if (absDeltaX == 0.0) { + // X方向无变化,射线平行于YZ平面 + stepDirectionX = 0; + nextStepTimeX = stepIncrementX; + } else if (currentTarget.x > start.x) { + // 目标在起点右侧,向右步进 + stepDirectionX = 1; + totalBlocksToCheck += MiscUtils.floor(currentTarget.x) - startBlockX; + nextStepTimeX = (startBlockX + 1 - start.x) * stepIncrementX; } else { - x_inc = -1; - intersectCount += startX - MiscUtils.fastFloor(target.x); - t_next_x = (start.x - startX) * dimFracX; + // 目标在起点左侧,向左步进 + stepDirectionX = -1; + totalBlocksToCheck += startBlockX - MiscUtils.floor(currentTarget.x); + nextStepTimeX = (start.x - startBlockX) * stepIncrementX; } - if (dimAbsY == 0f) { - y_inc = 0; t_next_y = dimFracY; - } else if (target.y > start.y) { - y_inc = 1; - intersectCount += MiscUtils.fastFloor(target.y) - startY; - t_next_y = (startY + 1 - start.y) * dimFracY; + // Y方向步进参数计算 + if (absDeltaY == 0.0) { + // Y方向无变化,射线平行于XZ平面 + stepDirectionY = 0; + nextStepTimeY = stepIncrementY; + } else if (currentTarget.y > start.y) { + // 目标在起点上方,向上步进 + stepDirectionY = 1; + totalBlocksToCheck += MiscUtils.floor(currentTarget.y) - startBlockY; + nextStepTimeY = (startBlockY + 1 - start.y) * stepIncrementY; } else { - y_inc = -1; - intersectCount += startY - MiscUtils.fastFloor(target.y); - t_next_y = (start.y - startY) * dimFracY; + // 目标在起点下方,向下步进 + stepDirectionY = -1; + totalBlocksToCheck += startBlockY - MiscUtils.floor(currentTarget.y); + nextStepTimeY = (start.y - startBlockY) * stepIncrementY; } - if (dimAbsZ == 0f) { - z_inc = 0; t_next_z = dimFracZ; - } else if (target.z > start.z) { - z_inc = 1; - intersectCount += MiscUtils.fastFloor(target.z) - startZ; - t_next_z = (startZ + 1 - start.z) * dimFracZ; + // Z方向步进参数计算 + if (absDeltaZ == 0.0) { + // Z方向无变化,射线平行于XY平面 + stepDirectionZ = 0; + nextStepTimeZ = stepIncrementZ; + } else if (currentTarget.z > start.z) { + // 目标在起点前方,向前步进 + stepDirectionZ = 1; + totalBlocksToCheck += MiscUtils.floor(currentTarget.z) - startBlockZ; + nextStepTimeZ = (startBlockZ + 1 - start.z) * stepIncrementZ; } else { - z_inc = -1; - intersectCount += startZ - MiscUtils.fastFloor(target.z); - t_next_z = (start.z - startZ) * dimFracZ; + // 目标在起点后方,向后步进 + stepDirectionZ = -1; + totalBlocksToCheck += startBlockZ - MiscUtils.floor(currentTarget.z); + nextStepTimeZ = (start.z - startBlockZ) * stepIncrementZ; } - boolean finished = stepRay(startX, startY, startZ, - dimFracX, dimFracY, dimFracZ, intersectCount, - x_inc, y_inc, z_inc, - t_next_y, t_next_x, t_next_z); - - provider.cleanup(); - if (finished) { - cacheResult(targets[0], true); + // 执行DDA步进算法,遍历射线路径上的所有方块 + boolean isLineOfSightClear = stepRay( + startBlockX, startBlockY, startBlockZ, + stepIncrementX, stepIncrementY, stepIncrementZ, totalBlocksToCheck, + stepDirectionX, stepDirectionY, stepDirectionZ, + nextStepTimeY, nextStepTimeX, nextStepTimeZ); + + // 如果当前目标点可见立即返回 + if (isLineOfSightClear) { return true; - } else { - allowRayChecks = true; } } - cacheResult(targets[0], false); + return false; } - private boolean stepRay(int currentX, int currentY, int currentZ, - double distInX, double distInY, double distInZ, - int n, int x_inc, int y_inc, int z_inc, - double t_next_y, double t_next_x, double t_next_z) { - - allowWallClipping = true; // 初始允许穿墙直到移出起始方块 + private boolean stepRay(int currentBlockX, int currentBlockY, int currentBlockZ, + double stepSizeX, double stepSizeY, double stepSizeZ, + int remainingSteps, int stepDirectionX, int stepDirectionY, + int stepDirectionZ, double nextStepTimeY, double nextStepTimeX, + double nextStepTimeZ) { - for (; n > 1; n--) { - // 检查缓存状态:2=遮挡 - int cVal = getCacheValue(currentX, currentY, currentZ); - if (cVal == 2 && !allowWallClipping) { - lastHitBlock[0] = currentX; lastHitBlock[1] = currentY; lastHitBlock[2] = currentZ; - return false; + // 遍历射线路径上的所有方块(跳过最后一个目标方块) + for (; remainingSteps > 1; remainingSteps--) { + + // 检查当前方块是否遮挡视线 + if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) { + return false; // 视线被遮挡,立即返回 } - if (cVal == 0) { - // 未缓存,查询Provider - int chunkX = currentX >> 4; - int chunkZ = currentZ >> 4; - if (!provider.prepareChunk(chunkX, chunkZ)) return false; - - if (provider.isOpaqueFullCube(currentX, currentY, currentZ)) { - if (!allowWallClipping) { - cache.setLastHidden(); - lastHitBlock[0] = currentX; lastHitBlock[1] = currentY; lastHitBlock[2] = currentZ; - return false; - } - } else { - allowWallClipping = false; - cache.setLastVisible(); - } - } else if(cVal == 1) { - allowWallClipping = false; - } - - // DDA算法选择下一个体素 - if (t_next_y < t_next_x && t_next_y < t_next_z) { - currentY += y_inc; - t_next_y += distInY; - } else if (t_next_x < t_next_y && t_next_x < t_next_z) { - currentX += x_inc; - t_next_x += distInX; + // 基于时间参数选择下一个要遍历的方块方向 + // 选择距离最近的方块边界作为下一步 + if (nextStepTimeY < nextStepTimeX && nextStepTimeY < nextStepTimeZ) { + // Y方向边界最近,垂直移动 + currentBlockY += stepDirectionY; + nextStepTimeY += stepSizeY; + } else if (nextStepTimeX < nextStepTimeY && nextStepTimeX < nextStepTimeZ) { + // X方向边界最近,水平移动 + currentBlockX += stepDirectionX; + nextStepTimeX += stepSizeX; } else { - currentZ += z_inc; - t_next_z += distInZ; + // Z方向边界最近,深度移动 + currentBlockZ += stepDirectionZ; + nextStepTimeZ += stepSizeZ; } } + + // 成功遍历所有中间方块,视线通畅 return true; } - // 缓存状态:-1=无效, 0=未检查, 1=可见, 2=遮挡 - private int getCacheValue(int x, int y, int z) { - x -= cameraPos[0]; - y -= cameraPos[1]; - z -= cameraPos[2]; - if (Math.abs(x) > reach - 2 || Math.abs(y) > reach - 2 || Math.abs(z) > reach - 2) { - return -1; + private double distanceSq(int min, int max, double camera, Relative rel) { + if (rel == Relative.NEGATIVE) { + double dx = camera - max; + return dx * dx; + } else if (rel == Relative.POSITIVE) { + double dx = min - camera; + return dx * dx; } - return cache.getState(x + reach, y + reach, z + reach); + return 0d; } - private void cacheResult(MutableVec3d vector, boolean result) { - int cx = MiscUtils.fastFloor(vector.x) - cameraPos[0] + reach; - int cy = MiscUtils.fastFloor(vector.y) - cameraPos[1] + reach; - int cz = MiscUtils.fastFloor(vector.z) - cameraPos[2] + reach; - if (result) cache.setVisible(cx, cy, cz); - else cache.setHidden(cx, cy, cz); - } - - public void resetCache() { - this.cache.resetCache(); + private boolean isOccluding(int x, int y, int z) { + ClientChunk trackedChunk = this.player.getTrackedChunk(ChunkPos.asLong(x >> 4, z >> 4)); + if (trackedChunk == null) { + return false; + } + return trackedChunk.isOccluding(x, y, z); } private enum Relative { INSIDE, POSITIVE, NEGATIVE; - public static Relative from(int min, int max, int pos) { - if (max > pos && min > pos) return POSITIVE; - else if (min < pos && max < pos) return NEGATIVE; + public static Relative from(int min, int max, double pos) { + if (min > pos) return POSITIVE; + else if (max < pos) return NEGATIVE; return INSIDE; } } - - public interface OcclusionCache { - void resetCache(); - void setVisible(int x, int y, int z); - void setHidden(int x, int y, int z); - int getState(int x, int y, int z); - void setLastHidden(); - void setLastVisible(); - } - - // 使用位运算压缩存储状态的缓存实现 - public static class ArrayOcclusionCache implements OcclusionCache { - private final int reachX2; - private final byte[] cache; - private int entry, offset; - - public ArrayOcclusionCache(int reach) { - this.reachX2 = reach * 2; - // 每一个位置占2位 - this.cache = new byte[(reachX2 * reachX2 * reachX2) / 4 + 1]; - } - - @Override - public void resetCache() { - Arrays.fill(cache, (byte) 0); - } - - private void calcIndex(int x, int y, int z) { - int positionKey = x + y * reachX2 + z * reachX2 * reachX2; - entry = positionKey / 4; - offset = (positionKey % 4) * 2; - } - - @Override - public void setVisible(int x, int y, int z) { - calcIndex(x, y, z); - cache[entry] |= 1 << offset; - } - - @Override - public void setHidden(int x, int y, int z) { - calcIndex(x, y, z); - cache[entry] |= 1 << (offset + 1); - } - - @Override - public int getState(int x, int y, int z) { - calcIndex(x, y, z); - return (cache[entry] >> offset) & 3; - } - - @Override - public void setLastVisible() { - cache[entry] |= 1 << offset; - } - - @Override - public void setLastHidden() { - cache[entry] |= 1 << (offset + 1); - } - } -} \ No newline at end of file +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java new file mode 100644 index 000000000..a77160fa1 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java @@ -0,0 +1,216 @@ +package net.momirealms.craftengine.core.plugin.entityculling; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +// Amanatides, J., & Woo, A. A Fast Voxel Traversal Algorithm for Ray Tracing. http://www.cse.yorku.ca/~amana/research/grid.pdf. +public final class VoxelIterator implements Iterator { + private int x; + private int y; + private int z; + private int stepX; + private int stepY; + private int stepZ; + private double tMax; + private double tMaxX; + private double tMaxY; + private double tMaxZ; + private double tDeltaX; + private double tDeltaY; + private double tDeltaZ; + private int[] ref = new int[3]; // This implementation always returns ref or refSwap to avoid garbage. Can easily be changed if needed. + private int[] refSwap = new int[3]; + private int[] next; + + public VoxelIterator(double startX, double startY, double startZ, double endX, double endY, double endZ) { + initialize(startX, startY, startZ, endX, endY, endZ); + } + + public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double endX, double endY, double endZ) { + initialize(x, y, z, startX, startY, startZ, endX, endY, endZ); + } + + public VoxelIterator(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + initialize(startX, startY, startZ, directionX, directionY, directionZ, distance); + } + + public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + initialize(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); + } + + public VoxelIterator(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance, boolean normalized) { + if (normalized) { + initializeNormalized(startX, startY, startZ, directionX, directionY, directionZ, distance); + } else { + initialize(startX, startY, startZ, directionX, directionY, directionZ, distance); + } + } + + public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance, boolean normalized) { + if (normalized) { + initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); + } else { + initialize(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); + } + } + + public VoxelIterator initialize(double startX, double startY, double startZ, double endX, double endY, double endZ) { + return initialize(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, endX, endY, endZ); + } + + public VoxelIterator initialize(int x, int y, int z, double startX, double startY, double startZ, double endX, double endY, double endZ) { + double directionX = endX - startX; + double directionY = endY - startY; + double directionZ = endZ - startZ; + double distance = Math.sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ); + double fixedDistance = distance == 0. ? Double.NaN : distance; + directionX /= fixedDistance; + directionY /= fixedDistance; + directionZ /= fixedDistance; + return initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); + } + + public VoxelIterator initialize(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + return initialize(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, directionX, directionY, directionZ, distance); + } + + public VoxelIterator initialize(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + double signum = Math.signum(distance); + directionX *= signum; + directionY *= signum; + directionZ *= signum; + double length = Math.sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ); + + if (length == 0.) { + length = Double.NaN; + } + + directionX /= length; + directionY /= length; + directionZ /= length; + return initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, Math.abs(distance)); + } + + public VoxelIterator initializeNormalized(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + return initializeNormalized(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, directionX, directionY, directionZ, Math.abs(distance)); + } + + public VoxelIterator initializeNormalized(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + this.x = x; + this.y = y; + this.z = z; + tMax = distance; + stepX = directionX < 0. ? -1 : 1; + stepY = directionY < 0. ? -1 : 1; + stepZ = directionZ < 0. ? -1 : 1; + tMaxX = directionX == 0. ? Double.POSITIVE_INFINITY : (x + (stepX + 1) / 2 - startX) / directionX; + tMaxY = directionY == 0. ? Double.POSITIVE_INFINITY : (y + (stepY + 1) / 2 - startY) / directionY; + tMaxZ = directionZ == 0. ? Double.POSITIVE_INFINITY : (z + (stepZ + 1) / 2 - startZ) / directionZ; + tDeltaX = 1. / Math.abs(directionX); + tDeltaY = 1. / Math.abs(directionY); + tDeltaZ = 1. / Math.abs(directionZ); + next = ref; + ref[0] = x; + ref[1] = y; + ref[2] = z; + return this; + } + + public int[] calculateNext() { + if (tMaxX < tMaxY) { + if (tMaxZ < tMaxX) { + if (tMaxZ <= tMax) { + z += stepZ; + // next = new int[] { x, y, z }; + ref[0] = x; + ref[1] = y; + ref[2] = z; + tMaxZ += tDeltaZ; + } else { + next = null; + } + } else { + if (tMaxX <= tMax) { + if (tMaxZ == tMaxX) { + z += stepZ; + tMaxZ += tDeltaZ; + } + + x += stepX; + // next = new int[] { x, y, z }; + ref[0] = x; + ref[1] = y; + ref[2] = z; + tMaxX += tDeltaX; + } else { + next = null; + } + } + } else if (tMaxY < tMaxZ) { + if (tMaxY <= tMax) { + if (tMaxX == tMaxY) { + x += stepX; + tMaxX += tDeltaX; + } + + y += stepY; + // next = new int[] { x, y, z }; + ref[0] = x; + ref[1] = y; + ref[2] = z; + tMaxY += tDeltaY; + } else { + next = null; + } + } else { + if (tMaxZ <= tMax) { + if (tMaxX == tMaxZ) { + x += stepX; + tMaxX += tDeltaX; + } + + if (tMaxY == tMaxZ) { + y += stepY; + tMaxY += tDeltaY; + } + + z += stepZ; + // next = new int[] { x, y, z }; + ref[0] = x; + ref[1] = y; + ref[2] = z; + tMaxZ += tDeltaZ; + } else { + next = null; + } + } + + return next; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public int[] next() { + int[] next = this.next; + + if (next == null) { + throw new NoSuchElementException(); + } + + int[] temp = ref; + ref = refSwap; + refSwap = temp; + this.next = ref; + calculateNext(); + return next; + } + + private static int floor(double value) { + int i = (int) value; + return value < (double) i ? i - 1 : i; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Color.java b/core/src/main/java/net/momirealms/craftengine/core/util/Color.java index 5f37d42b0..38299e43b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Color.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Color.java @@ -38,7 +38,7 @@ public class Color { } public static Color fromVector3f(Vector3f vec) { - return new Color(0 << 24 /*不可省略*/ | MiscUtils.fastFloor(vec.x) << 16 | MiscUtils.fastFloor(vec.y) << 8 | MiscUtils.fastFloor(vec.z)); + return new Color(0 << 24 /*不可省略*/ | MiscUtils.floor(vec.x) << 16 | MiscUtils.floor(vec.y) << 8 | MiscUtils.floor(vec.z)); } public static int opaque(int color) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java index 2e28001c2..523bbc364 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java @@ -21,19 +21,19 @@ public class MiscUtils { } }); - public static int fastFloor(double value) { + public static int floor(double value) { int truncated = (int) value; return value < (double) truncated ? truncated - 1 : truncated; } - public static int fastFloor(float value) { + public static int floor(float value) { int truncated = (int) value; return value < (double) truncated ? truncated - 1 : truncated; } public static int lerpDiscrete(float delta, int start, int end) { int i = end - start; - return start + fastFloor(delta * (float) (i - 1)) + (delta > 0.0F ? 1 : 0); + return start + floor(delta * (float) (i - 1)) + (delta > 0.0F ? 1 : 0); } public static int murmurHash3Mixer(int value) { @@ -270,7 +270,7 @@ public class MiscUtils { } public static byte packDegrees(float degrees) { - return (byte) fastFloor(degrees * 256.0F / 360.0F); + return (byte) floor(degrees * 256.0F / 360.0F); } public static float unpackDegrees(byte degrees) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java index 38a9acf20..71838e8c3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java @@ -23,7 +23,7 @@ public class BlockPos extends Vec3i { } public static BlockPos fromVec3d(Vec3d vec) { - return new BlockPos(MiscUtils.fastFloor(vec.x), MiscUtils.fastFloor(vec.y), MiscUtils.fastFloor(vec.z)); + return new BlockPos(MiscUtils.floor(vec.x), MiscUtils.floor(vec.y), MiscUtils.floor(vec.z)); } public static BlockPos of(long packedPos) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java b/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java index 6944337ed..fed33e417 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java @@ -23,9 +23,9 @@ public class EntityHitResult { } private BlockPos getBlockPos() { - int x = MiscUtils.fastFloor(this.position.x); - int y = MiscUtils.fastFloor(this.position.y); - int z = MiscUtils.fastFloor(this.position.z); + int x = MiscUtils.floor(this.position.x); + int y = MiscUtils.floor(this.position.y); + int z = MiscUtils.floor(this.position.z); if (this.direction == Direction.UP) { if (this.position.y % 1 == 0) { y--; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java b/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java index 1f4a989ac..dd469e7ec 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java @@ -14,9 +14,9 @@ public class MutableVec3d implements Position { } public MutableVec3d toCenter() { - this.x = MiscUtils.fastFloor(x) + 0.5; - this.y = MiscUtils.fastFloor(y) + 0.5; - this.z = MiscUtils.fastFloor(z) + 0.5; + this.x = MiscUtils.floor(x) + 0.5; + this.y = MiscUtils.floor(y) + 0.5; + this.z = MiscUtils.floor(z) + 0.5; return this; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java index 7165596a0..fe3cb6938 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java @@ -15,7 +15,7 @@ public class Vec3d implements Position { } public Vec3d toCenter() { - return new Vec3d(MiscUtils.fastFloor(x) + 0.5, MiscUtils.fastFloor(y) + 0.5, MiscUtils.fastFloor(z) + 0.5); + return new Vec3d(MiscUtils.floor(x) + 0.5, MiscUtils.floor(y) + 0.5, MiscUtils.floor(z) + 0.5); } public Vec3d add(Vec3d vec) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index f12123ae2..4ca2fe5df 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject; import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntityRendererSerializer; import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntitySerializer; import net.momirealms.sparrow.nbt.ListTag; @@ -155,8 +156,20 @@ public class CEChunk { if (element != null) { elements[0] = element; if (hasTrackedBy) { - for (Player player : trackedBy) { - element.transform(player); + // 如果启用实体剔除,那么只对已经渲染的进行变换 + if (Config.enableEntityCulling()) { + for (Player player : trackedBy) { + VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); + if (trackedBlockEntity == null || trackedBlockEntity.isShown()) { + element.transform(player); + } + } + } + // 否则直接变换 + else { + for (Player player : trackedBy) { + element.transform(player); + } } } break outer; @@ -165,9 +178,18 @@ public class CEChunk { BlockEntityElement element = config.create(wrappedWorld, pos); elements[0] = element; if (hasTrackedBy) { - for (Player player : trackedBy) { - previousElement.hide(player); - element.show(player); + // 如果启用实体剔除,那么只添加记录 + if (Config.enableEntityCulling()) { + for (Player player : trackedBy) { + player.addTrackedBlockEntity(pos, renderer); + } + } + // 否则直接显示 + else { + for (Player player : trackedBy) { + previousElement.hide(player); + element.show(player); + } } } } @@ -182,8 +204,20 @@ public class CEChunk { previousElements[j] = null; elements[i] = newElement; if (hasTrackedBy) { - for (Player player : trackedBy) { - newElement.transform(player); + // 如果启用实体剔除,那么只对已经渲染的进行变换 + if (Config.enableEntityCulling()) { + for (Player player : trackedBy) { + VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); + if (trackedBlockEntity == null || trackedBlockEntity.isShown()) { + newElement.transform(player); + } + } + } + // 否则直接变换 + else { + for (Player player : trackedBy) { + newElement.transform(player); + } } } continue outer; @@ -193,8 +227,14 @@ public class CEChunk { BlockEntityElement newElement = config.create(wrappedWorld, pos); elements[i] = newElement; if (hasTrackedBy) { - for (Player player : trackedBy) { - newElement.show(player); + if (Config.enableEntityCulling()) { + for (Player player : trackedBy) { + player.addTrackedBlockEntity(pos, renderer); + } + } else { + for (Player player : trackedBy) { + newElement.show(player); + } } } } @@ -214,8 +254,14 @@ public class CEChunk { elements[i] = renderers[i].create(wrappedWorld, pos); } if (hasTrackedBy) { - for (Player player : trackedBy) { - renderer.show(player); + if (Config.enableEntityCulling()) { + for (Player player : trackedBy) { + player.addTrackedBlockEntity(pos, renderer); + } + } else { + for (Player player : trackedBy) { + renderer.show(player); + } } } } @@ -242,8 +288,14 @@ public class CEChunk { ConstantBlockEntityRenderer removed = this.constantBlockEntityRenderers.remove(pos); if (removed != null) { if (hide) { - for (Player player : getTrackedBy()) { - removed.hide(player); + if (Config.enableEntityCulling()) { + for (Player player : getTrackedBy()) { + player.removeTrackedBlockEntities(List.of(pos)); + } + } else { + for (Player player : getTrackedBy()) { + removed.hide(player); + } } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java index 29379fd21..03e0ac6ad 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java @@ -32,7 +32,7 @@ public class ClientChunk { int index = sectionIndex(SectionPos.blockToSectionCoord(y)); ClientSection section = this.sections[index]; if (section == null) return; - section.setOccluding((y & 15) << 8 | (z & 15) << 4, occluding); + section.setOccluding((y & 15) << 8 | (z & 15) << 4 | x & 15, occluding); } public int sectionIndex(int sectionId) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java index 127b8b09e..80b6f9ef2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java @@ -4,7 +4,7 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.world.Cullable; public class VirtualCullableObject { - private final Cullable cullable; + public final Cullable cullable; private boolean isShown; public VirtualCullableObject(Cullable cullable) { @@ -21,6 +21,7 @@ public class VirtualCullableObject { } public void setShown(Player player, boolean shown) { + if (this.isShown == shown) return; this.isShown = shown; if (shown) { this.cullable.show(player); From 399a01cd1b827beb3a732a1369d7da264c8318fc Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 28 Nov 2025 22:49:35 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E5=AF=B9=E4=BA=8E=E9=A2=91=E7=B9=81?= =?UTF-8?q?=E5=91=BD=E4=B8=AD=E5=AF=B9=E8=B1=A1=E8=BF=9B=E8=A1=8C=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E9=A2=91=E6=AC=A1=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/entityculling/EntityCulling.java | 98 +++++++++++++++++-- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java index fe190845b..2b16695bf 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java @@ -11,22 +11,33 @@ import net.momirealms.craftengine.core.world.collision.AABB; import java.util.Arrays; public final class EntityCulling { + public static final int MAX_SAMPLES = 14; private final Player player; private final int maxDistance; private final double aabbExpansion; - private final boolean[] dotSelectors = new boolean[14]; - private final MutableVec3d[] targetPoints = new MutableVec3d[14]; - + private final boolean[] dotSelectors = new boolean[MAX_SAMPLES]; + private final MutableVec3d[] targetPoints = new MutableVec3d[MAX_SAMPLES]; + private final int[] lastHitBlock = new int[MAX_SAMPLES * 3]; + private final boolean[] canCheckLastHitBlock = new boolean[MAX_SAMPLES]; + private int hitBlockCount = 0; + private int lastVisitChunkX = Integer.MAX_VALUE; + private int lastVisitChunkZ = Integer.MAX_VALUE; + private ClientChunk lastVisitChunk = null; + public EntityCulling(Player player, int maxDistance, double aabbExpansion) { this.player = player; this.maxDistance = maxDistance; this.aabbExpansion = aabbExpansion; - for (int i = 0; i < this.targetPoints.length; i++) { + for (int i = 0; i < MAX_SAMPLES; i++) { this.targetPoints[i] = new MutableVec3d(0,0,0); } } public boolean isVisible(AABB aabb, Vec3d cameraPos) { + // 情空标志位 + Arrays.fill(this.canCheckLastHitBlock, false); + this.hitBlockCount = 0; + // 根据AABB获取能包裹此AABB的最小长方体 int minX = MiscUtils.floor(aabb.minX - this.aabbExpansion); int minY = MiscUtils.floor(aabb.minY - this.aabbExpansion); @@ -105,18 +116,59 @@ public final class EntityCulling { return isVisible(cameraPos, this.targetPoints, size); } + /** + * 检测射线与轴对齐边界框(AABB)是否相交 + * 使用slab方法进行射线-AABB相交检测 + */ + private boolean rayIntersection(int x, int y, int z, Vec3d rayOrigin, MutableVec3d rayDirection) { + // 计算射线方向的倒数,避免除法运算 + // 这对于处理射线方向分量为0的情况很重要 + MutableVec3d inverseRayDirection = new MutableVec3d(1, 1, 1).divide(rayDirection); + + // 计算射线与边界框各对面(slab)的相交参数 + // 对于每个轴,计算射线进入和退出该轴对应两个平面的时间 + double tMinX = (x - rayOrigin.x) * inverseRayDirection.x; + double tMaxX = (x + 1 - rayOrigin.x) * inverseRayDirection.x; + double tMinY = (y - rayOrigin.y) * inverseRayDirection.y; + double tMaxY = (y + 1 - rayOrigin.y) * inverseRayDirection.y; + double tMinZ = (z - rayOrigin.z) * inverseRayDirection.z; + double tMaxZ = (z + 1 - rayOrigin.z) * inverseRayDirection.z; + + // 计算射线进入边界框的最大时间(最近进入点) + // 需要取各轴进入时间的最大值,因为射线必须进入所有轴的范围内 + double tEntry = Math.max(Math.max(Math.min(tMinX, tMaxX), Math.min(tMinY, tMaxY)), Math.min(tMinZ, tMaxZ)); + + // 计算射线退出边界框的最短时间(最早退出点) + // 需要取各轴退出时间的最小值,因为射线一旦退出任一轴的范围就离开了边界框 + double tExit = Math.min(Math.min(Math.max(tMinX, tMaxX), Math.max(tMinY, tMaxY)), Math.max(tMinZ, tMaxZ)); + + // 如果最早退出时间大于0,说明整个边界框在射线起点后面 + // 这种情况我们视为不相交,因为通常我们只关心射线前方的相交 + if (tExit > 0) { + return false; + } + + // 如果进入时间大于退出时间,说明没有有效的相交区间 + // 这发生在射线完全错过边界框的情况下 + // 满足以下条件说明射线与边界框相交: + // 1. 进入时间 <= 退出时间(存在有效相交区间) + // 2. 退出时间 <= 0(边界框至少有一部分在射线起点前方或包含起点) + return tEntry <= tExit; + } + /** * 使用3D DDA算法检测从起点到多个目标点的视线是否通畅 * 算法基于数字微分分析,遍历射线路径上的所有方块 */ private boolean isVisible(Vec3d start, MutableVec3d[] targets, int targetCount) { + // 起点所在方块的整数坐标(世界坐标转换为方块坐标) int startBlockX = MiscUtils.floor(start.x); int startBlockY = MiscUtils.floor(start.y); int startBlockZ = MiscUtils.floor(start.z); // 遍历所有目标点进行视线检测 - for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) { + outer: for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) { MutableVec3d currentTarget = targets[targetIndex]; // 计算起点到目标的相对向量(世界坐标差) @@ -124,6 +176,18 @@ public final class EntityCulling { double deltaY = start.y - currentTarget.y; double deltaZ = start.z - currentTarget.z; + // 检查之前命中的方块,大概率还是命中 + for (int i = 0; i < MAX_SAMPLES; i++) { + if (this.canCheckLastHitBlock[i]) { + int offset = i * 3; + if (rayIntersection(this.lastHitBlock[offset], this.lastHitBlock[offset + 1], this.lastHitBlock[offset + 2], start, new MutableVec3d(deltaX, deltaY, deltaZ).normalize())) { + continue outer; + } + } else { + break; + } + } + // 计算相对向量的绝对值,用于确定各方向上的距离 double absDeltaX = Math.abs(deltaX); double absDeltaY = Math.abs(deltaY); @@ -205,6 +269,8 @@ public final class EntityCulling { // 如果当前目标点可见立即返回 if (isLineOfSightClear) { return true; + } else { + this.canCheckLastHitBlock[this.hitBlockCount++] = true; } } @@ -222,6 +288,9 @@ public final class EntityCulling { // 检查当前方块是否遮挡视线 if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) { + this.lastHitBlock[this.hitBlockCount * 3] = currentBlockX; + this.lastHitBlock[this.hitBlockCount * 3 + 1] = currentBlockY; + this.lastHitBlock[this.hitBlockCount * 3 + 2] = currentBlockZ; return false; // 视线被遮挡,立即返回 } @@ -258,13 +327,30 @@ public final class EntityCulling { } private boolean isOccluding(int x, int y, int z) { - ClientChunk trackedChunk = this.player.getTrackedChunk(ChunkPos.asLong(x >> 4, z >> 4)); + int chunkX = x >> 4; + int chunkZ = z >> 4; + ClientChunk trackedChunk; + // 使用上次记录的值,比每次走hash都更快 + if (chunkX == this.lastVisitChunkX && chunkZ == this.lastVisitChunkZ) { + trackedChunk = this.lastVisitChunk; + } else { + trackedChunk = this.player.getTrackedChunk(ChunkPos.asLong(chunkX, chunkZ)); + this.lastVisitChunk = trackedChunk; + this.lastVisitChunkX = chunkX; + this.lastVisitChunkZ = chunkZ; + } if (trackedChunk == null) { return false; } return trackedChunk.isOccluding(x, y, z); } + public void removeLastVisitChunkIfMatches(int chunkX, int chunkZ) { + if (this.lastVisitChunk != null && this.lastVisitChunkX == chunkX && this.lastVisitChunkZ == chunkZ) { + this.lastVisitChunk = null; + } + } + private enum Relative { INSIDE, POSITIVE, NEGATIVE; public static Relative from(int min, int max, double pos) {